001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.vfs;
030:
031: import com.caucho.server.util.CauchoSystem;
032: import com.caucho.util.Alarm;
033: import com.caucho.util.CharBuffer;
034: import com.caucho.util.Log;
035: import com.caucho.util.NullIterator;
036: import com.caucho.util.QDate;
037: import com.caucho.util.StringCharCursor;
038:
039: import java.io.IOException;
040: import java.io.InputStream;
041: import java.net.Socket;
042: import java.util.ArrayList;
043: import java.util.HashMap;
044: import java.util.Iterator;
045: import java.util.logging.Level;
046: import java.util.logging.Logger;
047:
048: /**
049: * Implementation of the SMTP/RFC822 protocol to handle mailto:
050: *
051: * <p>SmtpStream extends MemoryStream so the results will be buffered
052: * until the close(). When the stream is finally closed, it will
053: * send the results to the SMTP server.
054: */
055: class SmtpStream extends MemoryStream {
056: static final Logger log = Log.open(SmtpStream.class);
057:
058: // list of recipients
059: ArrayList<MailtoPath.Recipient> _to;
060: // list of copied recipients
061: ArrayList<MailtoPath.Recipient> _cc;
062: // list of blind copied recipients
063: ArrayList<MailtoPath.Recipient> _bcc;
064:
065: private String _from;
066: private String _sender;
067:
068: private HashMap<String, Object> _attributes;
069:
070: // true if it's already been written
071: boolean _isClosed;
072:
073: /**
074: * Creates a new SmtpStream
075: *
076: * @param to list of recipients
077: * @param attributes list of attributes set in the path
078: */
079: SmtpStream(ArrayList<MailtoPath.Recipient> to,
080: HashMap<String, Object> attributes) throws IOException {
081: if (to.size() <= 0)
082: throw new IOException("No recipients in mailto");
083:
084: _to = new ArrayList<MailtoPath.Recipient>();
085: for (int i = 0; i < to.size(); i++)
086: _to.add(to.get(i));
087:
088: // the 'to' and 'cc' attributes need to be converted.
089: // so set them as if they were set by setAttribute
090: if (attributes != null) {
091: Iterator<String> iter = attributes.keySet().iterator();
092:
093: while (iter.hasNext()) {
094: String key = iter.next();
095:
096: try {
097: setAttribute(key, attributes.get(key));
098: } catch (IOException e) {
099: }
100: }
101: }
102: }
103:
104: /**
105: * Return the named attribute
106: */
107: public Object getAttribute(String name) throws IOException {
108: if (_attributes != null)
109: return _attributes.get(name.toLowerCase());
110: else
111: return null;
112: }
113:
114: public Iterator<String> getAttributeNames() {
115: if (_attributes != null)
116: return _attributes.keySet().iterator();
117: else
118: return NullIterator.create();
119: }
120:
121: /**
122: * Sets an attribute. Some attributes, like "date" and "sender" cannot
123: * be set. Any unknown attribute will be treated as a user RFC822
124: * header.
125: */
126: public void setAttribute(String name, Object value)
127: throws IOException {
128: name = name.toLowerCase();
129: if (name.equals("date") || name.equals("received")
130: || name.equals("return-path")
131: || name.equals("message-id"))
132: throw new IOException("cannot set property `" + name + "'");
133:
134: if (name.equals("to")) {
135: addTo((String) value);
136: return;
137: }
138: if (name.equals("cc")) {
139: addCc((String) value);
140: return;
141: }
142:
143: if (name.equals("bcc")) {
144: addBcc((String) value);
145: return;
146: }
147:
148: if (name.equals("from")) {
149: _from = (String) value;
150: return;
151: }
152:
153: if (name.equals("sender")) {
154: _sender = (String) value;
155: return;
156: }
157:
158: if (_attributes == null)
159: _attributes = new HashMap<String, Object>();
160:
161: _attributes.put(name, value);
162: }
163:
164: /**
165: * Add new recipients
166: *
167: * @param to a list of new recipients
168: */
169: public void addTo(String to) throws IOException {
170: StringCharCursor cursor = new StringCharCursor(to);
171:
172: ArrayList<MailtoPath.Recipient> list = MailtoPath
173: .parseAddressList(cursor);
174:
175: for (int i = 0; i < list.size(); i++)
176: _to.add(list.get(i));
177: }
178:
179: /**
180: * Add new copied recipients
181: *
182: * @param to a list of new recipients
183: */
184: public void addCc(String to) throws IOException {
185: StringCharCursor cursor = new StringCharCursor(to);
186:
187: ArrayList<MailtoPath.Recipient> list = MailtoPath
188: .parseAddressList(cursor);
189:
190: if (_cc == null)
191: _cc = list;
192: else {
193: for (int i = 0; i < list.size(); i++)
194: _cc.add(list.get(i));
195: }
196: }
197:
198: /**
199: * Add new blind copied recipients
200: *
201: * @param to a list of new recipients
202: */
203: public void addBcc(String to) throws IOException {
204: StringCharCursor cursor = new StringCharCursor(to);
205:
206: ArrayList<MailtoPath.Recipient> list = MailtoPath
207: .parseAddressList(cursor);
208:
209: if (_bcc == null)
210: _bcc = list;
211: else {
212: for (int i = 0; i < list.size(); i++)
213: _bcc.add(list.get(i));
214: }
215: }
216:
217: /**
218: * Returns the "from" user
219: */
220: public String getFrom() {
221: if (_from != null)
222: return _from;
223: else
224: return null;
225:
226: // return Registry.getString("/caucho.com/smtp.vfs/sender", null);
227: }
228:
229: /**
230: * Returns the sender
231: */
232: public String getSender() {
233: if (_sender != null)
234: return _sender;
235: else
236: return null;
237:
238: // return Registry.getString("/caucho.com/smtp.vfs/sender", null);
239: }
240:
241: /**
242: * Reads a response from the SMTP server, returning the status code.
243: *
244: * @param is the input stream to read the response from
245: * @param msg CharBuffer holding the server's response
246: * @return the status code read from the server
247: */
248: private int readResponse(InputStream is, CharBuffer msg)
249: throws IOException {
250: int value;
251:
252: do {
253: msg.clear();
254: value = 0;
255: int ch;
256: if ((ch = is.read()) >= '0' && ch <= '9') {
257: for (; ch >= '0' && ch <= '9'; ch = is.read()) {
258: msg.append((char) ch);
259: value = 10 * value + ch - '0';
260: }
261: }
262:
263: // Multiline responses, e.g. "200-Foo", indicate there will be a
264: // following line. So the value should be zeroed to force another
265: // iteration. (fixed by Michael Kolfman)
266: if (ch == '-')
267: value = 0;
268:
269: for (; ch != '\r' && ch != '\n'; ch = is.read())
270: msg.append((char) ch);
271:
272: if (ch == '\r')
273: ch = is.read();
274: } while (value == 0);
275:
276: if (log.isLoggable(Level.FINE))
277: log.fine(msg.toString());
278:
279: return value;
280: }
281:
282: /**
283: * Send the recipient list to the server.
284: *
285: * @param is ReadStream from the server
286: * @param os WriteStream to the server
287: * @param to list of recipients
288: * @param msg CharBuffer to receive the response
289: */
290: void sendRecipients(ReadStream is, WriteStream os,
291: ArrayList<MailtoPath.Recipient> to, CharBuffer msg)
292: throws IOException {
293: if (to == null)
294: return;
295:
296: for (int i = 0; i < to.size(); i++) {
297: MailtoPath.Recipient rcpt = to.get(i);
298:
299: os.print("RCPT TO: ");
300: os.print(rcpt.user);
301: if (rcpt.host != null) {
302: os.print("@");
303: os.print(rcpt.host);
304: }
305: os.print("\r\n");
306:
307: if (log.isLoggable(Level.FINE))
308: log.fine("RCPT TO: " + rcpt.user + "@" + rcpt.host);
309:
310: if (readResponse(is, msg) / 100 != 2)
311: throw new IOException("Expected '221' from SMTP: "
312: + msg);
313: }
314: }
315:
316: /**
317: * Writes the message body to the server, escaping as necessary.
318: *
319: * @param os WriteStream to the server
320: */
321: private void writeMessageBody(WriteStream os) throws IOException {
322: int ch;
323:
324: ReadStream is = openReadAndSaveBuffer();
325:
326: ch = is.read();
327: if (ch < 0) {
328: os.print(".\r\n");
329: return;
330: }
331:
332: while (ch >= 0) {
333: if (ch == '\n') {
334: ch = is.read();
335: if (ch == '.') {
336: os.print("\r\n..");
337: ch = is.read();
338: } else if (ch <= 0) {
339: os.print("\r\n.\r\n");
340: return;
341: } else {
342: os.print("\r\n");
343: }
344: } else {
345: os.write(ch);
346: ch = is.read();
347: }
348: }
349:
350: os.print("\r\n.\r\n");
351: }
352:
353: /**
354: * Writes the message and the RFC822 headers to the SMTP server.
355: */
356: private void writeMessage(WriteStream os) throws IOException {
357: String from = getFrom();
358: os.print("From: ");
359: if (from != null)
360: os.print(from);
361: else {
362: os.print(CauchoSystem.getUserName());
363: os.print("@");
364: os.print(CauchoSystem.getLocalHost());
365: }
366: os.print("\r\n");
367:
368: String date = QDate.formatLocal(Alarm.getCurrentTime(),
369: "%a, %d %b %Y %H:%M:%S %z");
370:
371: os.print("Date: " + date + "\r\n");
372:
373: os.print("To: ");
374: writeMessageRecipients(os, _to);
375:
376: if (_cc != null && _cc.size() > 0) {
377: os.print("Cc: ");
378: writeMessageRecipients(os, _cc);
379: }
380:
381: Iterator<String> iter = getAttributeNames();
382: while (iter != null && iter.hasNext()) {
383: String key = iter.next();
384: Object value = getAttribute(key);
385:
386: if (value != null) {
387: os.print(key);
388: os.print(": ");
389: os.print(String.valueOf(value));
390: os.print("\r\n");
391: }
392: }
393:
394: String sender = getSender();
395: if (_from != sender && !_from.equals(sender)) {
396: os.print("Sender: ");
397:
398: if (sender != null)
399: os.print(sender);
400: else {
401: os.print(CauchoSystem.getUserName());
402: os.print("@");
403: os.print(CauchoSystem.getLocalHost());
404: }
405: os.print("\r\n");
406: }
407:
408: os.print("\r\n");
409:
410: writeMessageBody(os);
411: }
412:
413: /**
414: * Utility to write a list of mail addresses
415: */
416: private void writeMessageRecipients(WriteStream os,
417: ArrayList<MailtoPath.Recipient> list) throws IOException {
418: for (int i = 0; i < list.size(); i++) {
419: MailtoPath.Recipient rcpt = list.get(i);
420:
421: if (i != 0)
422: os.print(", ");
423:
424: os.print(rcpt.user);
425: if (rcpt.host != null) {
426: os.print("@");
427: os.print(rcpt.host);
428: }
429: }
430:
431: os.print("\r\n");
432: }
433:
434: /**
435: * On close, send the mail.
436: */
437: public void close() throws IOException {
438: if (_isClosed)
439: return;
440:
441: _isClosed = true;
442:
443: String host = System.getProperty("mail.smtp.host");
444: if (host == null)
445: host = "127.0.0.1";
446:
447: String portName = System.getProperty("mail.smtp.port");
448:
449: int port = 25;
450: if (portName != null)
451: port = Integer.parseInt(portName);
452:
453: Socket sock = new Socket(host, port);
454: CharBuffer msg = new CharBuffer();
455: ReadStream is = null;
456: WriteStream os = null;
457: try {
458: ReadWritePair s = VfsStream.openReadWrite(sock
459: .getInputStream(), sock.getOutputStream());
460: is = s.getReadStream();
461: os = s.getWriteStream();
462:
463: if (readResponse(is, msg) / 100 != 2)
464: throw new IOException("Expected '220' from SMTP");
465:
466: os.print("HELO ");
467: os.print(CauchoSystem.getLocalHost());
468: os.print("\r\n");
469: if (readResponse(is, msg) / 100 != 2)
470: throw new IOException("Expected '220' from SMTP");
471:
472: os.print("MAIL FROM: ");
473: String sender = getSender();
474: if (sender != null)
475: os.print(sender);
476: else {
477: os.print(CauchoSystem.getUserName());
478: os.print("@");
479: os.print(CauchoSystem.getLocalHost());
480: }
481: os.print("\r\n");
482: if (readResponse(is, msg) / 100 != 2)
483: throw new IOException("Expected '250' from SMTP: "
484: + msg);
485:
486: sendRecipients(is, os, _to, msg);
487: if (_cc != null)
488: sendRecipients(is, os, _cc, msg);
489: if (_bcc != null)
490: sendRecipients(is, os, _bcc, msg);
491:
492: os.print("DATA\r\n");
493: if (readResponse(is, msg) / 100 != 3)
494: throw new IOException("Expected '354' from SMTP: "
495: + msg);
496:
497: writeMessage(os);
498:
499: if (readResponse(is, msg) / 100 != 2)
500: throw new IOException("Expected '200' from SMTP: "
501: + msg);
502:
503: os.print("QUIT\r\n");
504: if (readResponse(is, msg) / 100 != 2)
505: throw new IOException("Expected '250' from SMTP: "
506: + msg);
507: } finally {
508: try {
509: if (is != null)
510: is.close();
511: if (os != null)
512: os.close();
513: } finally {
514: sock.close();
515: }
516: destroy();
517: }
518: }
519: }
|