PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/org/apache/tools/mail/MailMessage.java

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