/src/main/org/apache/tools/mail/MailMessage.java
Java | 526 lines | 272 code | 57 blank | 197 comment | 50 complexity | b887e783a830bd59e5a48db835770a4d MD5 | raw file
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
- /*
- * The original version of this class was donated by Jason Hunter,
- * who wrote the class as part of the com.oreilly.servlet
- * package for his book "Java Servlet Programming" (O'Reilly).
- * See http://www.servlets.com.
- *
- */
- package org.apache.tools.mail;
- import java.io.IOException;
- import java.io.PrintStream;
- import java.io.BufferedOutputStream;
- import java.io.OutputStream;
- import java.net.Socket;
- import java.net.InetAddress;
- import java.util.Vector;
- import java.util.Enumeration;
- /**
- * A class to help send SMTP email.
- * This class is an improvement on the sun.net.smtp.SmtpClient class
- * found in the JDK. This version has extra functionality, and can be used
- * with JVMs that did not extend from the JDK. It's not as robust as
- * the JavaMail Standard Extension classes, but it's easier to use and
- * easier to install, and has an Open Source license.
- * <p>
- * It can be used like this:
- * <blockquote><pre>
- * String mailhost = "localhost"; // or another mail host
- * String from = "Mail Message Servlet <MailMessage@server.com>";
- * String to = "to@you.com";
- * String cc1 = "cc1@you.com";
- * String cc2 = "cc2@you.com";
- * String bcc = "bcc@you.com";
- *
- * MailMessage msg = new MailMessage(mailhost);
- * msg.setPort(25);
- * msg.from(from);
- * msg.to(to);
- * msg.cc(cc1);
- * msg.cc(cc2);
- * msg.bcc(bcc);
- * msg.setSubject("Test subject");
- * PrintStream out = msg.getPrintStream();
- *
- * Enumeration enum = req.getParameterNames();
- * while (enum.hasMoreElements()) {
- * String name = (String)enum.nextElement();
- * String value = req.getParameter(name);
- * out.println(name + " = " + value);
- * }
- *
- * msg.sendAndClose();
- * </pre></blockquote>
- * <p>
- * Be sure to set the from address, then set the recipient
- * addresses, then set the subject and other headers, then get the
- * PrintStream, then write the message, and finally send and close.
- * The class does minimal error checking internally; it counts on the mail
- * host to complain if there's any malformatted input or out of order
- * execution.
- * <p>
- * An attachment mechanism based on RFC 1521 could be implemented on top of
- * this class. In the meanwhile, JavaMail is the best solution for sending
- * email with attachments.
- * <p>
- * Still to do:
- * <ul>
- * <li>Figure out how to close the connection in case of error
- * </ul>
- *
- * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers
- * version 1.0, 1999/12/29
- */
- public class MailMessage {
- /** default mailhost */
- public static final String DEFAULT_HOST = "localhost";
- /** default port for SMTP: 25 */
- public static final int DEFAULT_PORT = 25;
- /** host name for the mail server */
- private String host;
- /** host port for the mail server */
- private int port = DEFAULT_PORT;
- /** sender email address */
- private String from;
- /** list of email addresses to reply to */
- private Vector replyto;
- /** list of email addresses to send to */
- private Vector to;
- /** list of email addresses to cc to */
- private Vector cc;
- /** headers to send in the mail */
- private Vector headersKeys;
- private Vector headersValues;
- private MailPrintStream out;
- private SmtpResponseReader in;
- private Socket socket;
- private static final int OK_READY = 220;
- private static final int OK_HELO = 250;
- private static final int OK_FROM = 250;
- private static final int OK_RCPT_1 = 250;
- private static final int OK_RCPT_2 = 251;
- private static final int OK_DATA = 354;
- private static final int OK_DOT = 250;
- private static final int OK_QUIT = 221;
- /**
- * Constructs a new MailMessage to send an email.
- * Use localhost as the mail server with port 25.
- *
- * @exception IOException if there's any problem contacting the mail server
- */
- public MailMessage() throws IOException {
- this(DEFAULT_HOST, DEFAULT_PORT);
- }
- /**
- * Constructs a new MailMessage to send an email.
- * Use the given host as the mail server with port 25.
- *
- * @param host the mail server to use
- * @exception IOException if there's any problem contacting the mail server
- */
- public MailMessage(String host) throws IOException {
- this(host, DEFAULT_PORT);
- }
- /**
- * Constructs a new MailMessage to send an email.
- * Use the given host and port as the mail server.
- *
- * @param host the mail server to use
- * @param port the port to connect to
- * @exception IOException if there's any problem contacting the mail server
- */
- public MailMessage(String host, int port) throws IOException {
- this.port = port;
- this.host = host;
- replyto = new Vector();
- to = new Vector();
- cc = new Vector();
- headersKeys = new Vector();
- headersValues = new Vector();
- connect();
- sendHelo();
- }
- /**
- * Set the port to connect to the SMTP host.
- * @param port the port to use for connection.
- * @see #DEFAULT_PORT
- */
- public void setPort(int port) {
- this.port = port;
- }
- /**
- * Sets the from address. Also sets the "From" header. This method should
- * be called only once.
- * @param from the from address
- * @exception IOException if there's any problem reported by the mail server
- */
- public void from(String from) throws IOException {
- sendFrom(from);
- this.from = from;
- }
- /**
- * Sets the replyto address
- * This method may be
- * called multiple times.
- * @param rto the replyto address
- *
- */
- public void replyto(String rto) {
- this.replyto.addElement(rto);
- }
- /**
- * Sets the to address. Also sets the "To" header. This method may be
- * called multiple times.
- *
- * @param to the to address
- * @exception IOException if there's any problem reported by the mail server
- */
- public void to(String to) throws IOException {
- sendRcpt(to);
- this.to.addElement(to);
- }
- /**
- * Sets the cc address. Also sets the "Cc" header. This method may be
- * called multiple times.
- *
- * @param cc the cc address
- * @exception IOException if there's any problem reported by the mail server
- */
- public void cc(String cc) throws IOException {
- sendRcpt(cc);
- this.cc.addElement(cc);
- }
- /**
- * Sets the bcc address. Does NOT set any header since it's a *blind* copy.
- * This method may be called multiple times.
- *
- * @param bcc the bcc address
- * @exception IOException if there's any problem reported by the mail server
- */
- public void bcc(String bcc) throws IOException {
- sendRcpt(bcc);
- // No need to keep track of Bcc'd addresses
- }
- /**
- * Sets the subject of the mail message. Actually sets the "Subject"
- * header.
- * @param subj the subject of the mail message
- */
- public void setSubject(String subj) {
- setHeader("Subject", subj);
- }
- /**
- * Sets the named header to the given value. RFC 822 provides the rules for
- * what text may constitute a header name and value.
- * @param name name of the header
- * @param value contents of the header
- */
- public void setHeader(String name, String value) {
- // Blindly trust the user doesn't set any invalid headers
- headersKeys.add(name);
- headersValues.add(value);
- }
- /**
- * Returns a PrintStream that can be used to write the body of the message.
- * A stream is used since email bodies are byte-oriented. A writer can
- * be wrapped on top if necessary for internationalization.
- * This is actually done in Message.java
- *
- * @return a printstream containing the data and the headers of the email
- * @exception IOException if there's any problem reported by the mail server
- * @see org.apache.tools.ant.taskdefs.email.Message
- */
- public PrintStream getPrintStream() throws IOException {
- setFromHeader();
- setReplyToHeader();
- setToHeader();
- setCcHeader();
- setHeader("X-Mailer", "org.apache.tools.mail.MailMessage (ant.apache.org)");
- sendData();
- flushHeaders();
- return out;
- }
- // RFC 822 s4.1: "From:" header must be sent
- // We rely on error checking by the MTA
- void setFromHeader() {
- setHeader("From", from);
- }
- // RFC 822 s4.1: "Reply-To:" header is optional
- void setReplyToHeader() {
- if (!replyto.isEmpty()) {
- setHeader("Reply-To", vectorToList(replyto));
- }
- }
- void setToHeader() {
- if (!to.isEmpty()) {
- setHeader("To", vectorToList(to));
- }
- }
- void setCcHeader() {
- if (!cc.isEmpty()) {
- setHeader("Cc", vectorToList(cc));
- }
- }
- String vectorToList(Vector v) {
- StringBuffer buf = new StringBuffer();
- Enumeration e = v.elements();
- while (e.hasMoreElements()) {
- buf.append(e.nextElement());
- if (e.hasMoreElements()) {
- buf.append(", ");
- }
- }
- return buf.toString();
- }
- void flushHeaders() throws IOException {
- // RFC 822 s4.1:
- // "Header fields are NOT required to occur in any particular order,
- // except that the message body MUST occur AFTER the headers"
- // (the same section specifies a reccommended order, which we ignore)
- final int size = headersKeys.size();
- for (int i = 0; i < size; i++) {
- String name = (String) headersKeys.elementAt(i);
- String value = (String) headersValues.elementAt(i);
- out.println(name + ": " + value);
- }
- out.println();
- out.flush();
- }
- /**
- * Sends the message and closes the connection to the server.
- * The MailMessage object cannot be reused.
- *
- * @exception IOException if there's any problem reported by the mail server
- */
- public void sendAndClose() throws IOException {
- try {
- sendDot();
- sendQuit();
- } finally {
- disconnect();
- }
- }
- // Make a limited attempt to extract a sanitized email address
- // Prefer text in <brackets>, ignore anything in (parentheses)
- static String sanitizeAddress(String s) {
- int paramDepth = 0;
- int start = 0;
- int end = 0;
- int len = s.length();
- for (int i = 0; i < len; i++) {
- char c = s.charAt(i);
- if (c == '(') {
- paramDepth++;
- if (start == 0) {
- end = i; // support "address (name)"
- }
- } else if (c == ')') {
- paramDepth--;
- if (end == 0) {
- start = i + 1; // support "(name) address"
- }
- } else if (paramDepth == 0 && c == '<') {
- start = i + 1;
- } else if (paramDepth == 0 && c == '>') {
- end = i;
- }
- }
- if (end == 0) {
- end = len;
- }
- return s.substring(start, end);
- }
- // * * * * * Raw protocol methods below here * * * * *
- void connect() throws IOException {
- socket = new Socket(host, port);
- out = new MailPrintStream(
- new BufferedOutputStream(
- socket.getOutputStream()));
- in = new SmtpResponseReader(socket.getInputStream());
- getReady();
- }
- void getReady() throws IOException {
- String response = in.getResponse();
- int[] ok = {OK_READY};
- if (!isResponseOK(response, ok)) {
- throw new IOException(
- "Didn't get introduction from server: " + response);
- }
- }
- void sendHelo() throws IOException {
- String local = InetAddress.getLocalHost().getHostName();
- int[] ok = {OK_HELO};
- send("HELO " + local, ok);
- }
- void sendFrom(String from) throws IOException {
- int[] ok = {OK_FROM};
- send("MAIL FROM: " + "<" + sanitizeAddress(from) + ">", ok);
- }
- void sendRcpt(String rcpt) throws IOException {
- int[] ok = {OK_RCPT_1, OK_RCPT_2};
- send("RCPT TO: " + "<" + sanitizeAddress(rcpt) + ">", ok);
- }
- void sendData() throws IOException {
- int[] ok = {OK_DATA};
- send("DATA", ok);
- }
- void sendDot() throws IOException {
- int[] ok = {OK_DOT};
- send("\r\n.", ok); // make sure dot is on new line
- }
- void sendQuit() throws IOException {
- int[] ok = {OK_QUIT};
- try {
- send("QUIT", ok);
- } catch (IOException e) {
- throw new ErrorInQuitException(e);
- }
- }
- void send(String msg, int[] ok) throws IOException {
- out.rawPrint(msg + "\r\n"); // raw supports <CRLF>.<CRLF>
- String response = in.getResponse();
- if (!isResponseOK(response, ok)) {
- throw new IOException("Unexpected reply to command: "
- + msg + ": " + response);
- }
- }
- boolean isResponseOK(String response, int[] ok) {
- // Check that the response is one of the valid codes
- for (int i = 0; i < ok.length; i++) {
- if (response.startsWith("" + ok[i])) {
- return true;
- }
- }
- return false;
- }
- void disconnect() throws IOException {
- if (out != null) {
- out.close();
- }
- if (in != null) {
- try {
- in.close();
- } catch (IOException e) {
- // ignore
- }
- }
- if (socket != null) {
- try {
- socket.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
- }
- /**
- * This PrintStream subclass makes sure that <CRLF>. becomes <CRLF>..
- * per RFC 821. It also ensures that new lines are always \r\n.
- */
- class MailPrintStream extends PrintStream {
- private int lastChar;
- public MailPrintStream(OutputStream out) {
- super(out, true); // deprecated, but email is byte-oriented
- }
- // Mac does \n\r, but that's tough to distinguish from Windows \r\n\r\n.
- // Don't tackle that problem right now.
- public void write(int b) {
- if (b == '\n' && lastChar != '\r') {
- rawWrite('\r'); // ensure always \r\n
- rawWrite(b);
- } else if (b == '.' && lastChar == '\n') {
- rawWrite('.'); // add extra dot
- rawWrite(b);
- } else {
- rawWrite(b);
- }
- lastChar = b;
- }
- public void write(byte[] buf, int off, int len) {
- for (int i = 0; i < len; i++) {
- write(buf[off + i]);
- }
- }
- void rawWrite(int b) {
- super.write(b);
- }
- void rawPrint(String s) {
- int len = s.length();
- for (int i = 0; i < len; i++) {
- rawWrite(s.charAt(i));
- }
- }
- }