and
+// The Apache Software Foundation. All rights reserved.
+//
+// NOTE:
+// This code is mirrored from http://Servlets.com
+// Updates should be done in conjunction with Servlets.com
+// and the larger com.oreilly.servlet project
+
+/*
+ * The Apache Software License, Version 1.1
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ */
+
+package com.oreilly.servlet;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ * A class to help send SMTP email. It can be used by any Java program, not
+ * just servlets. Servlets are likely to use this class to:
+ *
+ * - Send submitted form data to interested parties
+ *
- Send an email page to an administrator in case of error
+ *
- Send the client an order confirmation
+ *
+ *
+ * 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 a more open license.
+ *
+ * It can be used like this:
+ *
+ * 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.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();
+ *
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Still to do:
+ *
+ * - Figure out how to close the connection in case of error
+ *
+ * This class is part of a larger com.oreilly.servlet project from
+ * Servlets.com.
+ *
+ * @author Jason Hunter, Copyright © 1999-2000
+ * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers
+ * @version 1.0, 1999/12/29
+ */
+public class MailMessage {
+
+ String host;
+ String from;
+ Vector to, cc;
+ Hashtable headers;
+ MailPrintStream out;
+ BufferedReader in;
+ Socket socket;
+
+ /**
+ * Constructs a new MailMessage to send an email.
+ * Use localhost as the mail server.
+ *
+ * @exception IOException if there's any problem contacting the mail server
+ */
+ public MailMessage() throws IOException {
+ this("localhost");
+ }
+
+ /**
+ * Constructs a new MailMessage to send an email.
+ * Use the given host as the mail server.
+ *
+ * @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 = host;
+ to = new Vector();
+ cc = new Vector();
+ headers = new Hashtable();
+ setHeader("X-Mailer", "com.oreilly.servlet.MailMessage (www.servlets.com)");
+ connect();
+ sendHelo();
+ }
+
+ /**
+ * Sets the from address. Also sets the "From" header. This method should
+ * be called only once.
+ *
+ * @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 to address. Also sets the "To" header. This method may be
+ * called multiple times.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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.
+ */
+ public void setSubject(String subj) {
+ headers.put("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.
+ */
+ public void setHeader(String name, String value) {
+ // Blindly trust the user doesn't set any invalid headers
+ headers.put(name, 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 could
+ * be wrapped on top if necessary for internationalization.
+ *
+ * @exception IOException if there's any problem reported by the mail server
+ */
+ public PrintStream getPrintStream() throws IOException {
+ setFromHeader();
+ setToHeader();
+ setCcHeader();
+ sendData();
+ flushHeaders();
+ return out;
+ }
+
+ void setFromHeader() {
+ setHeader("From", from);
+ }
+
+ void setToHeader() {
+ setHeader("To", vectorToList(to));
+ }
+
+ void setCcHeader() {
+ 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 {
+ // XXX Should I care about order here?
+ Enumeration e = headers.keys();
+ while (e.hasMoreElements()) {
+ String name = (String) e.nextElement();
+ String value = (String) headers.get(name);
+ 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 {
+ sendDot();
+ disconnect();
+ }
+
+ // Make a limited attempt to extract a sanitized email address
+ // Prefer text in , 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, 25);
+ out = new MailPrintStream(
+ new BufferedOutputStream(
+ socket.getOutputStream()));
+ in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ getReady();
+ }
+
+ void getReady() throws IOException {
+ String response = in.readLine();
+ int[] ok = { 220 };
+ 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 = { 250 };
+ send("HELO " + local, ok);
+ }
+
+ void sendFrom(String from) throws IOException {
+ int[] ok = { 250 };
+ send("MAIL FROM: " + "<" + sanitizeAddress(from) + ">", ok);
+ }
+
+ void sendRcpt(String rcpt) throws IOException {
+ int[] ok = { 250, 251 };
+ send("RCPT TO: " + "<" + sanitizeAddress(rcpt) + ">", ok);
+ }
+
+ void sendData() throws IOException {
+ int[] ok = { 354 };
+ send("DATA", ok);
+ }
+
+ void sendDot() throws IOException {
+ int[] ok = { 250 };
+ send("\r\n.", ok); // make sure dot is on new line
+ }
+
+ void sendQuit() throws IOException {
+ int[] ok = { 221 };
+ send("QUIT", ok);
+ }
+
+ void send(String msg, int[] ok) throws IOException {
+ out.rawPrint(msg + "\r\n"); // raw supports .
+ //System.out.println("S: " + msg);
+ String response = in.readLine();
+ //System.out.println("R: " + response);
+ 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) in.close();
+ if (socket != null) socket.close();
+ }
+}
+
+// This PrintStream subclass makes sure that . becomes ..
+// per RFC 821. It also ensures that new lines are always \r\n.
+//
+class MailPrintStream extends PrintStream {
+
+ 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));
+ }
+ }
+}