KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > oreilly > servlet > MailMessage


1 // Copyright (C) 1999-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
2
// All rights reserved. Use of this class is limited.
3
// Please see the LICENSE for more information.
4

5 package com.oreilly.servlet;
6
7 import java.io.*;
8 import java.net.*;
9 import java.util.*;
10
11 /**
12  * A class to help send SMTP email. It can be used by any Java program, not
13  * just servlets. Servlets are likely to use this class to:
14  * <ul>
15  * <li>Send submitted form data to interested parties
16  * <li>Send an email page to an administrator in case of error
17  * <li>Send the client an order confirmation
18  * </ul>
19  * <p>
20  * This class is an improvement on the sun.net.smtp.SmtpClient class
21  * found in the JDK. This version has extra functionality, and can be used
22  * with JVMs that did not extend from the JDK. It's not as robust as
23  * the JavaMail Standard Extension classes, but it's easier to use and
24  * easier to install.
25  * <p>
26  * It can be used like this:
27  * <blockquote><pre>
28  * String mailhost = "localhost"; // or another mail host
29  * String from = "Mail Message Servlet <MailMessage@somedomain.com>";
30  * String to = "to@somedomain.com";
31  * String cc1 = "cc1@somedomain.com";
32  * String cc2 = "cc2@somedomain.com";
33  * String bcc = "bcc@somedomain.com";
34  * &nbsp;
35  * MailMessage msg = new MailMessage(mailhost);
36  * msg.from(from);
37  * msg.to(to);
38  * msg.cc(cc1);
39  * msg.cc(cc2);
40  * msg.bcc(bcc);
41  * msg.setSubject("Test subject");
42  * PrintStream out = msg.getPrintStream();
43  * &nbsp;
44  * Enumeration paramenum = req.getParameterNames();
45  * while (paramenum.hasMoreElements()) {
46  * String name = (String)paramenum.nextElement();
47  * String value = req.getParameter(name);
48  * out.println(name + " = " + value);
49  * }
50  * &nbsp;
51  * msg.sendAndClose();
52  * </pre></blockquote>
53  * <p>
54  * Be sure to set the from address, then set the recepient
55  * addresses, then set the subject and other headers, then get the
56  * PrintStream, then write the message, and finally send and close.
57  * The class does minimal error checking internally; it counts on the mail
58  * host to complain if there's any malformatted input or out of order
59  * execution.
60  * <p>
61  * An attachment mechanism based on RFC 1521 could be implemented on top of
62  * this class. In the meanwhile, JavaMail is the best solution for sending
63  * email with attachments.
64  * <p>
65  * Still to do:
66  * <ul>
67  * <li>Figure out how to close the connection in case of error
68  * </ul>
69  *
70  * @author <b>Jason Hunter</b>, Copyright &#169; 1999
71  * @version 1.2, 2002/11/01, added logic to suppress CC: header if no CC addrs
72  * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers
73  * @version 1.0, 1999/12/29
74  */

75 public class MailMessage {
76
77   String JavaDoc host;
78   String JavaDoc from;
79   Vector to, cc;
80   Hashtable headers;
81   MailPrintStream out;
82   BufferedReader in;
83   Socket socket;
84
85   /**
86    * Constructs a new MailMessage to send an email.
87    * Use localhost as the mail server.
88    *
89    * @exception IOException if there's any problem contacting the mail server
90    */

91   public MailMessage() throws IOException {
92     this("localhost");
93   }
94
95   /**
96    * Constructs a new MailMessage to send an email.
97    * Use the given host as the mail server.
98    *
99    * @param host the mail server to use
100    * @exception IOException if there's any problem contacting the mail server
101    */

102   public MailMessage(String JavaDoc host) throws IOException {
103     this.host = host;
104     to = new Vector();
105     cc = new Vector();
106     headers = new Hashtable();
107     setHeader("X-Mailer", "com.oreilly.servlet.MailMessage (www.servlets.com)");
108     connect();
109     sendHelo();
110   }
111
112   /**
113    * Sets the from address. Also sets the "From" header. This method should
114    * be called only once.
115    *
116    * @exception IOException if there's any problem reported by the mail server
117    */

118   public void from(String JavaDoc from) throws IOException {
119     sendFrom(from);
120     this.from = from;
121   }
122
123   /**
124    * Sets the to address. Also sets the "To" header. This method may be
125    * called multiple times.
126    *
127    * @exception IOException if there's any problem reported by the mail server
128    */

129   public void to(String JavaDoc to) throws IOException {
130     sendRcpt(to);
131     this.to.addElement(to);
132   }
133
134   /**
135    * Sets the cc address. Also sets the "Cc" header. This method may be
136    * called multiple times.
137    *
138    * @exception IOException if there's any problem reported by the mail server
139    */

140   public void cc(String JavaDoc cc) throws IOException {
141     sendRcpt(cc);
142     this.cc.addElement(cc);
143   }
144
145   /**
146    * Sets the bcc address. Does NOT set any header since it's a *blind* copy.
147    * This method may be called multiple times.
148    *
149    * @exception IOException if there's any problem reported by the mail server
150    */

151   public void bcc(String JavaDoc bcc) throws IOException {
152     sendRcpt(bcc);
153     // No need to keep track of Bcc'd addresses
154
}
155
156   /**
157    * Sets the subject of the mail message. Actually sets the "Subject"
158    * header.
159    */

160   public void setSubject(String JavaDoc subj) {
161     headers.put("Subject", subj);
162   }
163
164   /**
165    * Sets the named header to the given value. RFC 822 provides the rules for
166    * what text may constitute a header name and value.
167    */

168   public void setHeader(String JavaDoc name, String JavaDoc value) {
169     // Blindly trust the user doesn't set any invalid headers
170
headers.put(name, value);
171   }
172
173   /**
174    * Returns a PrintStream that can be used to write the body of the message.
175    * A stream is used since email bodies are byte-oriented. A writer could
176    * be wrapped on top if necessary for internationalization.
177    *
178    * @exception IOException if there's any problem reported by the mail server
179    */

180   public PrintStream getPrintStream() throws IOException {
181     setFromHeader();
182     setToHeader();
183     setCcHeader();
184     sendData();
185     flushHeaders();
186     return out;
187   }
188
189   void setFromHeader() {
190     setHeader("From", from);
191   }
192
193   void setToHeader() {
194     setHeader("To", vectorToList(to));
195   }
196
197   void setCcHeader() {
198     if (!cc.isEmpty()) { // thanks to Patrice, patricek_97@yahoo.com
199
setHeader("Cc", vectorToList(cc));
200     }
201   }
202
203   String JavaDoc vectorToList(Vector v) {
204     StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
205     Enumeration e = v.elements();
206     while (e.hasMoreElements()) {
207       buf.append(e.nextElement());
208       if (e.hasMoreElements()) {
209         buf.append(", ");
210       }
211     }
212     return buf.toString();
213   }
214
215   void flushHeaders() throws IOException {
216     // XXX Should I care about order here?
217
Enumeration e = headers.keys();
218     while (e.hasMoreElements()) {
219       String JavaDoc name = (String JavaDoc) e.nextElement();
220       String JavaDoc value = (String JavaDoc) headers.get(name);
221       out.println(name + ": " + value);
222     }
223     out.println();
224     out.flush();
225   }
226
227   /**
228    * Sends the message and closes the connection to the server.
229    * The MailMessage object cannot be reused.
230    *
231    * @exception IOException if there's any problem reported by the mail server
232    */

233   public void sendAndClose() throws IOException {
234     sendDot();
235     disconnect();
236   }
237
238   // Make a limited attempt to extract a sanitized email address
239
// Prefer text in <brackets>, ignore anything in (parentheses)
240
static String JavaDoc sanitizeAddress(String JavaDoc s) {
241     int paramDepth = 0;
242     int start = 0;
243     int end = 0;
244     int len = s.length();
245
246     for (int i = 0; i < len; i++) {
247       char c = s.charAt(i);
248       if (c == '(') {
249         paramDepth++;
250         if (start == 0) {
251           end = i; // support "address (name)"
252
}
253       }
254       else if (c == ')') {
255         paramDepth--;
256         if (end == 0) {
257           start = i + 1; // support "(name) address"
258
}
259       }
260       else if (paramDepth == 0 && c == '<') {
261         start = i + 1;
262       }
263       else if (paramDepth == 0 && c == '>') {
264         end = i;
265       }
266     }
267
268     if (end == 0) {
269       end = len;
270     }
271
272     return s.substring(start, end);
273   }
274
275   // * * * * * Raw protocol methods below here * * * * *
276

277   void connect() throws IOException {
278     socket = new Socket(host, 25);
279     out = new MailPrintStream(
280           new BufferedOutputStream(
281           socket.getOutputStream()));
282     in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
283     getReady();
284   }
285
286   void getReady() throws IOException {
287     String JavaDoc response = in.readLine();
288     int[] ok = { 220 };
289     if (!isResponseOK(response, ok)) {
290       throw new IOException(
291         "Didn't get introduction from server: " + response);
292     }
293   }
294
295   void sendHelo() throws IOException {
296     String JavaDoc local = InetAddress.getLocalHost().getHostName();
297     int[] ok = { 250 };
298     send("HELO " + local, ok);
299   }
300
301   void sendFrom(String JavaDoc from) throws IOException {
302     int[] ok = { 250 };
303     send("MAIL FROM: " + "<" + sanitizeAddress(from) + ">", ok);
304   }
305
306   void sendRcpt(String JavaDoc rcpt) throws IOException {
307     int[] ok = { 250, 251 };
308     send("RCPT TO: " + "<" + sanitizeAddress(rcpt) + ">", ok);
309   }
310
311   void sendData() throws IOException {
312     int[] ok = { 354 };
313     send("DATA", ok);
314   }
315
316   void sendDot() throws IOException {
317     int[] ok = { 250 };
318     send("\r\n.", ok); // make sure dot is on new line
319
}
320
321   void sendQuit() throws IOException {
322     int[] ok = { 221 };
323     send("QUIT", ok);
324   }
325
326   void send(String JavaDoc msg, int[] ok) throws IOException {
327     out.rawPrint(msg + "\r\n"); // raw supports <CRLF>.<CRLF>
328
//System.out.println("S: " + msg);
329
String JavaDoc response = in.readLine();
330     //System.out.println("R: " + response);
331
if (!isResponseOK(response, ok)) {
332       throw new IOException(
333         "Unexpected reply to command: " + msg + ": " + response);
334     }
335   }
336
337   boolean isResponseOK(String JavaDoc response, int[] ok) {
338     // Check that the response is one of the valid codes
339
for (int i = 0; i < ok.length; i++) {
340       if (response.startsWith("" + ok[i])) {
341         return true;
342       }
343     }
344     return false;
345   }
346
347   void disconnect() throws IOException {
348     if (out != null) out.close();
349     if (in != null) in.close();
350     if (socket != null) socket.close();
351   }
352 }
353
354 // This PrintStream subclass makes sure that <CRLF>. becomes <CRLF>..
355
// per RFC 821. It also ensures that new lines are always \r\n.
356
//
357
class MailPrintStream extends PrintStream {
358
359   int lastChar;
360
361   public MailPrintStream(OutputStream out) {
362     super(out, true); // deprecated, but email is byte-oriented
363
}
364
365   // Mac OS 9 does \r, but that's tough to distinguish from Windows \r\n.
366
// Don't tackle that problem right now.
367
public void write(int b) {
368     if (b == '\n' && lastChar != '\r') {
369       rawWrite('\r'); // ensure always \r\n
370
rawWrite(b);
371     }
372     else if (b == '.' && lastChar == '\n') {
373       rawWrite('.'); // add extra dot
374
rawWrite(b);
375     }
376     else if (b != '\n' && lastChar == '\r') { // Special Mac OS 9 handling
377
rawWrite('\n');
378       rawWrite(b);
379       if (b == '.') {
380         rawWrite('.'); // add extra dot
381
}
382     }
383     else {
384       rawWrite(b);
385     }
386     lastChar = b;
387   }
388
389   public void write(byte buf[], int off, int len) {
390     for (int i = 0; i < len; i++) {
391       write(buf[off + i]);
392     }
393   }
394
395   void rawWrite(int b) {
396     super.write(b);
397   }
398
399   void rawPrint(String JavaDoc s) {
400     int len = s.length();
401     for (int i = 0; i < len; i++) {
402       rawWrite(s.charAt(i));
403     }
404   }
405 }
406
Popular Tags