Java tutorial
/* * SMTPSessionTest.java * This file is part of Freemail * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.freenetproject.freemail.smtp; import static org.junit.Assert.*; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.bouncycastle.util.encoders.Base64; import org.freenetproject.freemail.AccountManager; import org.freenetproject.freemail.FreemailAccount; import org.freenetproject.freemail.NullFreemailAccount; import org.freenetproject.freemail.transport.MessageHandler; import org.freenetproject.freemail.wot.Identity; import org.freenetproject.freemail.wot.IdentityMatcher; import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import data.TestId1Data; import utils.TextProtocolTester; import utils.TextProtocolTester.Command; import utils.UnitTestParameters; import utils.Utils; import fakes.FakeSocket; import fakes.NullIdentity; import fakes.NullMessageHandler; import freenet.pluginmanager.PluginNotFoundException; import freenet.support.api.Bucket; import freenet.support.io.BucketTools; public class SMTPSessionTest { private static final String PASSWORD = "oaJ4Aa1b"; /* * Directory structure: * smtptest/ * account_manager_dir/ * account_dir/ * channels/ */ private final File testDir = new File("smtptest"); private final File accountManagerDir = new File(testDir, "account_manager_dir"); private final File accountDir = new File(accountManagerDir, "account_dir"); private final File channelDir = new File(accountDir, "channels"); @Before public void before() { Utils.createDir(testDir); Utils.createDir(accountManagerDir); Utils.createDir(accountDir); Utils.createDir(channelDir); } @After public void after() { Utils.delete(testDir); } /** * Runs through a full session sending a simple message to one recipient. * * @throws IOException on IO errors with SMTP thread, should never happen */ @Test public void simpleSession() throws IOException { Assume.assumeTrue(UnitTestParameters.EXTENSIVE); final String message = "Date: Thu, 21 May 1998 05:33:29 -0700\r\n" + "From: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "Subject: Freemail SMTP test\r\n" + "To: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "\r\n" + "This is a simple SMTP test for Freemail\r\n"; String authData = new String( Base64.encode(("\0" + TestId1Data.Identity.ID + "\0" + PASSWORD).getBytes("ASCII")), "ASCII"); List<Command> commands = new LinkedList<Command>(); commands.add(new Command(null, "220 localhost ready")); commands.add(new Command("EHLO", "250-localhost", "250 AUTH LOGIN PLAIN")); commands.add(new Command("AUTH PLAIN " + authData, "235 Authenticated")); commands.add(new Command("MAIL FROM:<" + TestId1Data.FreemailAccount.ADDRESS + ">", "250 OK")); commands.add(new Command("RCPT TO:<" + TestId1Data.FreemailAccount.ADDRESS + ">", "250 OK")); commands.add(new Command("DATA", "354 Go crazy")); commands.add(new Command(message + ".\r\n", "250 So be it")); runSimpleSessionTest(commands, true, Collections.singletonList(message), Collections.singletonList(TestId1Data.FreemailAccount.ADDRESS)); } /** * Runs through a full session sending a simple message to one recipient where MessageHandler * returns a failure. * * @throws IOException on IO errors with SMTP thread, should never happen */ @Test public void simpleSessionSendFails() throws IOException { Assume.assumeTrue(UnitTestParameters.EXTENSIVE); final String message = "Date: Thu, 21 May 1998 05:33:29 -0700\r\n" + "From: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "Subject: Freemail SMTP test\r\n" + "To: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "\r\n" + "This is a simple SMTP test for Freemail\r\n"; String authData = new String( Base64.encode(("\0" + TestId1Data.Identity.ID + "\0" + PASSWORD).getBytes("ASCII")), "ASCII"); List<Command> commands = new LinkedList<Command>(); commands.add(new Command(null, "220 localhost ready")); commands.add(new Command("EHLO", "250-localhost", "250 AUTH LOGIN PLAIN")); commands.add(new Command("AUTH PLAIN " + authData, "235 Authenticated")); commands.add(new Command("MAIL FROM:<" + TestId1Data.FreemailAccount.ADDRESS + ">", "250 OK")); commands.add(new Command("RCPT TO:<" + TestId1Data.FreemailAccount.ADDRESS + ">", "250 OK")); commands.add(new Command("DATA", "354 Go crazy")); commands.add(new Command(message + ".\r\n", "452 Message sending failed")); runSimpleSessionTest(commands, false, Collections.singletonList(message), Collections.singletonList(TestId1Data.FreemailAccount.ADDRESS)); } /** * Tests that the correct message is handed to the MessageHandler when the message contains * dot padding (see <a href="https://tools.ietf.org/html/rfc5321#section-4.5.2">RFC5321 section 4.5.2</a>). * * @throws IOException on IO errors with SMTP thread, should never happen */ @Test public void messageWithDotPadding() throws IOException { Assume.assumeTrue(UnitTestParameters.EXTENSIVE); final String message = "Date: Thu, 21 May 1998 05:33:29 -0700\r\n" + "From: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "Subject: Freemail SMTP test\r\n" + "To: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "\r\n" + "This is a simple SMTP test for Freemail\r\n" + ".\r\n"; final String padded = "Date: Thu, 21 May 1998 05:33:29 -0700\r\n" + "From: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "Subject: Freemail SMTP test\r\n" + "To: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "\r\n" + "This is a simple SMTP test for Freemail\r\n" + "..\r\n"; String authData = new String( Base64.encode(("\0" + TestId1Data.Identity.ID + "\0" + PASSWORD).getBytes("ASCII")), "ASCII"); List<Command> commands = new LinkedList<Command>(); commands.add(new Command(null, "220 localhost ready")); commands.add(new Command("EHLO", "250-localhost", "250 AUTH LOGIN PLAIN")); commands.add(new Command("AUTH PLAIN " + authData, "235 Authenticated")); commands.add(new Command("MAIL FROM:<" + TestId1Data.FreemailAccount.ADDRESS + ">", "250 OK")); commands.add(new Command("RCPT TO:<" + TestId1Data.FreemailAccount.ADDRESS + ">", "250 OK")); commands.add(new Command("DATA", "354 Go crazy")); commands.add(new Command(padded + ".\r\n", "250 So be it")); runSimpleSessionTest(commands, true, Collections.singletonList(message), Collections.singletonList(TestId1Data.FreemailAccount.ADDRESS)); } /** * Sends two messages in the same session to test that the state is reset properly. This checks * for bugs like the one fixed in 76444b878e where the state isn't cleared properly before the * second mail transaction starts. * * @throws IOException on IO errors with SMTP thread, should never happen */ @Test public void twoMessagesInOneSession() throws IOException { Assume.assumeTrue(UnitTestParameters.EXTENSIVE); final String message1 = "Date: Thu, 21 May 1998 05:33:29 -0700\r\n" + "From: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "Subject: Freemail SMTP test\r\n" + "To: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "\r\n" + "This is test message 1.\r\n"; final String message2 = "Date: Thu, 21 May 1998 05:33:29 -0700\r\n" + "From: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "Subject: Freemail SMTP test\r\n" + "To: " + TestId1Data.FreemailAccount.ADDRESS_WITH_ANGLE + "\r\n" + "\r\n" + "This is test message 2.\r\n"; String authData = new String( Base64.encode(("\0" + TestId1Data.Identity.ID + "\0" + PASSWORD).getBytes("ASCII")), "ASCII"); List<Command> commands = new LinkedList<Command>(); commands.add(new Command(null, "220 localhost ready")); commands.add(new Command("EHLO", "250-localhost", "250 AUTH LOGIN PLAIN")); commands.add(new Command("AUTH PLAIN " + authData, "235 Authenticated")); //Message 1 commands.add(new Command("MAIL FROM:<" + TestId1Data.FreemailAccount.ADDRESS + ">", "250 OK")); commands.add(new Command("RCPT TO:<" + TestId1Data.FreemailAccount.ADDRESS + ">", "250 OK")); commands.add(new Command("DATA", "354 Go crazy")); commands.add(new Command(message1 + ".\r\n", "250 So be it")); //Message 2 commands.add(new Command("MAIL FROM:<" + TestId1Data.FreemailAccount.ADDRESS + ">", "250 OK")); commands.add(new Command("RCPT TO:<" + TestId1Data.FreemailAccount.ADDRESS + ">", "250 OK")); commands.add(new Command("DATA", "354 Go crazy")); commands.add(new Command(message2 + ".\r\n", "250 So be it")); List<String> messages = new ArrayList<String>(2); messages.add(message1); messages.add(message2); List<String> recipients = new ArrayList<String>(2); recipients.add(TestId1Data.FreemailAccount.ADDRESS); recipients.add(TestId1Data.FreemailAccount.ADDRESS); runSimpleSessionTest(commands, true, messages, recipients); } /** * Sets up the SMTP server and supporting mocks and runs through the commands and the expected * responses, then quit. The list of recipients can only contain one recipient per message. * * @param commands the commands to be sent and their responses * @param sendResult the result that will be returned from MessageHandler.sendMessage() * @param messages a list of the messages that the MessageHandler should receive * @param rcpts a list of the recipients that are expected * @throws IOException on IO errors with SMTP thread, should never happen */ public void runSimpleSessionTest(List<Command> commands, final boolean sendResult, final List<String> messages, final List<String> rcpts) throws IOException { assertEquals(messages.size(), rcpts.size()); final Identity recipient = new NullIdentity(TestId1Data.Identity.ID, null, null) { @Override public boolean equals(Object o) { return o == this; } @Override public int hashCode() { throw new UnsupportedOperationException(); } }; final MessageHandler messageHandler = new NullMessageHandler(null, null, channelDir, null, null) { private int offset = 0; @Override public boolean sendMessage(List<Identity> recipients, Bucket msg) throws IOException { assertEquals(1, recipients.size()); assertEquals(recipient, recipients.get(0)); assertEquals(messages.get(offset++), new String(BucketTools.toByteArray(msg), "ASCII")); return sendResult; } }; final FreemailAccount account = new NullFreemailAccount(TestId1Data.Identity.ID, null, null, null) { @Override public MessageHandler getMessageHandler() { return messageHandler; } @Override public File getAccountDir() { return accountDir; } @Override public String getIdentity() { return TestId1Data.Identity.ID; } }; AccountManager accManager = new AccountManager(accountManagerDir, null) { @Override public FreemailAccount authenticate(String username, String password) { if (username.equals(TestId1Data.Identity.ID) && password.equals(PASSWORD)) { return account; } return null; } }; IdentityMatcher matcher = new IdentityMatcher(null) { private int offset = 0; @Override public Map<String, List<Identity>> matchIdentities(Set<String> recipients, String wotOwnIdentity, EnumSet<MatchMethod> methods) throws PluginNotFoundException { String curRcpt = rcpts.get(offset++); assertEquals(1, recipients.size()); assertEquals(curRcpt, recipients.iterator().next()); assertEquals(TestId1Data.Identity.ID, wotOwnIdentity); Map<String, List<Identity>> result = new HashMap<String, List<Identity>>(); List<Identity> ids = new LinkedList<Identity>(); ids.add(recipient); result.put(curRcpt, ids); return result; } }; FakeSocket sock = new FakeSocket(); SMTPHandler handler = new SMTPHandler(accManager, sock, matcher); Thread smtpThread = new Thread(handler); smtpThread.start(); PrintWriter toHandler = new PrintWriter(sock.getOutputStreamOtherSide()); BufferedReader fromHandler = new BufferedReader(new InputStreamReader(sock.getInputStreamOtherSide())); TextProtocolTester tester = new TextProtocolTester(toHandler, fromHandler); tester.runProtocolTest(commands); //QUIT toHandler.write("QUIT\r\n"); toHandler.flush(); //Accept null or exception here since the socket might have closed before we read try { String line = fromHandler.readLine(); if (line != null && !line.equals("221 localhost")) { fail("Expected final line to be 221 localhost, but was " + line); } } catch (IOException e) { assertEquals("Pipe closed", e.getMessage()); } handler.kill(); sock.close(); try { smtpThread.join(); } catch (InterruptedException e) { fail("Caught unexpected InterruptedException"); } } }