source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/mail/MailMessage.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 14.9 KB
Line 
1/*
2 * Copyright 2000-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18/*
19 * The original version of this class was donated by Jason Hunter,
20 * who wrote the class as part of the com.oreilly.servlet
21 * package for his book "Java Servlet Programming" (O'Reilly).
22 * See http://www.servlets.com.
23 *
24 */
25
26package org.apache.tools.mail;
27
28import java.io.IOException;
29import java.io.PrintStream;
30import java.io.BufferedOutputStream;
31import java.io.OutputStream;
32import java.net.Socket;
33import java.net.InetAddress;
34import java.util.Vector;
35import java.util.Enumeration;
36
37/**
38 * A class to help send SMTP email.
39 * This class is an improvement on the sun.net.smtp.SmtpClient class
40 * found in the JDK. This version has extra functionality, and can be used
41 * with JVMs that did not extend from the JDK. It's not as robust as
42 * the JavaMail Standard Extension classes, but it's easier to use and
43 * easier to install, and has an Open Source license.
44 * <p>
45 * It can be used like this:
46 * <blockquote><pre>
47 * String mailhost = "localhost"; // or another mail host
48 * String from = "Mail Message Servlet &lt;[email protected]&gt;";
49 * String to = "[email protected]";
50 * String cc1 = "[email protected]";
51 * String cc2 = "[email protected]";
52 * String bcc = "[email protected]";
53 * &nbsp;
54 * MailMessage msg = new MailMessage(mailhost);
55 * msg.setPort(25);
56 * msg.from(from);
57 * msg.to(to);
58 * msg.cc(cc1);
59 * msg.cc(cc2);
60 * msg.bcc(bcc);
61 * msg.setSubject("Test subject");
62 * PrintStream out = msg.getPrintStream();
63 * &nbsp;
64 * Enumeration enum = req.getParameterNames();
65 * while (enum.hasMoreElements()) {
66 * String name = (String)enum.nextElement();
67 * String value = req.getParameter(name);
68 * out.println(name + " = " + value);
69 * }
70 * &nbsp;
71 * msg.sendAndClose();
72 * </pre></blockquote>
73 * <p>
74 * Be sure to set the from address, then set the recepient
75 * addresses, then set the subject and other headers, then get the
76 * PrintStream, then write the message, and finally send and close.
77 * The class does minimal error checking internally; it counts on the mail
78 * host to complain if there's any malformatted input or out of order
79 * execution.
80 * <p>
81 * An attachment mechanism based on RFC 1521 could be implemented on top of
82 * this class. In the meanwhile, JavaMail is the best solution for sending
83 * email with attachments.
84 * <p>
85 * Still to do:
86 * <ul>
87 * <li>Figure out how to close the connection in case of error
88 * </ul>
89 *
90 * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers
91 * version 1.0, 1999/12/29
92 */
93public class MailMessage {
94
95 /** default mailhost */
96 public static final String DEFAULT_HOST = "localhost";
97
98 /** default port for SMTP: 25 */
99 public static final int DEFAULT_PORT = 25;
100
101 /** host name for the mail server */
102 private String host;
103
104 /** host port for the mail server */
105 private int port = DEFAULT_PORT;
106
107 /** sender email address */
108 private String from;
109
110 /** list of email addresses to reply to */
111 private Vector replyto;
112
113 /** list of email addresses to send to */
114 private Vector to;
115
116 /** list of email addresses to cc to */
117 private Vector cc;
118
119 /** headers to send in the mail */
120 private Vector headersKeys;
121 private Vector headersValues;
122
123 private MailPrintStream out;
124
125 private SmtpResponseReader in;
126
127 private Socket socket;
128 private static final int OK_READY = 220;
129 private static final int OK_HELO = 250;
130 private static final int OK_FROM = 250;
131 private static final int OK_RCPT_1 = 250;
132 private static final int OK_RCPT_2 = 251;
133 private static final int OK_DATA = 354;
134 private static final int OK_DOT = 250;
135 private static final int OK_QUIT = 221;
136
137 /**
138 * Constructs a new MailMessage to send an email.
139 * Use localhost as the mail server with port 25.
140 *
141 * @exception IOException if there's any problem contacting the mail server
142 */
143 public MailMessage() throws IOException {
144 this(DEFAULT_HOST, DEFAULT_PORT);
145 }
146
147 /**
148 * Constructs a new MailMessage to send an email.
149 * Use the given host as the mail server with port 25.
150 *
151 * @param host the mail server to use
152 * @exception IOException if there's any problem contacting the mail server
153 */
154 public MailMessage(String host) throws IOException {
155 this(host, DEFAULT_PORT);
156 }
157
158 /**
159 * Constructs a new MailMessage to send an email.
160 * Use the given host and port as the mail server.
161 *
162 * @param host the mail server to use
163 * @param port the port to connect to
164 * @exception IOException if there's any problem contacting the mail server
165 */
166 public MailMessage(String host, int port) throws IOException {
167 this.port = port;
168 this.host = host;
169 replyto = new Vector();
170 to = new Vector();
171 cc = new Vector();
172 headersKeys = new Vector();
173 headersValues = new Vector();
174 connect();
175 sendHelo();
176 }
177
178 /**
179 * Set the port to connect to the SMTP host.
180 * @param port the port to use for connection.
181 * @see #DEFAULT_PORT
182 */
183 public void setPort(int port) {
184 this.port = port;
185 }
186
187 /**
188 * Sets the from address. Also sets the "From" header. This method should
189 * be called only once.
190 * @param from the from address
191 * @exception IOException if there's any problem reported by the mail server
192 */
193 public void from(String from) throws IOException {
194 sendFrom(from);
195 this.from = from;
196 }
197
198 /**
199 * Sets the replyto address
200 * This method may be
201 * called multiple times.
202 * @param rto the replyto address
203 *
204 */
205 public void replyto(String rto) {
206 this.replyto.addElement(rto);
207 }
208
209 /**
210 * Sets the to address. Also sets the "To" header. This method may be
211 * called multiple times.
212 *
213 * @param to the to address
214 * @exception IOException if there's any problem reported by the mail server
215 */
216 public void to(String to) throws IOException {
217 sendRcpt(to);
218 this.to.addElement(to);
219 }
220
221 /**
222 * Sets the cc address. Also sets the "Cc" header. This method may be
223 * called multiple times.
224 *
225 * @param cc the cc address
226 * @exception IOException if there's any problem reported by the mail server
227 */
228 public void cc(String cc) throws IOException {
229 sendRcpt(cc);
230 this.cc.addElement(cc);
231 }
232
233 /**
234 * Sets the bcc address. Does NOT set any header since it's a *blind* copy.
235 * This method may be called multiple times.
236 *
237 * @param bcc the bcc address
238 * @exception IOException if there's any problem reported by the mail server
239 */
240 public void bcc(String bcc) throws IOException {
241 sendRcpt(bcc);
242 // No need to keep track of Bcc'd addresses
243 }
244
245 /**
246 * Sets the subject of the mail message. Actually sets the "Subject"
247 * header.
248 * @param subj the subject of the mail message
249 */
250 public void setSubject(String subj) {
251 setHeader("Subject", subj);
252 }
253
254 /**
255 * Sets the named header to the given value. RFC 822 provides the rules for
256 * what text may constitute a header name and value.
257 * @param name name of the header
258 * @param value contents of the header
259 */
260 public void setHeader(String name, String value) {
261 // Blindly trust the user doesn't set any invalid headers
262 headersKeys.add(name);
263 headersValues.add(value);
264 }
265
266 /**
267 * Returns a PrintStream that can be used to write the body of the message.
268 * A stream is used since email bodies are byte-oriented. A writer can
269 * be wrapped on top if necessary for internationalization.
270 * This is actually done in Message.java
271 *
272 * @return a printstream containing the data and the headers of the email
273 * @exception IOException if there's any problem reported by the mail server
274 * @see org.apache.tools.ant.taskdefs.email.Message
275 */
276 public PrintStream getPrintStream() throws IOException {
277 setFromHeader();
278 setReplyToHeader();
279 setToHeader();
280 setCcHeader();
281 setHeader("X-Mailer", "org.apache.tools.mail.MailMessage (ant.apache.org)");
282 sendData();
283 flushHeaders();
284 return out;
285 }
286
287
288 // RFC 822 s4.1: "From:" header must be sent
289 // We rely on error checking by the MTA
290 void setFromHeader() {
291 setHeader("From", from);
292 }
293
294 // RFC 822 s4.1: "Reply-To:" header is optional
295 void setReplyToHeader() {
296 if (!replyto.isEmpty()) {
297 setHeader("Reply-To", vectorToList(replyto));
298 }
299 }
300
301 void setToHeader() {
302 if (!to.isEmpty()) {
303 setHeader("To", vectorToList(to));
304 }
305 }
306
307 void setCcHeader() {
308 if (!cc.isEmpty()) {
309 setHeader("Cc", vectorToList(cc));
310 }
311 }
312
313 String vectorToList(Vector v) {
314 StringBuffer buf = new StringBuffer();
315 Enumeration e = v.elements();
316 while (e.hasMoreElements()) {
317 buf.append(e.nextElement());
318 if (e.hasMoreElements()) {
319 buf.append(", ");
320 }
321 }
322 return buf.toString();
323 }
324
325 void flushHeaders() throws IOException {
326 // RFC 822 s4.1:
327 // "Header fields are NOT required to occur in any particular order,
328 // except that the message body MUST occur AFTER the headers"
329 // (the same section specifies a reccommended order, which we ignore)
330 for (int i = 0; i < headersKeys.size(); i++) {
331 String name = (String) headersKeys.elementAt(i);
332 String value = (String) headersValues.elementAt(i);
333 out.println(name + ": " + value);
334 }
335 out.println();
336 out.flush();
337 }
338
339 /**
340 * Sends the message and closes the connection to the server.
341 * The MailMessage object cannot be reused.
342 *
343 * @exception IOException if there's any problem reported by the mail server
344 */
345 public void sendAndClose() throws IOException {
346 try {
347 sendDot();
348 sendQuit();
349 } finally {
350 disconnect();
351 }
352 }
353
354 // Make a limited attempt to extract a sanitized email address
355 // Prefer text in <brackets>, ignore anything in (parentheses)
356 static String sanitizeAddress(String s) {
357 int paramDepth = 0;
358 int start = 0;
359 int end = 0;
360 int len = s.length();
361
362 for (int i = 0; i < len; i++) {
363 char c = s.charAt(i);
364 if (c == '(') {
365 paramDepth++;
366 if (start == 0) {
367 end = i; // support "address (name)"
368 }
369 } else if (c == ')') {
370 paramDepth--;
371 if (end == 0) {
372 start = i + 1; // support "(name) address"
373 }
374 } else if (paramDepth == 0 && c == '<') {
375 start = i + 1;
376 } else if (paramDepth == 0 && c == '>') {
377 end = i;
378 }
379 }
380
381 if (end == 0) {
382 end = len;
383 }
384
385 return s.substring(start, end);
386 }
387
388 // * * * * * Raw protocol methods below here * * * * *
389
390 void connect() throws IOException {
391 socket = new Socket(host, port);
392 out = new MailPrintStream(
393 new BufferedOutputStream(
394 socket.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 void sendHelo() throws IOException {
408 String local = InetAddress.getLocalHost().getHostName();
409 int[] ok = {OK_HELO};
410 send("HELO " + local, ok);
411 }
412 void sendFrom(String from) throws IOException {
413 int[] ok = {OK_FROM};
414 send("MAIL FROM: " + "<" + sanitizeAddress(from) + ">", ok);
415 }
416 void sendRcpt(String rcpt) throws IOException {
417 int[] ok = {OK_RCPT_1, OK_RCPT_2};
418 send("RCPT TO: " + "<" + sanitizeAddress(rcpt) + ">", ok);
419 }
420
421 void sendData() throws IOException {
422 int[] ok = {OK_DATA};
423 send("DATA", ok);
424 }
425
426 void sendDot() throws IOException {
427 int[] ok = {OK_DOT};
428 send("\r\n.", ok); // make sure dot is on new line
429 }
430
431 void sendQuit() throws IOException {
432 int[] ok = {OK_QUIT};
433 try {
434 send("QUIT", ok);
435 } catch (IOException e) {
436 throw new ErrorInQuitException(e);
437 }
438 }
439
440 void send(String msg, int[] ok) throws IOException {
441 out.rawPrint(msg + "\r\n"); // raw supports <CRLF>.<CRLF>
442 String response = in.getResponse();
443 if (!isResponseOK(response, ok)) {
444 throw new IOException("Unexpected reply to command: "
445 + msg + ": " + response);
446 }
447 }
448
449 boolean isResponseOK(String response, int[] ok) {
450 // Check that the response is one of the valid codes
451 for (int i = 0; i < ok.length; i++) {
452 if (response.startsWith("" + ok[i])) {
453 return true;
454 }
455 }
456 return false;
457 }
458
459 void disconnect() throws IOException {
460 if (out != null) {
461 out.close();
462 }
463 if (in != null) {
464 try {
465 in.close();
466 } catch (IOException e) {
467 // ignore
468 }
469 }
470 if (socket != null) {
471 try {
472 socket.close();
473 } catch (IOException e) {
474 // ignore
475 }
476 }
477 }
478}
479
480/**
481 * This PrintStream subclass makes sure that <CRLF>. becomes <CRLF>..
482 * per RFC 821. It also ensures that new lines are always \r\n.
483*/
484class MailPrintStream extends PrintStream {
485
486 private int lastChar;
487
488 public MailPrintStream(OutputStream out) {
489 super(out, true); // deprecated, but email is byte-oriented
490 }
491
492 // Mac does \n\r, but that's tough to distinguish from Windows \r\n\r\n.
493 // Don't tackle that problem right now.
494 public void write(int b) {
495 if (b == '\n' && lastChar != '\r') {
496 rawWrite('\r'); // ensure always \r\n
497 rawWrite(b);
498 } else if (b == '.' && lastChar == '\n') {
499 rawWrite('.'); // add extra dot
500 rawWrite(b);
501 } else {
502 rawWrite(b);
503 }
504 lastChar = b;
505 }
506
507 public void write(byte[] buf, int off, int len) {
508 for (int i = 0; i < len; i++) {
509 write(buf[off + i]);
510 }
511 }
512
513 void rawWrite(int b) {
514 super.write(b);
515 }
516
517 void rawPrint(String s) {
518 int len = s.length();
519 for (int i = 0; i < len; i++) {
520 rawWrite(s.charAt(i));
521 }
522 }
523}
524
Note: See TracBrowser for help on using the repository browser.