- /*
 - * Copyright 2000-2004 The Apache Software Foundation
 - *
 - * Licensed 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 recepient
 - * 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)
 - for (int i = 0; i < headersKeys.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));
 - }
 - }
 - }