001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: /*
020: * The original version of this class was donated by Jason Hunter,
021: * who wrote the class as part of the com.oreilly.servlet
022: * package for his book "Java Servlet Programming" (O'Reilly).
023: * See http://www.servlets.com.
024: *
025: */
026:
027: package org.apache.tools.mail;
028:
029: import java.io.IOException;
030: import java.io.PrintStream;
031: import java.io.BufferedOutputStream;
032: import java.io.OutputStream;
033: import java.net.Socket;
034: import java.net.InetAddress;
035: import java.util.Vector;
036: import java.util.Enumeration;
037:
038: /**
039: * A class to help send SMTP email.
040: * This class is an improvement on the sun.net.smtp.SmtpClient class
041: * found in the JDK. This version has extra functionality, and can be used
042: * with JVMs that did not extend from the JDK. It's not as robust as
043: * the JavaMail Standard Extension classes, but it's easier to use and
044: * easier to install, and has an Open Source license.
045: * <p>
046: * It can be used like this:
047: * <blockquote><pre>
048: * String mailhost = "localhost"; // or another mail host
049: * String from = "Mail Message Servlet <MailMessage@server.com>";
050: * String to = "to@you.com";
051: * String cc1 = "cc1@you.com";
052: * String cc2 = "cc2@you.com";
053: * String bcc = "bcc@you.com";
054: *
055: * MailMessage msg = new MailMessage(mailhost);
056: * msg.setPort(25);
057: * msg.from(from);
058: * msg.to(to);
059: * msg.cc(cc1);
060: * msg.cc(cc2);
061: * msg.bcc(bcc);
062: * msg.setSubject("Test subject");
063: * PrintStream out = msg.getPrintStream();
064: *
065: * Enumeration enum = req.getParameterNames();
066: * while (enum.hasMoreElements()) {
067: * String name = (String)enum.nextElement();
068: * String value = req.getParameter(name);
069: * out.println(name + " = " + value);
070: * }
071: *
072: * msg.sendAndClose();
073: * </pre></blockquote>
074: * <p>
075: * Be sure to set the from address, then set the recepient
076: * addresses, then set the subject and other headers, then get the
077: * PrintStream, then write the message, and finally send and close.
078: * The class does minimal error checking internally; it counts on the mail
079: * host to complain if there's any malformatted input or out of order
080: * execution.
081: * <p>
082: * An attachment mechanism based on RFC 1521 could be implemented on top of
083: * this class. In the meanwhile, JavaMail is the best solution for sending
084: * email with attachments.
085: * <p>
086: * Still to do:
087: * <ul>
088: * <li>Figure out how to close the connection in case of error
089: * </ul>
090: *
091: * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers
092: * version 1.0, 1999/12/29
093: */
094: public class MailMessage {
095:
096: /** default mailhost */
097: public static final String DEFAULT_HOST = "localhost";
098:
099: /** default port for SMTP: 25 */
100: public static final int DEFAULT_PORT = 25;
101:
102: /** host name for the mail server */
103: private String host;
104:
105: /** host port for the mail server */
106: private int port = DEFAULT_PORT;
107:
108: /** sender email address */
109: private String from;
110:
111: /** list of email addresses to reply to */
112: private Vector replyto;
113:
114: /** list of email addresses to send to */
115: private Vector to;
116:
117: /** list of email addresses to cc to */
118: private Vector cc;
119:
120: /** headers to send in the mail */
121: private Vector headersKeys;
122: private Vector headersValues;
123:
124: private MailPrintStream out;
125:
126: private SmtpResponseReader in;
127:
128: private Socket socket;
129: private static final int OK_READY = 220;
130: private static final int OK_HELO = 250;
131: private static final int OK_FROM = 250;
132: private static final int OK_RCPT_1 = 250;
133: private static final int OK_RCPT_2 = 251;
134: private static final int OK_DATA = 354;
135: private static final int OK_DOT = 250;
136: private static final int OK_QUIT = 221;
137:
138: /**
139: * Constructs a new MailMessage to send an email.
140: * Use localhost as the mail server with port 25.
141: *
142: * @exception IOException if there's any problem contacting the mail server
143: */
144: public MailMessage() throws IOException {
145: this (DEFAULT_HOST, DEFAULT_PORT);
146: }
147:
148: /**
149: * Constructs a new MailMessage to send an email.
150: * Use the given host as the mail server with port 25.
151: *
152: * @param host the mail server to use
153: * @exception IOException if there's any problem contacting the mail server
154: */
155: public MailMessage(String host) throws IOException {
156: this (host, DEFAULT_PORT);
157: }
158:
159: /**
160: * Constructs a new MailMessage to send an email.
161: * Use the given host and port as the mail server.
162: *
163: * @param host the mail server to use
164: * @param port the port to connect to
165: * @exception IOException if there's any problem contacting the mail server
166: */
167: public MailMessage(String host, int port) throws IOException {
168: this .port = port;
169: this .host = host;
170: replyto = new Vector();
171: to = new Vector();
172: cc = new Vector();
173: headersKeys = new Vector();
174: headersValues = new Vector();
175: connect();
176: sendHelo();
177: }
178:
179: /**
180: * Set the port to connect to the SMTP host.
181: * @param port the port to use for connection.
182: * @see #DEFAULT_PORT
183: */
184: public void setPort(int port) {
185: this .port = port;
186: }
187:
188: /**
189: * Sets the from address. Also sets the "From" header. This method should
190: * be called only once.
191: * @param from the from address
192: * @exception IOException if there's any problem reported by the mail server
193: */
194: public void from(String from) throws IOException {
195: sendFrom(from);
196: this .from = from;
197: }
198:
199: /**
200: * Sets the replyto address
201: * This method may be
202: * called multiple times.
203: * @param rto the replyto address
204: *
205: */
206: public void replyto(String rto) {
207: this .replyto.addElement(rto);
208: }
209:
210: /**
211: * Sets the to address. Also sets the "To" header. This method may be
212: * called multiple times.
213: *
214: * @param to the to address
215: * @exception IOException if there's any problem reported by the mail server
216: */
217: public void to(String to) throws IOException {
218: sendRcpt(to);
219: this .to.addElement(to);
220: }
221:
222: /**
223: * Sets the cc address. Also sets the "Cc" header. This method may be
224: * called multiple times.
225: *
226: * @param cc the cc address
227: * @exception IOException if there's any problem reported by the mail server
228: */
229: public void cc(String cc) throws IOException {
230: sendRcpt(cc);
231: this .cc.addElement(cc);
232: }
233:
234: /**
235: * Sets the bcc address. Does NOT set any header since it's a *blind* copy.
236: * This method may be called multiple times.
237: *
238: * @param bcc the bcc address
239: * @exception IOException if there's any problem reported by the mail server
240: */
241: public void bcc(String bcc) throws IOException {
242: sendRcpt(bcc);
243: // No need to keep track of Bcc'd addresses
244: }
245:
246: /**
247: * Sets the subject of the mail message. Actually sets the "Subject"
248: * header.
249: * @param subj the subject of the mail message
250: */
251: public void setSubject(String subj) {
252: setHeader("Subject", subj);
253: }
254:
255: /**
256: * Sets the named header to the given value. RFC 822 provides the rules for
257: * what text may constitute a header name and value.
258: * @param name name of the header
259: * @param value contents of the header
260: */
261: public void setHeader(String name, String value) {
262: // Blindly trust the user doesn't set any invalid headers
263: headersKeys.add(name);
264: headersValues.add(value);
265: }
266:
267: /**
268: * Returns a PrintStream that can be used to write the body of the message.
269: * A stream is used since email bodies are byte-oriented. A writer can
270: * be wrapped on top if necessary for internationalization.
271: * This is actually done in Message.java
272: *
273: * @return a printstream containing the data and the headers of the email
274: * @exception IOException if there's any problem reported by the mail server
275: * @see org.apache.tools.ant.taskdefs.email.Message
276: */
277: public PrintStream getPrintStream() throws IOException {
278: setFromHeader();
279: setReplyToHeader();
280: setToHeader();
281: setCcHeader();
282: setHeader("X-Mailer",
283: "org.apache.tools.mail.MailMessage (ant.apache.org)");
284: sendData();
285: flushHeaders();
286: return out;
287: }
288:
289: // RFC 822 s4.1: "From:" header must be sent
290: // We rely on error checking by the MTA
291: void setFromHeader() {
292: setHeader("From", from);
293: }
294:
295: // RFC 822 s4.1: "Reply-To:" header is optional
296: void setReplyToHeader() {
297: if (!replyto.isEmpty()) {
298: setHeader("Reply-To", vectorToList(replyto));
299: }
300: }
301:
302: void setToHeader() {
303: if (!to.isEmpty()) {
304: setHeader("To", vectorToList(to));
305: }
306: }
307:
308: void setCcHeader() {
309: if (!cc.isEmpty()) {
310: setHeader("Cc", vectorToList(cc));
311: }
312: }
313:
314: String vectorToList(Vector v) {
315: StringBuffer buf = new StringBuffer();
316: Enumeration e = v.elements();
317: while (e.hasMoreElements()) {
318: buf.append(e.nextElement());
319: if (e.hasMoreElements()) {
320: buf.append(", ");
321: }
322: }
323: return buf.toString();
324: }
325:
326: void flushHeaders() throws IOException {
327: // RFC 822 s4.1:
328: // "Header fields are NOT required to occur in any particular order,
329: // except that the message body MUST occur AFTER the headers"
330: // (the same section specifies a reccommended order, which we ignore)
331: for (int i = 0; i < headersKeys.size(); i++) {
332: String name = (String) headersKeys.elementAt(i);
333: String value = (String) headersValues.elementAt(i);
334: out.println(name + ": " + value);
335: }
336: out.println();
337: out.flush();
338: }
339:
340: /**
341: * Sends the message and closes the connection to the server.
342: * The MailMessage object cannot be reused.
343: *
344: * @exception IOException if there's any problem reported by the mail server
345: */
346: public void sendAndClose() throws IOException {
347: try {
348: sendDot();
349: sendQuit();
350: } finally {
351: disconnect();
352: }
353: }
354:
355: // Make a limited attempt to extract a sanitized email address
356: // Prefer text in <brackets>, ignore anything in (parentheses)
357: static String sanitizeAddress(String s) {
358: int paramDepth = 0;
359: int start = 0;
360: int end = 0;
361: int len = s.length();
362:
363: for (int i = 0; i < len; i++) {
364: char c = s.charAt(i);
365: if (c == '(') {
366: paramDepth++;
367: if (start == 0) {
368: end = i; // support "address (name)"
369: }
370: } else if (c == ')') {
371: paramDepth--;
372: if (end == 0) {
373: start = i + 1; // support "(name) address"
374: }
375: } else if (paramDepth == 0 && c == '<') {
376: start = i + 1;
377: } else if (paramDepth == 0 && c == '>') {
378: end = i;
379: }
380: }
381:
382: if (end == 0) {
383: end = len;
384: }
385:
386: return s.substring(start, end);
387: }
388:
389: // * * * * * Raw protocol methods below here * * * * *
390:
391: void connect() throws IOException {
392: socket = new Socket(host, port);
393: out = new MailPrintStream(new BufferedOutputStream(socket
394: .getOutputStream()));
395: in = new SmtpResponseReader(socket.getInputStream());
396: getReady();
397: }
398:
399: void getReady() throws IOException {
400: String response = in.getResponse();
401: int[] ok = { OK_READY };
402: if (!isResponseOK(response, ok)) {
403: throw new IOException(
404: "Didn't get introduction from server: " + response);
405: }
406: }
407:
408: void sendHelo() throws IOException {
409: String local = InetAddress.getLocalHost().getHostName();
410: int[] ok = { OK_HELO };
411: send("HELO " + local, ok);
412: }
413:
414: void sendFrom(String from) throws IOException {
415: int[] ok = { OK_FROM };
416: send("MAIL FROM: " + "<" + sanitizeAddress(from) + ">", ok);
417: }
418:
419: void sendRcpt(String rcpt) throws IOException {
420: int[] ok = { OK_RCPT_1, OK_RCPT_2 };
421: send("RCPT TO: " + "<" + sanitizeAddress(rcpt) + ">", ok);
422: }
423:
424: void sendData() throws IOException {
425: int[] ok = { OK_DATA };
426: send("DATA", ok);
427: }
428:
429: void sendDot() throws IOException {
430: int[] ok = { OK_DOT };
431: send("\r\n.", ok); // make sure dot is on new line
432: }
433:
434: void sendQuit() throws IOException {
435: int[] ok = { OK_QUIT };
436: try {
437: send("QUIT", ok);
438: } catch (IOException e) {
439: throw new ErrorInQuitException(e);
440: }
441: }
442:
443: void send(String msg, int[] ok) throws IOException {
444: out.rawPrint(msg + "\r\n"); // raw supports <CRLF>.<CRLF>
445: String response = in.getResponse();
446: if (!isResponseOK(response, ok)) {
447: throw new IOException("Unexpected reply to command: " + msg
448: + ": " + response);
449: }
450: }
451:
452: boolean isResponseOK(String response, int[] ok) {
453: // Check that the response is one of the valid codes
454: for (int i = 0; i < ok.length; i++) {
455: if (response.startsWith("" + ok[i])) {
456: return true;
457: }
458: }
459: return false;
460: }
461:
462: void disconnect() throws IOException {
463: if (out != null) {
464: out.close();
465: }
466: if (in != null) {
467: try {
468: in.close();
469: } catch (IOException e) {
470: // ignore
471: }
472: }
473: if (socket != null) {
474: try {
475: socket.close();
476: } catch (IOException e) {
477: // ignore
478: }
479: }
480: }
481: }
482:
483: /**
484: * This PrintStream subclass makes sure that <CRLF>. becomes <CRLF>..
485: * per RFC 821. It also ensures that new lines are always \r\n.
486: */
487: class MailPrintStream extends PrintStream {
488:
489: private int lastChar;
490:
491: public MailPrintStream(OutputStream out) {
492: super (out, true); // deprecated, but email is byte-oriented
493: }
494:
495: // Mac does \n\r, but that's tough to distinguish from Windows \r\n\r\n.
496: // Don't tackle that problem right now.
497: public void write(int b) {
498: if (b == '\n' && lastChar != '\r') {
499: rawWrite('\r'); // ensure always \r\n
500: rawWrite(b);
501: } else if (b == '.' && lastChar == '\n') {
502: rawWrite('.'); // add extra dot
503: rawWrite(b);
504: } else {
505: rawWrite(b);
506: }
507: lastChar = b;
508: }
509:
510: public void write(byte[] buf, int off, int len) {
511: for (int i = 0; i < len; i++) {
512: write(buf[off + i]);
513: }
514: }
515:
516: void rawWrite(int b) {
517: super .write(b);
518: }
519:
520: void rawPrint(String s) {
521: int len = s.length();
522: for (int i = 0; i < len; i++) {
523: rawWrite(s.charAt(i));
524: }
525: }
526: }
|