|
|
@@ -0,0 +1,448 @@ |
|
|
|
// Copyright (c) 2000 Jason Hunter <jh@servlets.com> 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: |
|
|
|
* <ul> |
|
|
|
* <li>Send submitted form data to interested parties |
|
|
|
* <li>Send an email page to an administrator in case of error |
|
|
|
* <li>Send the client an order confirmation |
|
|
|
* </ul> |
|
|
|
* <p> |
|
|
|
* 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. |
|
|
|
* <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.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> |
|
|
|
* This class is part of a larger com.oreilly.servlet project from |
|
|
|
* <a href="http://Servlets.com">Servlets.com</a>. |
|
|
|
* |
|
|
|
* @author <b>Jason Hunter</b>, 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 <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, 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 <CRLF>.<CRLF> |
|
|
|
//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 <CRLF>. becomes <CRLF>.. |
|
|
|
// 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)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |