KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > mail > MailMessage


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18
19 /*
20  * The original version of this class was donated by Jason Hunter,
21  * who wrote the class as part of the com.oreilly.servlet
22  * package for his book "Java Servlet Programming" (O'Reilly).
23  * See http://www.servlets.com.
24  *
25  */

26
27 package org.apache.tools.mail;
28
29 import java.io.IOException JavaDoc;
30 import java.io.PrintStream JavaDoc;
31 import java.io.BufferedOutputStream JavaDoc;
32 import java.io.OutputStream JavaDoc;
33 import java.net.Socket JavaDoc;
34 import java.net.InetAddress JavaDoc;
35 import java.util.Vector JavaDoc;
36 import java.util.Enumeration JavaDoc;
37
38 /**
39  * A class to help send SMTP email.
40  * This class is an improvement on the sun.net.smtp.SmtpClient class
41  * found in the JDK. This version has extra functionality, and can be used
42  * with JVMs that did not extend from the JDK. It's not as robust as
43  * the JavaMail Standard Extension classes, but it's easier to use and
44  * easier to install, and has an Open Source license.
45  * <p>
46  * It can be used like this:
47  * <blockquote><pre>
48  * String mailhost = "localhost"; // or another mail host
49  * String from = "Mail Message Servlet &lt;MailMessage@server.com&gt;";
50  * String to = "to@you.com";
51  * String cc1 = "cc1@you.com";
52  * String cc2 = "cc2@you.com";
53  * String bcc = "bcc@you.com";
54  * &nbsp;
55  * MailMessage msg = new MailMessage(mailhost);
56  * msg.setPort(25);
57  * msg.from(from);
58  * msg.to(to);
59  * msg.cc(cc1);
60  * msg.cc(cc2);
61  * msg.bcc(bcc);
62  * msg.setSubject("Test subject");
63  * PrintStream out = msg.getPrintStream();
64  * &nbsp;
65  * Enumeration enum = req.getParameterNames();
66  * while (enum.hasMoreElements()) {
67  * String name = (String)enum.nextElement();
68  * String value = req.getParameter(name);
69  * out.println(name + " = " + value);
70  * }
71  * &nbsp;
72  * msg.sendAndClose();
73  * </pre></blockquote>
74  * <p>
75  * Be sure to set the from address, then set the recepient
76  * addresses, then set the subject and other headers, then get the
77  * PrintStream, then write the message, and finally send and close.
78  * The class does minimal error checking internally; it counts on the mail
79  * host to complain if there's any malformatted input or out of order
80  * execution.
81  * <p>
82  * An attachment mechanism based on RFC 1521 could be implemented on top of
83  * this class. In the meanwhile, JavaMail is the best solution for sending
84  * email with attachments.
85  * <p>
86  * Still to do:
87  * <ul>
88  * <li>Figure out how to close the connection in case of error
89  * </ul>
90  *
91  * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers
92  * version 1.0, 1999/12/29
93  */

94 public class MailMessage {
95
96     /** default mailhost */
97     public static final String JavaDoc DEFAULT_HOST = "localhost";
98
99     /** default port for SMTP: 25 */
100     public static final int DEFAULT_PORT = 25;
101
102     /** host name for the mail server */
103     private String JavaDoc host;
104
105     /** host port for the mail server */
106     private int port = DEFAULT_PORT;
107
108     /** sender email address */
109     private String JavaDoc from;
110
111     /** list of email addresses to reply to */
112     private Vector JavaDoc replyto;
113
114     /** list of email addresses to send to */
115     private Vector JavaDoc to;
116
117     /** list of email addresses to cc to */
118     private Vector JavaDoc cc;
119
120     /** headers to send in the mail */
121     private Vector JavaDoc headersKeys;
122     private Vector JavaDoc headersValues;
123
124     private MailPrintStream out;
125
126     private SmtpResponseReader in;
127
128     private Socket JavaDoc 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 JavaDoc {
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 JavaDoc host) throws IOException JavaDoc {
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 JavaDoc host, int port) throws IOException JavaDoc {
168     this.port = port;
169     this.host = host;
170     replyto = new Vector JavaDoc();
171     to = new Vector JavaDoc();
172     cc = new Vector JavaDoc();
173     headersKeys = new Vector JavaDoc();
174     headersValues = new Vector JavaDoc();
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 JavaDoc from) throws IOException JavaDoc {
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 JavaDoc 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 JavaDoc to) throws IOException JavaDoc {
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 JavaDoc cc) throws IOException JavaDoc {
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 JavaDoc bcc) throws IOException JavaDoc {
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 JavaDoc 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 JavaDoc name, String JavaDoc 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 JavaDoc getPrintStream() throws IOException JavaDoc {
278     setFromHeader();
279     setReplyToHeader();
280     setToHeader();
281     setCcHeader();
282     setHeader("X-Mailer", "org.apache.tools.mail.MailMessage (ant.apache.org)");
283     sendData();
284     flushHeaders();
285     return out;
286   }
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 JavaDoc vectorToList(Vector JavaDoc v) {
315     StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
316     Enumeration JavaDoc 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 JavaDoc {
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 JavaDoc name = (String JavaDoc) headersKeys.elementAt(i);
333       String JavaDoc value = (String JavaDoc) 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 JavaDoc {
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 JavaDoc sanitizeAddress(String JavaDoc 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 JavaDoc {
392     socket = new Socket JavaDoc(host, port);
393     out = new MailPrintStream(
394           new BufferedOutputStream JavaDoc(
395           socket.getOutputStream()));
396     in = new SmtpResponseReader(socket.getInputStream());
397     getReady();
398   }
399
400   void getReady() throws IOException JavaDoc {
401     String JavaDoc response = in.getResponse();
402     int[] ok = {OK_READY};
403     if (!isResponseOK(response, ok)) {
404       throw new IOException JavaDoc(
405         "Didn't get introduction from server: " + response);
406     }
407   }
408   void sendHelo() throws IOException JavaDoc {
409     String JavaDoc local = InetAddress.getLocalHost().getHostName();
410     int[] ok = {OK_HELO};
411     send("HELO " + local, ok);
412   }
413   void sendFrom(String JavaDoc from) throws IOException JavaDoc {
414     int[] ok = {OK_FROM};
415     send("MAIL FROM: " + "<" + sanitizeAddress(from) + ">", ok);
416   }
417   void sendRcpt(String JavaDoc rcpt) throws IOException JavaDoc {
418     int[] ok = {OK_RCPT_1, OK_RCPT_2};
419     send("RCPT TO: " + "<" + sanitizeAddress(rcpt) + ">", ok);
420   }
421
422   void sendData() throws IOException JavaDoc {
423     int[] ok = {OK_DATA};
424     send("DATA", ok);
425   }
426
427   void sendDot() throws IOException JavaDoc {
428     int[] ok = {OK_DOT};
429     send("\r\n.", ok); // make sure dot is on new line
430
}
431
432     void sendQuit() throws IOException JavaDoc {
433         int[] ok = {OK_QUIT};
434         try {
435             send("QUIT", ok);
436         } catch (IOException JavaDoc e) {
437             throw new ErrorInQuitException(e);
438         }
439     }
440
441     void send(String JavaDoc msg, int[] ok) throws IOException JavaDoc {
442         out.rawPrint(msg + "\r\n"); // raw supports <CRLF>.<CRLF>
443
String JavaDoc response = in.getResponse();
444         if (!isResponseOK(response, ok)) {
445             throw new IOException JavaDoc("Unexpected reply to command: "
446                                   + msg + ": " + response);
447         }
448     }
449
450   boolean isResponseOK(String JavaDoc response, int[] ok) {
451     // Check that the response is one of the valid codes
452
for (int i = 0; i < ok.length; i++) {
453       if (response.startsWith("" + ok[i])) {
454         return true;
455       }
456     }
457     return false;
458   }
459
460     void disconnect() throws IOException JavaDoc {
461         if (out != null) {
462             out.close();
463         }
464         if (in != null) {
465             try {
466                 in.close();
467             } catch (IOException JavaDoc e) {
468                 // ignore
469
}
470         }
471         if (socket != null) {
472             try {
473                 socket.close();
474             } catch (IOException JavaDoc e) {
475                 // ignore
476
}
477         }
478     }
479 }
480
481 /**
482  * This PrintStream subclass makes sure that <CRLF>. becomes <CRLF>..
483  * per RFC 821. It also ensures that new lines are always \r\n.
484 */

485 class MailPrintStream extends PrintStream JavaDoc {
486
487   private int lastChar;
488
489   public MailPrintStream(OutputStream JavaDoc out) {
490     super(out, true); // deprecated, but email is byte-oriented
491
}
492
493   // Mac does \n\r, but that's tough to distinguish from Windows \r\n\r\n.
494
// Don't tackle that problem right now.
495
public void write(int b) {
496     if (b == '\n' && lastChar != '\r') {
497       rawWrite('\r'); // ensure always \r\n
498
rawWrite(b);
499     } else if (b == '.' && lastChar == '\n') {
500       rawWrite('.'); // add extra dot
501
rawWrite(b);
502     } else {
503       rawWrite(b);
504     }
505     lastChar = b;
506   }
507
508   public void write(byte[] buf, int off, int len) {
509     for (int i = 0; i < len; i++) {
510       write(buf[off + i]);
511     }
512   }
513
514   void rawWrite(int b) {
515     super.write(b);
516   }
517
518   void rawPrint(String JavaDoc s) {
519     int len = s.length();
520     for (int i = 0; i < len; i++) {
521       rawWrite(s.charAt(i));
522     }
523   }
524 }
525
526
Popular Tags