You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

MailMessage.java 13 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 1999 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowlegement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowlegement may appear in the software itself,
  24. * if and wherever such third-party acknowlegements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Group.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. */
  50. /*
  51. * The original version of this class was donated by Jason Hunter,
  52. * who wrote the class as part of the com.oreilly.servlet
  53. * package for his book "Java Servlet Programming" (O'Reilly).
  54. * See http://www.servlets.com.
  55. *
  56. */
  57. package org.apache.tools.mail;
  58. import java.io.*;
  59. import java.net.*;
  60. import java.util.*;
  61. /**
  62. * A class to help send SMTP email.
  63. * This class is an improvement on the sun.net.smtp.SmtpClient class
  64. * found in the JDK. This version has extra functionality, and can be used
  65. * with JVMs that did not extend from the JDK. It's not as robust as
  66. * the JavaMail Standard Extension classes, but it's easier to use and
  67. * easier to install, and has an Open Source license.
  68. * <p>
  69. * It can be used like this:
  70. * <blockquote><pre>
  71. * String mailhost = "localhost"; // or another mail host
  72. * String from = "Mail Message Servlet &lt;MailMessage@server.com&gt;";
  73. * String to = "to@you.com";
  74. * String cc1 = "cc1@you.com";
  75. * String cc2 = "cc2@you.com";
  76. * String bcc = "bcc@you.com";
  77. * &nbsp;
  78. * MailMessage msg = new MailMessage(mailhost);
  79. * msg.from(from);
  80. * msg.to(to);
  81. * msg.cc(cc1);
  82. * msg.cc(cc2);
  83. * msg.bcc(bcc);
  84. * msg.setSubject("Test subject");
  85. * PrintStream out = msg.getPrintStream();
  86. * &nbsp;
  87. * Enumeration enum = req.getParameterNames();
  88. * while (enum.hasMoreElements()) {
  89. * String name = (String)enum.nextElement();
  90. * String value = req.getParameter(name);
  91. * out.println(name + " = " + value);
  92. * }
  93. * &nbsp;
  94. * msg.sendAndClose();
  95. * </pre></blockquote>
  96. * <p>
  97. * Be sure to set the from address, then set the recepient
  98. * addresses, then set the subject and other headers, then get the
  99. * PrintStream, then write the message, and finally send and close.
  100. * The class does minimal error checking internally; it counts on the mail
  101. * host to complain if there's any malformatted input or out of order
  102. * execution.
  103. * <p>
  104. * An attachment mechanism based on RFC 1521 could be implemented on top of
  105. * this class. In the meanwhile, JavaMail is the best solution for sending
  106. * email with attachments.
  107. * <p>
  108. * Still to do:
  109. * <ul>
  110. * <li>Figure out how to close the connection in case of error
  111. * </ul>
  112. *
  113. * @author Jason Hunter
  114. * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers
  115. * version 1.0, 1999/12/29
  116. */
  117. public class MailMessage {
  118. String host;
  119. String from;
  120. Vector to, cc;
  121. Hashtable headers;
  122. MailPrintStream out;
  123. SmtpResponseReader in;
  124. Socket socket;
  125. /**
  126. * Constructs a new MailMessage to send an email.
  127. * Use localhost as the mail server.
  128. *
  129. * @exception IOException if there's any problem contacting the mail server
  130. */
  131. public MailMessage() throws IOException {
  132. this("localhost");
  133. }
  134. /**
  135. * Constructs a new MailMessage to send an email.
  136. * Use the given host as the mail server.
  137. *
  138. * @param host the mail server to use
  139. * @exception IOException if there's any problem contacting the mail server
  140. */
  141. public MailMessage(String host) throws IOException {
  142. this.host = host;
  143. to = new Vector();
  144. cc = new Vector();
  145. headers = new Hashtable();
  146. setHeader("X-Mailer", "org.apache.tools.mail.MailMessage (jakarta.apache.org)");
  147. connect();
  148. sendHelo();
  149. }
  150. /**
  151. * Sets the from address. Also sets the "From" header. This method should
  152. * be called only once.
  153. *
  154. * @exception IOException if there's any problem reported by the mail server
  155. */
  156. public void from(String from) throws IOException {
  157. sendFrom(from);
  158. this.from = from;
  159. }
  160. /**
  161. * Sets the to address. Also sets the "To" header. This method may be
  162. * called multiple times.
  163. *
  164. * @exception IOException if there's any problem reported by the mail server
  165. */
  166. public void to(String to) throws IOException {
  167. sendRcpt(to);
  168. this.to.addElement(to);
  169. }
  170. /**
  171. * Sets the cc address. Also sets the "Cc" header. This method may be
  172. * called multiple times.
  173. *
  174. * @exception IOException if there's any problem reported by the mail server
  175. */
  176. public void cc(String cc) throws IOException {
  177. sendRcpt(cc);
  178. this.cc.addElement(cc);
  179. }
  180. /**
  181. * Sets the bcc address. Does NOT set any header since it's a *blind* copy.
  182. * This method may be called multiple times.
  183. *
  184. * @exception IOException if there's any problem reported by the mail server
  185. */
  186. public void bcc(String bcc) throws IOException {
  187. sendRcpt(bcc);
  188. // No need to keep track of Bcc'd addresses
  189. }
  190. /**
  191. * Sets the subject of the mail message. Actually sets the "Subject"
  192. * header.
  193. */
  194. public void setSubject(String subj) {
  195. headers.put("Subject", subj);
  196. }
  197. /**
  198. * Sets the named header to the given value. RFC 822 provides the rules for
  199. * what text may constitute a header name and value.
  200. */
  201. public void setHeader(String name, String value) {
  202. // Blindly trust the user doesn't set any invalid headers
  203. headers.put(name, value);
  204. }
  205. /**
  206. * Returns a PrintStream that can be used to write the body of the message.
  207. * A stream is used since email bodies are byte-oriented. A writer could
  208. * be wrapped on top if necessary for internationalization.
  209. *
  210. * @exception IOException if there's any problem reported by the mail server
  211. */
  212. public PrintStream getPrintStream() throws IOException {
  213. setFromHeader();
  214. setToHeader();
  215. setCcHeader();
  216. sendData();
  217. flushHeaders();
  218. return out;
  219. }
  220. void setFromHeader() {
  221. setHeader("From", from);
  222. }
  223. void setToHeader() {
  224. setHeader("To", vectorToList(to));
  225. }
  226. void setCcHeader() {
  227. setHeader("Cc", vectorToList(cc));
  228. }
  229. String vectorToList(Vector v) {
  230. StringBuffer buf = new StringBuffer();
  231. Enumeration e = v.elements();
  232. while (e.hasMoreElements()) {
  233. buf.append(e.nextElement());
  234. if (e.hasMoreElements()) {
  235. buf.append(", ");
  236. }
  237. }
  238. return buf.toString();
  239. }
  240. void flushHeaders() throws IOException {
  241. // XXX Should I care about order here?
  242. Enumeration e = headers.keys();
  243. while (e.hasMoreElements()) {
  244. String name = (String) e.nextElement();
  245. String value = (String) headers.get(name);
  246. out.println(name + ": " + value);
  247. }
  248. out.println();
  249. out.flush();
  250. }
  251. /**
  252. * Sends the message and closes the connection to the server.
  253. * The MailMessage object cannot be reused.
  254. *
  255. * @exception IOException if there's any problem reported by the mail server
  256. */
  257. public void sendAndClose() throws IOException {
  258. sendDot();
  259. sendQuit();
  260. disconnect();
  261. }
  262. // Make a limited attempt to extract a sanitized email address
  263. // Prefer text in <brackets>, ignore anything in (parentheses)
  264. static String sanitizeAddress(String s) {
  265. int paramDepth = 0;
  266. int start = 0;
  267. int end = 0;
  268. int len = s.length();
  269. for (int i = 0; i < len; i++) {
  270. char c = s.charAt(i);
  271. if (c == '(') {
  272. paramDepth++;
  273. if (start == 0) {
  274. end = i; // support "address (name)"
  275. }
  276. }
  277. else if (c == ')') {
  278. paramDepth--;
  279. if (end == 0) {
  280. start = i + 1; // support "(name) address"
  281. }
  282. }
  283. else if (paramDepth == 0 && c == '<') {
  284. start = i + 1;
  285. }
  286. else if (paramDepth == 0 && c == '>') {
  287. end = i;
  288. }
  289. }
  290. if (end == 0) {
  291. end = len;
  292. }
  293. return s.substring(start, end);
  294. }
  295. // * * * * * Raw protocol methods below here * * * * *
  296. void connect() throws IOException {
  297. socket = new Socket(host, 25);
  298. out = new MailPrintStream(
  299. new BufferedOutputStream(
  300. socket.getOutputStream()));
  301. in = new SmtpResponseReader(socket.getInputStream());
  302. getReady();
  303. }
  304. void getReady() throws IOException {
  305. String response = in.getResponse();
  306. int[] ok = { 220 };
  307. if (!isResponseOK(response, ok)) {
  308. throw new IOException(
  309. "Didn't get introduction from server: " + response);
  310. }
  311. }
  312. void sendHelo() throws IOException {
  313. String local = InetAddress.getLocalHost().getHostName();
  314. int[] ok = { 250 };
  315. send("HELO " + local, ok);
  316. }
  317. void sendFrom(String from) throws IOException {
  318. int[] ok = { 250 };
  319. send("MAIL FROM: " + "<" + sanitizeAddress(from) + ">", ok);
  320. }
  321. void sendRcpt(String rcpt) throws IOException {
  322. int[] ok = { 250, 251 };
  323. send("RCPT TO: " + "<" + sanitizeAddress(rcpt) + ">", ok);
  324. }
  325. void sendData() throws IOException {
  326. int[] ok = { 354 };
  327. send("DATA", ok);
  328. }
  329. void sendDot() throws IOException {
  330. int[] ok = { 250 };
  331. send("\r\n.", ok); // make sure dot is on new line
  332. }
  333. void sendQuit() throws IOException {
  334. int[] ok = { 221 };
  335. send("QUIT", ok);
  336. }
  337. void send(String msg, int[] ok) throws IOException {
  338. out.rawPrint(msg + "\r\n"); // raw supports <CRLF>.<CRLF>
  339. //System.out.println("S: " + msg);
  340. String response = in.getResponse();
  341. //System.out.println("R: " + response);
  342. if (!isResponseOK(response, ok)) {
  343. throw new IOException(
  344. "Unexpected reply to command: " + msg + ": " + response);
  345. }
  346. }
  347. boolean isResponseOK(String response, int[] ok) {
  348. // Check that the response is one of the valid codes
  349. for (int i = 0; i < ok.length; i++) {
  350. if (response.startsWith("" + ok[i])) {
  351. return true;
  352. }
  353. }
  354. return false;
  355. }
  356. void disconnect() throws IOException {
  357. if (out != null) out.close();
  358. if (in != null) in.close();
  359. if (socket != null) socket.close();
  360. }
  361. }
  362. // This PrintStream subclass makes sure that <CRLF>. becomes <CRLF>..
  363. // per RFC 821. It also ensures that new lines are always \r\n.
  364. //
  365. class MailPrintStream extends PrintStream {
  366. int lastChar;
  367. public MailPrintStream(OutputStream out) {
  368. super(out, true); // deprecated, but email is byte-oriented
  369. }
  370. // Mac does \n\r, but that's tough to distinguish from Windows \r\n\r\n.
  371. // Don't tackle that problem right now.
  372. public void write(int b) {
  373. if (b == '\n' && lastChar != '\r') {
  374. rawWrite('\r'); // ensure always \r\n
  375. rawWrite(b);
  376. }
  377. else if (b == '.' && lastChar == '\n') {
  378. rawWrite('.'); // add extra dot
  379. rawWrite(b);
  380. }
  381. else {
  382. rawWrite(b);
  383. }
  384. lastChar = b;
  385. }
  386. public void write(byte buf[], int off, int len) {
  387. for (int i = 0; i < len; i++) {
  388. write(buf[off + i]);
  389. }
  390. }
  391. void rawWrite(int b) {
  392. super.write(b);
  393. }
  394. void rawPrint(String s) {
  395. int len = s.length();
  396. for (int i = 0; i < len; i++) {
  397. rawWrite(s.charAt(i));
  398. }
  399. }
  400. }