diff --git a/WHATSNEW b/WHATSNEW index d756c6908..d6687ac32 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -405,6 +405,11 @@ Other changes: or (name) address@xyz.com Bugzilla Report 22474. +* (version PlainMail) + prevent blank headers from being sent, + make the order of the headers of plain mail messages predictable + Bugzilla Report 22088. + * can now be defined in the main body of a project and referred to with refid="xyz". Bugzilla Report 17007. diff --git a/src/main/org/apache/tools/mail/MailMessage.java b/src/main/org/apache/tools/mail/MailMessage.java index c17d8f39c..c40b0ac17 100644 --- a/src/main/org/apache/tools/mail/MailMessage.java +++ b/src/main/org/apache/tools/mail/MailMessage.java @@ -69,7 +69,6 @@ import java.io.OutputStream; import java.net.Socket; import java.net.InetAddress; import java.util.Vector; -import java.util.Hashtable; import java.util.Enumeration; /** @@ -131,6 +130,9 @@ import java.util.Enumeration; */ public class MailMessage { + /** default mailhost */ + public static final String DEFAULT_HOST = "localhost"; + /** default port for SMTP: 25 */ public static final int DEFAULT_PORT = 25; @@ -153,13 +155,22 @@ public class MailMessage { private Vector cc; /** headers to send in the mail */ - private Hashtable headers; + 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. @@ -168,7 +179,7 @@ public class MailMessage { * @exception IOException if there's any problem contacting the mail server */ public MailMessage() throws IOException { - this("localhost", DEFAULT_PORT); + this(DEFAULT_HOST, DEFAULT_PORT); } /** @@ -179,7 +190,7 @@ public class MailMessage { * @exception IOException if there's any problem contacting the mail server */ public MailMessage(String host) throws IOException { - this(host, DEFAULT_PORT); + this(host, DEFAULT_PORT); } /** @@ -196,8 +207,8 @@ public class MailMessage { replyto = new Vector(); to = new Vector(); cc = new Vector(); - headers = new Hashtable(); - setHeader("X-Mailer", "org.apache.tools.mail.MailMessage (ant.apache.org)"); + headersKeys = new Vector(); + headersValues = new Vector(); connect(); sendHelo(); } @@ -214,7 +225,7 @@ public class MailMessage { /** * 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 { @@ -226,6 +237,7 @@ public class MailMessage { * Sets the replyto address * This method may be * called multiple times. + * @param rto the replyto address * */ public void replyto(String rto) { @@ -236,6 +248,7 @@ public class MailMessage { * 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 { @@ -247,6 +260,7 @@ public class MailMessage { * 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 { @@ -258,6 +272,7 @@ public class MailMessage { * 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 { @@ -268,50 +283,69 @@ public class MailMessage { /** * 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) { - headers.put("Subject", 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 - headers.put(name, value); + 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 could + * 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() { - setHeader("To", vectorToList(to)); + if (!to.isEmpty()) { + setHeader("To", vectorToList(to)); + } } void setCcHeader() { - setHeader("Cc", vectorToList(cc)); + if (!cc.isEmpty()) { + setHeader("Cc", vectorToList(cc)); + } } String vectorToList(Vector v) { @@ -327,11 +361,13 @@ public class MailMessage { } 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); + // 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(); @@ -400,41 +436,38 @@ public class MailMessage { void getReady() throws IOException { String response = in.getResponse(); - int[] ok = {220}; + 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 = {250}; + int[] ok = {OK_HELO}; send("HELO " + local, ok); } - void sendFrom(String from) throws IOException { - int[] ok = {250}; + int[] ok = {OK_FROM}; send("MAIL FROM: " + "<" + sanitizeAddress(from) + ">", ok); } - void sendRcpt(String rcpt) throws IOException { - int[] ok = {250, 251}; + int[] ok = {OK_RCPT_1, OK_RCPT_2}; send("RCPT TO: " + "<" + sanitizeAddress(rcpt) + ">", ok); } void sendData() throws IOException { - int[] ok = {354}; + int[] ok = {OK_DATA}; send("DATA", ok); } void sendDot() throws IOException { - int[] ok = {250}; + int[] ok = {OK_DOT}; send("\r\n.", ok); // make sure dot is on new line } void sendQuit() throws IOException { - int[] ok = {221}; + int[] ok = {OK_QUIT}; try { send("QUIT", ok); } catch (IOException e) { @@ -482,12 +515,13 @@ public class MailMessage { } } -// This PrintStream subclass makes sure that . becomes .. -// per RFC 821. It also ensures that new lines are always \r\n. -// +/** + * 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; + private int lastChar; public MailPrintStream(OutputStream out) { super(out, true); // deprecated, but email is byte-oriented diff --git a/src/testcases/org/apache/tools/mail/MailMessageTest.java b/src/testcases/org/apache/tools/mail/MailMessageTest.java new file mode 100644 index 000000000..376845753 --- /dev/null +++ b/src/testcases/org/apache/tools/mail/MailMessageTest.java @@ -0,0 +1,771 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * 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 "Ant" 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.mail; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.ServerSocket; +import java.util.Enumeration; +import java.util.Vector; + +import org.apache.tools.mail.MailMessage; + +import junit.framework.TestCase; + +/** + * JUnit 3 testcases for org.apache.tools.mail.MailMessage. + * + * @author Michael Davey + * @since Ant 1.6 + */ +public class MailMessageTest extends TestCase { + + // 27224 = magic (a random port which is unlikely to be in use) + private static int TEST_PORT = 27224; + + private String local = null; + + public MailMessageTest(String name) { + super(name); + } + + public void setUp() { + try { + local = InetAddress.getLocalHost().getHostName(); + } catch (java.net.UnknownHostException uhe) { + // ignore + } + } + + /** + * Test an example that is similar to the one given in the API + * If this testcase takes >90s to complete, it is very likely that + * the two threads are blocked waiting for each other and Thread.join() + * timed out. + */ + public void testAPIExample() { + + ServerThread testMailServer = new ServerThread(); + Thread server = new Thread(testMailServer); + server.start(); + + ClientThread testMailClient = new ClientThread(); + + testMailClient.from("Mail Message "); + testMailClient.to("to@you.com"); + testMailClient.cc("cc1@you.com"); + testMailClient.cc("cc2@you.com"); + testMailClient.bcc("bcc@you.com"); + testMailClient.setSubject("Test subject"); + testMailClient.setMessage( "test line 1\n" + + "test line 2" ); + + Thread client = new Thread(testMailClient); + client.start(); + + try { + server.join(60 * 1000); // 60s + client.join(30 * 1000); // a further 30s + } catch (InterruptedException ie ) { + fail( "InterruptedException: " + ie ); + } + + String result = testMailServer.getResult(); + String expectedResult = "220 test SMTP EmailTaskTest\r\n" + + "HELO " + local + "\r\n" + + "250 " + local + " Hello " + local + " [127.0.0.1], pleased to meet you\r\n" + + "MAIL FROM: \r\n" + + "250\r\n" + + "RCPT TO: \r\n" + + "250\r\n" + + "RCPT TO: \r\n" + + "250\r\n" + + "RCPT TO: \r\n" + + "250\r\n" + + "RCPT TO: \r\n" + + "250\r\n" + + "DATA\r\n" + + "354\r\n" + + "Subject: Test subject\r\n" + + "From: Mail Message \r\n" + + "To: to@you.com\r\n" + + "Cc: cc1@you.com, cc2@you.com\r\n" + + "X-Mailer: org.apache.tools.mail.MailMessage (ant.apache.org)\r\n" + + "\r\n" + + "test line 1\r\n" + + "test line 2\r\n" + + "\r\n" + + ".\r\n" + + "250\r\n" + + "QUIT\r\n" + + "221\r\n"; + for (int icounter = 0; icounterresult.length()) { + System.out.println("excedent of expected result " + + expectedResult.substring(result.length())); + } + if (expectedResult.length()"); + testMailClient.to("to@you.com"); + testMailClient.setSubject("Test subject"); + testMailClient.setMessage( "test line 1\n" + + "test line 2" ); + + Thread client = new Thread(testMailClient); + client.start(); + + try { + server.join(60 * 1000); // 60s + client.join(30 * 1000); // a further 30s + } catch (InterruptedException ie ) { + fail("InterruptedException: " + ie); + } + + String result = testMailServer.getResult(); + String expectedResult = "220 test SMTP EmailTaskTest\r\n" + + "HELO " + local + "\r\n" + + "250 " + local + " Hello " + local + " [127.0.0.1], pleased to meet you\r\n" + + "MAIL FROM: \r\n" + + "250\r\n" + + "RCPT TO: \r\n" + + "250\r\n" + + "DATA\r\n" + + "354\r\n" + + "Subject: Test subject\r\n" + + "From: Mail Message \r\n" + + "To: to@you.com\r\n" + + "X-Mailer: org.apache.tools.mail.MailMessage (ant.apache.org)\r\n" + + "\r\n" + + "test line 1\r\n" + + "test line 2\r\n" + + "\r\n" + + ".\r\n" + + "250\r\n" + + "QUIT\r\n" + + "221\r\n"; + assertEquals(expectedResult.length(), result.length()); + assertEquals(expectedResult, result); // order of headers cannot be guaranteed + if (testMailClient.isFailed()) { + fail(testMailClient.getFailMessage()); + } + } + + + /** + * Test a MailMessage with no to or bcc lines + */ + public void testCcOnly() { + ServerThread testMailServer = new ServerThread(); + Thread server = new Thread(testMailServer); + server.start(); + + ClientThread testMailClient = new ClientThread(); + + testMailClient.from("Mail Message "); + testMailClient.cc("cc@you.com"); + testMailClient.setSubject("Test subject"); + testMailClient.setMessage( "test line 1\n" + + "test line 2" ); + + Thread client = new Thread(testMailClient); + client.start(); + + try { + server.join(60 * 1000); // 60s + client.join(30 * 1000); // a further 30s + } catch (InterruptedException ie ) { + fail( "InterruptedException: " + ie ); + } + + String result = testMailServer.getResult(); + String expectedResult = "220 test SMTP EmailTaskTest\r\n" + + "HELO " + local + "\r\n" + + "250 " + local + " Hello " + local + " [127.0.0.1], pleased to meet you\r\n" + + "MAIL FROM: \r\n" + + "250\r\n" + + "RCPT TO: \r\n" + + "250\r\n" + + "DATA\r\n" + + "354\r\n" + + "Subject: Test subject\r\n" + + "From: Mail Message \r\n" + + "Cc: cc@you.com\r\n" + + "X-Mailer: org.apache.tools.mail.MailMessage (ant.apache.org)\r\n" + + "\r\n" + + "test line 1\r\n" + + "test line 2\r\n" + + "\r\n" + + ".\r\n" + + "250\r\n" + + "QUIT\r\n" + + "221\r\n"; + assertEquals(expectedResult.length(), result.length()); + assertEquals(expectedResult, result); + if (testMailClient.isFailed()) { + fail(testMailClient.getFailMessage()); + } + } + + + /** + * Test a MailMessage with no to or cc lines + */ + public void testBccOnly() { + ServerThread testMailServer = new ServerThread(); + Thread server = new Thread(testMailServer); + server.start(); + + ClientThread testMailClient = new ClientThread(); + + testMailClient.from("Mail Message "); + testMailClient.bcc("bcc@you.com"); + testMailClient.setSubject("Test subject"); + testMailClient.setMessage( "test line 1\n" + + "test line 2" ); + + Thread client = new Thread(testMailClient); + client.start(); + + try { + server.join(60 * 1000); // 60s + client.join(30 * 1000); // a further 30s + } catch (InterruptedException ie ) { + fail( "InterruptedException: " + ie ); + } + + String result = testMailServer.getResult(); + String expectedResult = "220 test SMTP EmailTaskTest\r\n" + + "HELO " + local + "\r\n" + + "250 " + local + " Hello " + local + " [127.0.0.1], pleased to meet you\r\n" + + "MAIL FROM: \r\n" + + "250\r\n" + + "RCPT TO: \r\n" + + "250\r\n" + + "DATA\r\n" + + "354\r\n" + + "Subject: Test subject\r\n" + + "From: Mail Message \r\n" + + "X-Mailer: org.apache.tools.mail.MailMessage (ant.apache.org)\r\n" + + "\r\n" + + "test line 1\r\n" + + "test line 2\r\n" + + "\r\n" + + ".\r\n" + + "250\r\n" + + "QUIT\r\n" + + "221\r\n"; + assertEquals( expectedResult.length(), result.length() ); + assertEquals( expectedResult, result ); + if ( testMailClient.isFailed() ) { + fail( testMailClient.getFailMessage() ); + } + } + + + /** + * Test a MailMessage with no subject line + * Subject is an optional field (RFC 822 s4.1) + */ + public void testNoSubject() { + ServerThread testMailServer = new ServerThread(); + Thread server = new Thread(testMailServer); + server.start(); + + ClientThread testMailClient = new ClientThread(); + + testMailClient.from("Mail Message "); + testMailClient.to("to@you.com"); + testMailClient.setMessage( "test line 1\n" + + "test line 2" ); + + Thread client = new Thread(testMailClient); + client.start(); + + try { + server.join(60 * 1000); // 60s + client.join(30 * 1000); // a further 30s + } catch (InterruptedException ie ) { + fail( "InterruptedException: " + ie ); + } + + String result = testMailServer.getResult(); + String expectedResult = "220 test SMTP EmailTaskTest\r\n" + + "HELO " + local + "\r\n" + + "250 " + local + " Hello " + local + " [127.0.0.1], pleased to meet you\r\n" + + "MAIL FROM: \r\n" + + "250\r\n" + + "RCPT TO: \r\n" + + "250\r\n" + + "DATA\r\n" + + "354\r\n" + + "From: Mail Message \r\n" + + "To: to@you.com\r\n" + + "X-Mailer: org.apache.tools.mail.MailMessage (ant.apache.org)\r\n" + + "\r\n" + + "test line 1\r\n" + + "test line 2\r\n" + + "\r\n" + + ".\r\n" + + "250\r\n" + + "QUIT\r\n" + + "221\r\n"; + assertEquals( expectedResult.length(), result.length() ); + assertEquals( expectedResult, result ); + if ( testMailClient.isFailed() ) { + fail( testMailClient.getFailMessage() ); + } + } + + + /** + * Test a MailMessage with empty body message + */ + public void testEmptyBody() { + ServerThread testMailServer = new ServerThread(); + Thread server = new Thread(testMailServer); + server.start(); + + ClientThread testMailClient = new ClientThread(); + + testMailClient.from("Mail Message "); + testMailClient.to("to@you.com"); + testMailClient.setSubject("Test subject"); + testMailClient.setMessage(""); + + Thread client = new Thread(testMailClient); + client.start(); + + try { + server.join(60 * 1000); // 60s + client.join(30 * 1000); // a further 30s + } catch (InterruptedException ie ) { + fail( "InterruptedException: " + ie ); + } + + String result = testMailServer.getResult(); + String expectedResult = "220 test SMTP EmailTaskTest\r\n" + + "HELO " + local + "\r\n" + + "250 " + local + " Hello " + local + " [127.0.0.1], pleased to meet you\r\n" + + "MAIL FROM: \r\n" + + "250\r\n" + + "RCPT TO: \r\n" + + "250\r\n" + + "DATA\r\n" + + "354\r\n" + + "Subject: Test subject\r\n" + + "From: Mail Message \r\n" + + "To: to@you.com\r\n" + + "X-Mailer: org.apache.tools.mail.MailMessage (ant.apache.org)\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + ".\r\n" + + "250\r\n" + + "QUIT\r\n" + + "221\r\n"; + assertEquals(expectedResult.length(), result.length()); + assertEquals(expectedResult, result); + if (testMailClient.isFailed()) { + fail(testMailClient.getFailMessage()); + } + } + + + /** + * Test a MailMessage with US-ASCII character set + * The next four testcase can be kinda hard to debug as Ant will often + * print the junit failure in US-ASCII. + */ + public void testAsciiCharset() { + + ServerThread testMailServer = new ServerThread(); + Thread server = new Thread(testMailServer); + server.start(); + + ClientThread testMailClient = new ClientThread(); + + testMailClient.from("Mail Message "); + testMailClient.to("Ceki G\u00fclc\u00fc "); + testMailClient.setSubject("Test subject"); + testMailClient.setMessage(""); + + Thread client = new Thread(testMailClient); + client.start(); + + try { + server.join(60 * 1000); // 60s + client.join(30 * 1000); // a further 30s + } catch (InterruptedException ie ) { + fail("InterruptedException: " + ie); + } + + String result = testMailServer.getResult(); + String expectedResult = "220 test SMTP EmailTaskTest\r\n" + + "HELO " + local + "\r\n" + + "250 " + local + " Hello " + local + " [127.0.0.1], pleased to meet you\r\n" + + "MAIL FROM: \r\n" + + "250\r\n" + + "RCPT TO: \r\n" + + "250\r\n" + + "DATA\r\n" + + "354\r\n" + + "Subject: Test subject\r\n" + + "From: Mail Message \r\n" + + "To: Ceki G\u00fclc\u00fc \r\n" + + "X-Mailer: org.apache.tools.mail.MailMessage (ant.apache.org)\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + ".\r\n" + + "250\r\n" + + "QUIT\r\n" + + "221\r\n"; + ByteArrayOutputStream baos1 = new ByteArrayOutputStream(); + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + PrintStream bos1 = new PrintStream(baos1, true); + PrintStream bos2 = new PrintStream(baos2, true); + + bos1.print(expectedResult); + bos2.print(result); + + assertEquals( "expected message length != actual message length " + + "in testAsciiCharset()", expectedResult.length(), result.length() ); + assertEquals( "baos1 and baos2 should be the same in testAsciiCharset()", + baos1.toString(), baos2.toString() ); // order of headers cannot be guaranteed + if (testMailClient.isFailed()) { + fail(testMailClient.getFailMessage()); + } + } + + + + + /** + * A private test class that pretends to be a mail transfer agent + */ + private class ServerThread implements Runnable { + + private StringBuffer sb = null; + private boolean loop = false; + ServerSocket ssock = null; + Socket sock = null; + BufferedWriter out = null; + BufferedReader in = null; + private boolean data = false; // state engine: false=envelope, true=message + + public void run() { + + try { + ssock = new ServerSocket(TEST_PORT); + sock = ssock.accept(); // wait for connection + in = new BufferedReader( new InputStreamReader( + sock.getInputStream()) ); + out = new BufferedWriter( new OutputStreamWriter( + sock.getOutputStream() ) ); + sb = new StringBuffer(); + send( "220 test SMTP EmailTaskTest\r\n" ); + loop = true; + while ( loop ) { + String response = in.readLine(); + if ( response == null ) { + loop = false; + break; + } + sb.append( response + "\r\n" ); + + if ( !data && response.startsWith( "HELO" ) ) { + send( "250 " + local + " Hello " + local + " " + + "[127.0.0.1], pleased to meet you\r\n" ); + } else if ( !data && response.startsWith("MAIL") ) { + send( "250\r\n" ); + } else if ( !data && response.startsWith("RCPT")) { + send( "250\r\n" ); + } else if (!data && response.startsWith("DATA")) { + send( "354\r\n" ); + data = true; + } else if (data && response.equals(".") ) { + send( "250\r\n" ); + data = false; + } else if (!data && response.startsWith("QUIT")) { + send( "221\r\n" ); + loop = false; + } else if (!data) { + //throw new IllegalStateException("Command unrecognized: " + // + response); + send( "500 5.5.1 Command unrecognized: \"" + + response + "\"\r\n" ); + loop = false; + } else { + // sb.append( response + "\r\n" ); + } + + } // while + } catch (IOException ioe) { + fail(); + } finally { + disconnect(); + } + } + + private void send(String retmsg) throws IOException { + out.write( retmsg ); + out.flush(); + sb.append( retmsg ); + } + + private void disconnect() { + if (out != null) { + try { + out.flush(); + out.close(); + out = null; + } catch (IOException e) { + // ignore + } + } + if (in != null) { + try { + in.close(); + in = null; + } catch (IOException e) { + // ignore + } + } + if (sock != null) { + try { + sock.close(); + sock = null; + } catch (IOException e) { + // ignore + } + } + if (ssock != null) { + try { + ssock.close(); + ssock = null; + } catch (IOException e) { + // ignore + } + } + } + + public synchronized String getResult() { + loop = false; + return sb.toString(); + } + + } + + /** + * A private test class that wraps MailMessage + */ + private class ClientThread implements Runnable { + + private MailMessage msg; + private boolean fail = false; + private String failMessage = null; + + protected String from = null; + protected String subject = null; + protected String message = null; + + protected Vector replyToList = new Vector(); + protected Vector toList = new Vector(); + protected Vector ccList = new Vector(); + protected Vector bccList = new Vector(); + + + public void run() { + for (int i = 9; i > 0; i--) { + try { + msg = new MailMessage("localhost", TEST_PORT); + } catch (java.net.ConnectException ce) { + try { + Thread.sleep(10 * 1000); + } catch (InterruptedException ie) { + // ignore + } + } catch (IOException ioe) { + fail = true; + failMessage = "IOException: " + ioe; + return; + } + if (msg != null) { + break; + } + } + + if (msg == null) { + fail = true; + failMessage = "java.net.ConnectException: Connection refused"; + return; + } + + try { + msg.from(from); + + Enumeration e; + + e = replyToList.elements(); + while (e.hasMoreElements()) { + msg.replyto(e.nextElement().toString()); + } + + e = toList.elements(); + while (e.hasMoreElements()) { + msg.to(e.nextElement().toString()); + } + + e = ccList.elements(); + while (e.hasMoreElements()) { + msg.cc(e.nextElement().toString()); + } + + e = bccList.elements(); + while (e.hasMoreElements()) { + msg.bcc(e.nextElement().toString()); + } + + if (subject != null) { + msg.setSubject(subject); + } + + if (message != null ) { + PrintStream out = msg.getPrintStream(); + out.println( message ); + } + + msg.sendAndClose(); + } catch (IOException ioe) { + fail = true; + failMessage = "IOException: " + ioe; + return; + } + } + + public boolean isFailed() { + return fail; + } + + public String getFailMessage() { + return failMessage; + } + + public void replyTo(String replyTo) { + replyToList.add(replyTo); + } + + public void to(String to) { + toList.add(to); + } + + public void cc(String cc) { + ccList.add(cc); + } + + public void bcc(String bcc) { + bccList.add(bcc); + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public void from(String from) { + this.from = from; + } + + public void setMessage(String message) { + this.message = message; + } + + } + +}