├── .gitignore ├── lib ├── mail.jar ├── junit-4.8.2.jar └── log4j-1.2.9.jar ├── version.txt ├── .idea ├── copyright │ └── profiles_settings.xml ├── ant.xml ├── encodings.xml ├── vcs.xml ├── modules.xml ├── libraries │ └── lib.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── compiler.xml ├── misc.xml ├── uiDesigner.xml └── workspace.xml ├── test-src └── com │ └── dumbster │ └── smtp │ ├── StringUtil.java │ ├── SmtpStateTest.java │ ├── RollingMailStoreTest.java │ ├── eml │ ├── EMLMailMessageTest.java │ └── EMLMailStoreTest.java │ ├── ServerOptionsTest.java │ ├── MailMessageTest.java │ ├── SmtpServerTest.java │ └── RequestTest.java ├── src └── com │ └── dumbster │ └── smtp │ ├── action │ ├── Action.java │ ├── NoOp.java │ ├── Rset.java │ ├── Expn.java │ ├── Vrfy.java │ ├── Help.java │ ├── Quit.java │ ├── Ehlo.java │ ├── Rcpt.java │ ├── DataEnd.java │ ├── Mail.java │ ├── Connect.java │ ├── Unrecognized.java │ ├── Data.java │ ├── BlankLine.java │ └── List.java │ ├── MailStore.java │ ├── IOSource.java │ ├── SocketWrapper.java │ ├── SmtpState.java │ ├── mailstores │ ├── RollingMailStore.java │ └── EMLMailStore.java │ ├── ServerOptions.java │ ├── Main.java │ ├── SmtpServerFactory.java │ ├── Response.java │ ├── MailMessage.java │ ├── eml │ └── EMLMailMessage.java │ ├── MailMessageImpl.java │ ├── ClientSession.java │ ├── Request.java │ └── SmtpServer.java ├── .classpath ├── Dumbster.iml ├── .project ├── README.txt ├── smtp-states.txt ├── pom.xml └── license.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | bin 3 | build 4 | doc 5 | *~ 6 | target 7 | -------------------------------------------------------------------------------- /lib/mail.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjo1970/dumbster/HEAD/lib/mail.jar -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjo1970/dumbster/HEAD/version.txt -------------------------------------------------------------------------------- /lib/junit-4.8.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjo1970/dumbster/HEAD/lib/junit-4.8.2.jar -------------------------------------------------------------------------------- /lib/log4j-1.2.9.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rjo1970/dumbster/HEAD/lib/log4j-1.2.9.jar -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/ant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test-src/com/dumbster/smtp/StringUtil.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | public class StringUtil { 4 | public static String longString(int size) { 5 | StringBuffer b = new StringBuffer(); 6 | for(int i=0; i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/IOSource.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.PrintWriter; 6 | 7 | public interface IOSource { 8 | 9 | public BufferedReader getInputStream() throws IOException; 10 | 11 | public PrintWriter getOutputStream() throws IOException; 12 | 13 | public void close() throws IOException; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/NoOp.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class NoOp implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "NOOP"; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | return new Response(250, "OK", smtpState); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Rset.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Rset implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "RSET"; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | return new Response(250, "OK", SmtpState.GREET); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Expn.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Expn implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "EXPN"; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | return new Response(252, "Not supported", smtpState); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Vrfy.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Vrfy implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "VRFY"; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | return new Response(252, "Not supported", smtpState); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Help.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Help implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "HELP"; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | return new Response(211, "No help available", smtpState); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Dumbster.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | dumbster 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | 19 | classes 20 | 2 21 | PROJECT_LOC/build/classes 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Quit.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Quit implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "QUIT"; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | return new Response(221, "localhost Dumbster service closing transmission channel", 17 | SmtpState.CONNECT); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Ehlo.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Ehlo implements Action { 9 | 10 | public String toString() { 11 | return "EHLO"; 12 | } 13 | 14 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 15 | if (SmtpState.GREET == smtpState) { 16 | return new Response(250, "OK", SmtpState.MAIL); 17 | } else { 18 | return new Response(503, "Bad sequence of commands: " 19 | + this, smtpState); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Rcpt.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Rcpt implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "RCPT"; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | if (SmtpState.RCPT == smtpState) { 17 | return new Response(250, "OK", smtpState); 18 | } else { 19 | return new Response(503, 20 | "Bad sequence of commands: " + this, smtpState); 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/DataEnd.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class DataEnd implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "."; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | if (SmtpState.DATA_HDR == smtpState || SmtpState.DATA_BODY == smtpState) { 17 | return new Response(250, "OK", SmtpState.QUIT); 18 | } else { 19 | return new Response(503, "Bad sequence of commands: " + this, smtpState); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Mail.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Mail implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "MAIL"; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | if (SmtpState.MAIL == smtpState || SmtpState.QUIT == smtpState) { 17 | return new Response(250, "OK", SmtpState.RCPT); 18 | } else { 19 | return new Response(503, 20 | "Bad sequence of commands: " + this, smtpState); 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Connect.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Connect implements Action { 9 | 10 | public String toString() { 11 | return "Connect"; 12 | } 13 | 14 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 15 | if (SmtpState.CONNECT == smtpState) { 16 | return new Response(220, 17 | "localhost Dumbster SMTP service ready", SmtpState.GREET); 18 | } else { 19 | return new Response(503, "Bad sequence of commands: " + this, 20 | smtpState); 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Unrecognized.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Unrecognized implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "Unrecognized command / data"; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | if (SmtpState.DATA_HDR == smtpState || SmtpState.DATA_BODY == smtpState) { 17 | return new Response(-1, "", smtpState); 18 | } else { 19 | return new Response(500, "Command not recognized", 20 | smtpState); 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/Data.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class Data implements Action { 9 | 10 | @Override 11 | public String toString() { 12 | return "DATA"; 13 | } 14 | 15 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 16 | if (SmtpState.RCPT == smtpState) { 17 | return new Response(354, 18 | "Start mail input; end with .", 19 | SmtpState.DATA_HDR); 20 | } else { 21 | return new Response(503, 22 | "Bad sequence of commands: " + this, smtpState); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/BlankLine.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | public class BlankLine implements Action { 9 | 10 | 11 | @Override 12 | public String toString() { 13 | return "Blank line"; 14 | } 15 | 16 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 17 | if (SmtpState.DATA_HDR == smtpState) { 18 | return new Response(-1, "", SmtpState.DATA_BODY); 19 | } else if (SmtpState.DATA_BODY == smtpState) { 20 | return new Response(-1, "", smtpState); 21 | } else { 22 | return new Response(503, 23 | "Bad sequence of commands: " + this, smtpState); 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/SocketWrapper.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.io.PrintWriter; 7 | import java.net.Socket; 8 | 9 | public class SocketWrapper implements IOSource { 10 | private Socket socket; 11 | 12 | public SocketWrapper(Socket socket) throws IOException { 13 | this.socket = socket; 14 | this.socket.setSoTimeout(10000); // protects against hanged clients 15 | } 16 | 17 | public BufferedReader getInputStream() throws IOException { 18 | return new BufferedReader( 19 | new InputStreamReader(socket.getInputStream())); 20 | } 21 | 22 | public PrintWriter getOutputStream() throws IOException { 23 | return new PrintWriter(socket.getOutputStream()); 24 | } 25 | 26 | public void close() throws IOException { 27 | socket.close(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/SmtpState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Dumbster - a dummy SMTP server 3 | * Copyright 2004 Jason Paul Kitchen 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.dumbster.smtp; 18 | 19 | public enum SmtpState { 20 | CONNECT("CONNECT"), GREET("GREET"), MAIL("MAIL"), RCPT("RCPT"), DATA_HDR( 21 | "DATA_HDR"), DATA_BODY("DATA_BODY"), QUIT("QUIT"); 22 | 23 | private String description; 24 | 25 | SmtpState(String description) { 26 | this.description = description; 27 | } 28 | 29 | public String toString() { 30 | return this.description; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/mailstores/RollingMailStore.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.mailstores; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class RollingMailStore implements MailStore { 11 | 12 | private List receivedMail; 13 | 14 | public RollingMailStore() { 15 | receivedMail = Collections.synchronizedList(new ArrayList()); 16 | } 17 | 18 | public int getEmailCount() { 19 | return receivedMail.size(); 20 | } 21 | 22 | public void addMessage(MailMessage message) { 23 | System.out.println("\n\nReceived message:\n" + message); 24 | receivedMail.add(message); 25 | if (getEmailCount() > 100) { 26 | receivedMail.remove(0); 27 | } 28 | } 29 | 30 | public MailMessage[] getMessages() { 31 | return receivedMail.toArray(new MailMessage[receivedMail.size()]); 32 | } 33 | 34 | public MailMessage getMessage(int index) { 35 | return receivedMail.get(index); 36 | } 37 | 38 | @Override 39 | public void clearMessages() { 40 | this.receivedMail.clear(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/action/List.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.action; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailStore; 5 | import com.dumbster.smtp.Response; 6 | import com.dumbster.smtp.SmtpState; 7 | 8 | 9 | public class List implements Action { 10 | 11 | private Integer messageIndex = null; 12 | 13 | public List(String params) { 14 | try { 15 | Integer messageIndex = Integer.valueOf(params); 16 | if (messageIndex > -1) 17 | this.messageIndex = messageIndex; 18 | } catch (NumberFormatException ignored) { 19 | } 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return "LIST"; 25 | } 26 | 27 | public Response response(SmtpState smtpState, MailStore mailStore, MailMessage currentMessage) { 28 | 29 | StringBuffer result = new StringBuffer(); 30 | if (messageIndex != null && messageIndex < mailStore.getEmailCount()) { 31 | result.append("\n-------------------------------------------\n"); 32 | result.append(mailStore.getMessage(messageIndex).toString()); 33 | } 34 | result.append("There are "); 35 | result.append(mailStore.getEmailCount()); 36 | result.append(" message(s)."); 37 | return new Response(250, result.toString(), SmtpState.GREET); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/ServerOptions.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | import com.dumbster.smtp.mailstores.RollingMailStore; 4 | 5 | /** 6 | * User: rj 7 | * Date: 7/18/13 8 | * Time: 5:35 AM 9 | */ 10 | public class ServerOptions { 11 | public int port = SmtpServer.DEFAULT_SMTP_PORT; 12 | public boolean threaded = true; 13 | public MailStore mailStore = new RollingMailStore(); 14 | public boolean valid = true; 15 | 16 | public ServerOptions() { 17 | } 18 | 19 | public ServerOptions(String[] args) { 20 | if (args.length == 0) { 21 | return; 22 | } 23 | 24 | for (String argument : args) { 25 | if (argument.startsWith("--mailStore")) { 26 | String[] values = argument.split("="); 27 | if (values.length != 2) { 28 | this.valid = false; 29 | return; 30 | } 31 | try { 32 | this.mailStore = (MailStore) Class.forName("com.dumbster.smtp.mailstores."+values[1]).newInstance(); 33 | } catch (Exception e) { 34 | this.valid = false; 35 | return; 36 | } 37 | } else if (argument.startsWith("--threaded")) { 38 | this.threaded = !argument.equalsIgnoreCase("--threaded=false"); 39 | } else { 40 | try { 41 | this.port = Integer.parseInt(argument); 42 | } catch (NumberFormatException e) { 43 | this.valid = false; 44 | break; 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/Main.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | public class Main { 4 | 5 | public static void main(String[] args) { 6 | ServerOptions serverOptions = new ServerOptions(args); 7 | if (shouldShowHelp(args) || serverOptions.valid == false) { 8 | showHelp(); 9 | System.exit(1); 10 | } 11 | 12 | SmtpServerFactory.startServer(serverOptions); 13 | } 14 | 15 | private static boolean shouldShowHelp(String[] args) { 16 | if (args.length == 0) 17 | return false; 18 | for (String arg : args) { 19 | if ("--help".equalsIgnoreCase(arg) || "-h".equalsIgnoreCase(arg)) 20 | return true; 21 | } 22 | return false; 23 | } 24 | 25 | private static void showHelp() { 26 | System.out.println(); 27 | System.out.println("Dumbster Fake SMTP Server"); 28 | System.out.println("usage: java -jar dumbster.jar [options] [port]"); 29 | System.out.println("Starts the SMTP server in the given port. Default port is 25."); 30 | System.out.println(); 31 | System.out.println("Options:"); 32 | System.out.println("\t-h, --help this message"); 33 | System.out.println("\t--mailStore=EMLMailMessage Use a file-based mail store"); 34 | System.out.println("MailStores:"); 35 | System.out.println("\tRollingMailStore (Default) Store messages in memory. Only the last 100 messages will be kept in memory"); 36 | System.out.println("\tEMLMailStore Save messages in EML files"); 37 | System.out.println(); 38 | System.out.println("\t--threaded=false Forces the SMTP server to be single-threaded."); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Dumbster fake SMTP Server 2 | Forked from http://quintanasoft.com/dumbster/ version 1.6 by Jason Kitchen 3 | 4 | * Works as a single-threaded unit testing SMTP target 5 | * Works as a multi-threaded unit testing SMTP target 6 | * API change- returns an Array of messages rather than an Iterator 7 | * API change- RollingMailStore implements MailStore keeps rolling 100 msgs. 8 | * API change- EMLMailStore persists mail to files 9 | * API change- SmtpServer you can inject your own MailStore 10 | implementation. 11 | * API change- SmtpServer configured via ServerOptions 12 | 13 | * Now works stand-alone as an executable JAR 14 | * Improved test coverage 15 | * telnet to smtp server and use "list" command to view number of msgs 16 | * use list command with an index 0..(size-1) of messages to view a message 17 | 18 | EXAMPLE (SMTP unit testing fake) 19 | public class SmtpServerTest extends TestCase { 20 | ... 21 | public void testSend() { 22 | SmtpServer server = SmtpServerFactory.startServer(); 23 | 24 | try { 25 | // Submits an email using javamail to the email server listening on 26 | // port 25 27 | // (method not shown here). Replace this with a call to your app 28 | // logic. 29 | sendMessage(25, "sender@here.com", "Test", "Test Body", 30 | "receiver@there.com"); 31 | } catch(Exception e) { 32 | e.printStackTrace(); 33 | fail("Unexpected exception: "+e); 34 | } 35 | 36 | server.stop(); 37 | 38 | assertTrue(server.getReceivedEmailSize() == 1); 39 | MailMessage message = server.getMessage(0); 40 | assertEquals("Test", email.getHeaderValue("Subject")); 41 | assertEquals("Test Body", email.getBody()); 42 | } 43 | ... 44 | } 45 | 46 | EXAMPLE (SMTP fake server for QA, running on port 4444) 47 | java -jar dumbster.jar 4444 48 | 49 | For more help use the command: 50 | java -jar dumbster.jar --help 51 | 52 | 53 | LICENSE 54 | ======= 55 | Under Apache 2.0 license. 56 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/SmtpServerFactory.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | /** 4 | * User: rj 5 | * Date: Aug 28, 2011 6 | * Time: 6:48:14 AM 7 | */ 8 | public class SmtpServerFactory { 9 | public static SmtpServer startServer() { 10 | ServerOptions serverOptions = new ServerOptions(); 11 | return startServer(serverOptions); 12 | } 13 | 14 | public static SmtpServer startServer(ServerOptions options) { 15 | SmtpServer server = wireUpServer(options); 16 | wrapInShutdownHook(server); 17 | startServerThread(server); 18 | System.out.println("Dumbster SMTP Server started on port " + options.port + ".\n"); 19 | return server; 20 | } 21 | 22 | private static SmtpServer wireUpServer(ServerOptions options) { 23 | SmtpServer server = new SmtpServer(); 24 | server.setPort(options.port); 25 | server.setThreaded(options.threaded); 26 | server.setMailStore(options.mailStore); 27 | return server; 28 | } 29 | 30 | private static void wrapInShutdownHook(final SmtpServer server) { 31 | Runtime.getRuntime().addShutdownHook(new Thread() { 32 | public void run() { 33 | server.stop(); 34 | System.out.println("\nDumbster SMTP Server stopped"); 35 | System.out.println("\tTotal messages received: " + server.getEmailCount()); 36 | } 37 | }); 38 | } 39 | 40 | private static void startServerThread(SmtpServer server) { 41 | new Thread(server).start(); 42 | int timeout=1000; 43 | while(! server.isReady()) { 44 | try { 45 | Thread.sleep(1); 46 | timeout--; 47 | if (timeout < 1) { 48 | throw new RuntimeException("Server could not be started."); 49 | } 50 | } catch (InterruptedException ignored) { 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/Response.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Dumbster - a dummy SMTP server 3 | * Copyright 2004 Jason Paul Kitchen 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.dumbster.smtp; 18 | 19 | /** 20 | * SMTP response container. 21 | */ 22 | public class Response { 23 | /** 24 | * Response code - see RFC-2821. 25 | */ 26 | private int code; 27 | /** 28 | * Response message. 29 | */ 30 | private String message; 31 | /** 32 | * New state of the SMTP server once the request has been executed. 33 | */ 34 | private SmtpState nextState; 35 | 36 | /** 37 | * Constructor. 38 | * 39 | * @param code 40 | * response code 41 | * @param message 42 | * response message 43 | * @param next 44 | * next state of the SMTP server 45 | */ 46 | public Response(int code, String message, SmtpState next) { 47 | this.code = code; 48 | this.message = message; 49 | this.nextState = next; 50 | } 51 | 52 | /** 53 | * Get the response code. 54 | * 55 | * @return response code 56 | */ 57 | public int getCode() { 58 | return code; 59 | } 60 | 61 | /** 62 | * Get the response message. 63 | * 64 | * @return response message 65 | */ 66 | public String getMessage() { 67 | return message; 68 | } 69 | 70 | /** 71 | * Get the next SMTP server state. 72 | * 73 | * @return state 74 | */ 75 | public SmtpState getNextState() { 76 | return nextState; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/MailMessage.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | import java.util.Iterator; 4 | 5 | public interface MailMessage { 6 | 7 | /** 8 | * Gets an iterator over header names. 9 | * @return {@code Iterator} 10 | */ 11 | Iterator getHeaderNames(); 12 | 13 | /** 14 | * Gets the values of a given header. 15 | * 16 | * @param name 17 | * the name of the header. 18 | * @return the list of values associated with this header. 19 | */ 20 | String[] getHeaderValues(String name); 21 | 22 | /** 23 | * A shortcut to get only the first value of a header 24 | * 25 | * @param name 26 | * is name of the header 27 | * @return the first value of the header. 28 | */ 29 | String getFirstHeaderValue(String name); 30 | 31 | /** 32 | * Returns the body of the message. 33 | * @return the body of the message. 34 | */ 35 | String getBody(); 36 | 37 | /** 38 | * Adds a header to the message 39 | * 40 | * @param name 41 | * is the name of the header. 42 | * @param value 43 | * is the value to add to the header. 44 | */ 45 | void addHeader(String name, String value); 46 | 47 | /** 48 | * Append some text to the last existing value of a header. 49 | * It differs from {@code addHeader} method because it doesn't add a new header entry, 50 | * instead it appends the given value to an existing header entry. 51 | * If the given header name doesn't exist this method will add a new header. 52 | * 53 | * @param name 54 | * is the name of the header 55 | * @param value 56 | * is the value to append to the header. 57 | */ 58 | void appendHeader(String name, String value); 59 | 60 | /** 61 | * Appends the given text to the body. 62 | * The text will be added in a new line. 63 | * 64 | * @param line 65 | * is the text to append. 66 | */ 67 | void appendBody(String line); 68 | 69 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /test-src/com/dumbster/smtp/RollingMailStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | import com.dumbster.smtp.mailstores.RollingMailStore; 4 | import org.junit.*; 5 | 6 | import static org.junit.Assert.*; 7 | 8 | public class RollingMailStoreTest { 9 | 10 | private MailStore mailStore; 11 | 12 | @Before 13 | public void setup() { 14 | mailStore = new RollingMailStore(); 15 | } 16 | 17 | @Test 18 | public void testNewMailStoreHasNoMail() { 19 | assertEquals(0, mailStore.getEmailCount()); 20 | } 21 | 22 | @Test 23 | public void testAddOneMessageLeavesOneMail() { 24 | addAMessage(); 25 | assertEquals(1, mailStore.getEmailCount()); 26 | } 27 | 28 | private void addAMessage() { 29 | MailMessage message = new MailMessageImpl(); 30 | mailStore.addMessage(message); 31 | } 32 | 33 | @Test 34 | public void testNewMailStoreHasEmptyMailList() { 35 | assertEquals(0, mailStore.getMessages().length); 36 | } 37 | 38 | @Test 39 | public void testAddOneMessageLeavesOneMailInMailMessagesArray() { 40 | addAMessage(); 41 | assertEquals(1, mailStore.getMessages().length); 42 | } 43 | 44 | @Test 45 | public void testGettingMailFromEmptyMailStoreThrowsIndexOutOfBounds() { 46 | try { 47 | mailStore.getMessage(0); 48 | fail("Should have raised exception."); 49 | } catch (IndexOutOfBoundsException ignored) { 50 | } 51 | } 52 | 53 | @Test 54 | public void testGettingMail0FromMailStoreWithAnItemWorks() { 55 | addAMessage(); 56 | assertNotNull(mailStore.getMessage(0)); 57 | } 58 | 59 | @Test 60 | public void testMailRollsOff() { 61 | MailMessage firstMessage = new MailMessageImpl(); 62 | firstMessage.appendBody("First Post!"); 63 | mailStore.addMessage(firstMessage); 64 | 65 | assertEquals("First Post!", mailStore.getMessage(0).getBody()); 66 | for (int i = 0; i < 100; i++) { 67 | addAMessage(); 68 | } 69 | 70 | assertEquals(100, mailStore.getEmailCount()); 71 | assertEquals("", mailStore.getMessage(0).getBody()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /smtp-states.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains an SMTP client request. Handles state transitions using the 3 | * following state transition table. 4 | * 5 | *
 6 |  * -----------+-------------------------------------------------------------------------------------------------
 7 |  *            |                                 State
 8 |  *  Action    +-------------+-----------+-----------+--------------+---------------+---------------+------------
 9 |  *            | CONNECT     | GREET     | MAIL      | RCPT         | DATA_HDR      | DATA_BODY     | QUIT
10 |  * -----------+-------------+-----------+-----------+--------------+---------------+---------------+------------
11 |  * connect    | 220/GREET   | 503/GREET | 503/MAIL  | 503/RCPT     | 503/DATA_HDR  | 503/DATA_BODY | 503/QUIT
12 |  * ehlo       | 503/CONNECT | 250/MAIL  | 503/MAIL  | 503/RCPT     | 503/DATA_HDR  | 503/DATA_BODY | 503/QUIT
13 |  * mail       | 503/CONNECT | 503/GREET | 250/RCPT  | 503/RCPT     | 503/DATA_HDR  | 503/DATA_BODY | 250/RCPT
14 |  * rcpt       | 503/CONNECT | 503/GREET | 503/MAIL  | 250/RCPT     | 503/DATA_HDR  | 503/DATA_BODY | 503/QUIT
15 |  * data       | 503/CONNECT | 503/GREET | 503/MAIL  | 354/DATA_HDR | 503/DATA_HDR  | 503/DATA_BODY | 503/QUIT
16 |  * data_end   | 503/CONNECT | 503/GREET | 503/MAIL  | 503/RCPT     | 250/QUIT      | 250/QUIT      | 503/QUIT
17 |  * unrecog    | 500/CONNECT | 500/GREET | 500/MAIL  | 500/RCPT     | ---/DATA_HDR  | ---/DATA_BODY | 500/QUIT
18 |  * quit       | 503/CONNECT | 503/GREET | 503/MAIL  | 503/RCPT     | 503/DATA_HDR  | 503/DATA_BODY | 250/CONNECT
19 |  * blank_line | 503/CONNECT | 503/GREET | 503/MAIL  | 503/RCPT     | ---/DATA_BODY | ---/DATA_BODY | 503/QUIT
20 |  * rset       | 250/GREET   | 250/GREET | 250/GREET | 250/GREET    | 250/GREET     | 250/GREET     | 250/GREET
21 |  * vrfy       | 252/CONNECT | 252/GREET | 252/MAIL  | 252/RCPT     | 252/DATA_HDR  | 252/DATA_BODY | 252/QUIT
22 |  * expn       | 252/CONNECT | 252/GREET | 252/MAIL  | 252/RCPT     | 252/DATA_HDR  | 252/DATA_BODY | 252/QUIT
23 |  * help       | 211/CONNECT | 211/GREET | 211/MAIL  | 211/RCPT     | 211/DATA_HDR  | 211/DATA_BODY | 211/QUIT
24 |  * noop       | 250/CONNECT | 250/GREET | 250/MAIL  | 250/RCPT     | 250|DATA_HDR  | 250/DATA_BODY | 250/QUIT
25 |  * 
26 | */ 27 | -------------------------------------------------------------------------------- /test-src/com/dumbster/smtp/eml/EMLMailMessageTest.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.eml; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.io.ByteArrayInputStream; 7 | import java.util.ArrayList; 8 | import java.util.Iterator; 9 | import java.util.List; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | import static org.junit.Assert.assertNull; 13 | 14 | public class EMLMailMessageTest { 15 | 16 | private EMLMailMessage message; 17 | /* 18 | Example From http://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol 19 | */ 20 | private final String text = 21 | "From: \"Bob Example\" \n" + 22 | "To: \"Alice Example\" \n" + 23 | "Cc: theboss@example.com\n" + 24 | "Date: Tue, 15 January 2008 16:02:43 -0500\n" + 25 | "Subject: Test message\n" + 26 | "\n" + 27 | "Hello Alice.\n" + 28 | "This is a test message with 5 header fields and 4 lines in the message body.\n" + 29 | "Your friend,\n" + 30 | "Bob\n"; 31 | 32 | @Before 33 | public void setup() { 34 | message = new EMLMailMessage(new ByteArrayInputStream(text.getBytes())); 35 | } 36 | 37 | @Test 38 | public void testReadHeaders() { 39 | String[] from = message.getHeaderValues("From"); 40 | assertEquals(1, from.length); 41 | assertEquals("\"Bob Example\" ", from[0]); 42 | assertEquals(1, message.getHeaderValues("To").length); 43 | } 44 | 45 | @Test 46 | public void testGetBody() { 47 | assertEquals("Hello Alice.\n" + 48 | "This is a test message with 5 header fields and 4 lines in the message body.\n" + 49 | "Your friend,\n" + 50 | "Bob", message.getBody()); 51 | } 52 | 53 | @Test 54 | public void testGetHeaderNames() { 55 | List headers = new ArrayList(); 56 | Iterator iterator = message.getHeaderNames(); 57 | while(iterator.hasNext()) { 58 | headers.add((String) iterator.next()); 59 | } 60 | assertEquals(5, headers.size()); 61 | } 62 | 63 | @Test 64 | public void testGetFirstHeaderValue() { 65 | String firstFrom = message.getFirstHeaderValue("From"); 66 | assertEquals("\"Bob Example\" ", firstFrom); 67 | assertNull(message.getFirstHeaderValue("MissingHeader")); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test-src/com/dumbster/smtp/ServerOptionsTest.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | import com.dumbster.smtp.mailstores.EMLMailStore; 4 | import com.dumbster.smtp.mailstores.RollingMailStore; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | /** 10 | * User: rj 11 | * Date: 7/21/13 12 | * Time: 8:20 AM 13 | */ 14 | public class ServerOptionsTest { 15 | 16 | private ServerOptions options; 17 | 18 | 19 | @Test 20 | public void defaultConfiguration() { 21 | options = new ServerOptions(); 22 | assertEquals(true, options.valid); 23 | assertEquals(25, options.port); 24 | assertEquals(true, options.threaded); 25 | assertEquals(RollingMailStore.class, options.mailStore.getClass()); 26 | } 27 | 28 | @Test 29 | public void emptyOptions() { 30 | String[] args = new String[]{}; 31 | options = new ServerOptions(args); 32 | assertEquals(true, options.valid); 33 | assertEquals(25, options.port); 34 | assertEquals(true, options.threaded); 35 | assertEquals(RollingMailStore.class, options.mailStore.getClass()); 36 | } 37 | 38 | @Test 39 | public void optionMailStoreEMLMailStore() { 40 | String[] args = new String[]{"--mailStore=EMLMailStore"}; 41 | options = new ServerOptions(args); 42 | assertEquals(EMLMailStore.class, options.mailStore.getClass()); 43 | assertEquals(true, options.valid); 44 | assertEquals(25, options.port); 45 | assertEquals(true, options.threaded); 46 | } 47 | 48 | @Test 49 | public void optionMailStoreInvalid() { 50 | String[] args = new String[]{"--mailStore"}; 51 | options = new ServerOptions(args); 52 | assertEquals(false, options.valid); 53 | } 54 | 55 | @Test 56 | public void badMailStore() { 57 | String[] args = new String[]{"--mailStore=foo"}; 58 | options = new ServerOptions(args); 59 | assertEquals(RollingMailStore.class, options.mailStore.getClass()); 60 | assertEquals(false, options.valid); 61 | } 62 | 63 | @Test 64 | public void threaded() { 65 | String[] args = new String[]{"--threaded"}; 66 | options = new ServerOptions(args); 67 | assertEquals(true, options.threaded); 68 | assertEquals(true, options.valid); 69 | assertEquals(25, options.port); 70 | assertEquals(RollingMailStore.class, options.mailStore.getClass()); 71 | } 72 | 73 | @Test 74 | public void notThreaded() { 75 | String[] args = new String[]{"--threaded=false"}; 76 | options = new ServerOptions(args); 77 | assertEquals(false, options.threaded); 78 | assertEquals(true, options.valid); 79 | assertEquals(25, options.port); 80 | assertEquals(RollingMailStore.class, options.mailStore.getClass()); 81 | } 82 | 83 | @Test 84 | public void alternativePort() { 85 | String[] args = new String[]{"12345"}; 86 | options = new ServerOptions(args); 87 | assertEquals(12345, options.port); 88 | assertEquals(true, options.threaded); 89 | assertEquals(true, options.valid); 90 | assertEquals(RollingMailStore.class, options.mailStore.getClass()); 91 | } 92 | 93 | @Test 94 | public void badPort() { 95 | String[] args = new String[]{"invalid"}; 96 | options = new ServerOptions(args); 97 | assertEquals(false, options.valid); 98 | } 99 | 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/eml/EMLMailMessage.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.eml; 2 | 3 | import java.io.*; 4 | import java.util.Iterator; 5 | import java.util.Scanner; 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | import com.dumbster.smtp.MailMessage; 10 | import com.dumbster.smtp.MailMessageImpl; 11 | import com.dumbster.smtp.SmtpState; 12 | 13 | /** 14 | * An implementation of MailMessage to support lazy load of messages stored in EML files. 15 | *

16 | * Each message is attached to a file but won't load the file until data is requested.
17 | * This object is detached from the original file, so changes made to this object won't reflect to the file automatically. 18 | */ 19 | public class EMLMailMessage implements MailMessage { 20 | 21 | private static final Pattern PATTERN = Pattern.compile("(.*?): (.*)"); 22 | 23 | private InputStream stream; 24 | private MailMessage delegate = new MailMessageImpl(); 25 | 26 | private boolean isLoaded = false; 27 | 28 | public EMLMailMessage(InputStream file) { 29 | this.stream = file; 30 | } 31 | 32 | public EMLMailMessage(File file) { 33 | try { 34 | this.stream = new FileInputStream(file); 35 | } catch (FileNotFoundException fnf) { 36 | throw new RuntimeException(fnf); 37 | } 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public Iterator getHeaderNames() { 45 | checkLoaded(); 46 | return delegate.getHeaderNames(); 47 | } 48 | 49 | private void checkLoaded() { 50 | if (!isLoaded) { 51 | loadFile(); 52 | isLoaded = true; 53 | } 54 | } 55 | 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | @Override 60 | public String[] getHeaderValues(String name) { 61 | checkLoaded(); 62 | return delegate.getHeaderValues(name); 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | */ 68 | @Override 69 | public String getFirstHeaderValue(String name) { 70 | checkLoaded(); 71 | return delegate.getFirstHeaderValue(name); 72 | } 73 | 74 | /** 75 | * {@inheritDoc} 76 | */ 77 | @Override 78 | public String getBody() { 79 | checkLoaded(); 80 | return delegate.getBody(); 81 | } 82 | 83 | /** 84 | * {@inheritDoc} 85 | */ 86 | @Override 87 | public void addHeader(String name, String value) { 88 | delegate.addHeader(name, value); 89 | } 90 | 91 | /** 92 | * {@inheritDoc} 93 | */ 94 | @Override 95 | public void appendHeader(String name, String value) { 96 | delegate.appendHeader(name, value); 97 | } 98 | 99 | /** 100 | * Adds the given text to message body 101 | */ 102 | @Override 103 | public void appendBody(String line) { 104 | delegate.appendBody(line); 105 | } 106 | 107 | private void loadFile() { 108 | Scanner scanner = new Scanner(stream); 109 | SmtpState state = SmtpState.DATA_HDR; 110 | while (scanner.hasNextLine()) { 111 | String line = scanner.nextLine(); 112 | 113 | if (state == SmtpState.DATA_HDR) { 114 | if (line.isEmpty()) { 115 | state = SmtpState.DATA_BODY; 116 | continue; 117 | } 118 | 119 | Matcher matcher = PATTERN.matcher(line); 120 | if (matcher.matches()) { 121 | String headerName = matcher.group(1); 122 | String headerValue = matcher.group(2); 123 | addHeader(headerName, headerValue); 124 | } 125 | } else { 126 | appendBody(line); 127 | } 128 | } 129 | scanner.close(); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /test-src/com/dumbster/smtp/MailMessageTest.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | import org.junit.*; 4 | 5 | import com.dumbster.smtp.MailMessage; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | public class MailMessageTest { 10 | 11 | private MailMessage message; 12 | 13 | @Before 14 | public void setup() { 15 | this.message = new MailMessageImpl(); 16 | } 17 | 18 | @Test 19 | public void testConstructor() { 20 | assertEquals("", message.getBody()); 21 | assertFalse(message.getHeaderNames().hasNext()); 22 | assertEquals("\n\n", message.toString()); 23 | } 24 | 25 | @Test 26 | public void testAddHeader() { 27 | message.addHeader("foo", "bar1"); 28 | assertEquals("bar1", message.getFirstHeaderValue("foo")); 29 | assertEquals("foo", message.getHeaderNames().next()); 30 | assertEquals("foo: bar1\n\n\n", message.toString()); 31 | } 32 | 33 | @Test 34 | public void testAppendHeader() { 35 | message.addHeader("foo", "bar1"); 36 | message.appendHeader("foo", " baz2"); 37 | assertEquals("bar1 baz2", message.getFirstHeaderValue("foo")); 38 | } 39 | 40 | @Test 41 | public void testAppendToNonExistingHeader() { 42 | message.appendHeader("foo", " baz2"); 43 | assertEquals(" baz2", message.getFirstHeaderValue("foo")); 44 | } 45 | 46 | @Test 47 | public void testLongSubjectHeader() { 48 | String longSubject = StringUtil.longString(500); 49 | message.addHeader("Subject", longSubject); 50 | assertEquals("Subject: "+longSubject+"\n\n\n", message.toString()); 51 | } 52 | 53 | @Test 54 | public void testEmptyHeaderValue() { 55 | String[] values = message.getHeaderValues("NOT PRESENT"); 56 | assertEquals(0, values.length); 57 | } 58 | 59 | @Test 60 | public void testEmptyFirstHeaderValue() { 61 | String value = message.getFirstHeaderValue("NOT PRESENT"); 62 | assertEquals(null, value); 63 | } 64 | 65 | @Test 66 | public void testAddTwoSameHeaders() { 67 | message.addHeader("foo", "bar1"); 68 | message.addHeader("foo", "bar2"); 69 | assertEquals("bar1", message.getFirstHeaderValue("foo")); 70 | assertEquals("bar2", message.getHeaderValues("foo")[1]); 71 | assertEquals("foo: bar1\nfoo: bar2\n\n\n", message.toString()); 72 | } 73 | 74 | @Test 75 | public void testGetHeaders() { 76 | message.addHeader("foo", "bar1"); 77 | message.addHeader("foo", "bar2"); 78 | message.addHeader("baz", "bar3"); 79 | assertEquals("bar1", message.getFirstHeaderValue("foo")); 80 | assertEquals("bar2", message.getHeaderValues("foo")[1]); 81 | assertEquals("bar3", message.getFirstHeaderValue("baz")); 82 | assertEquals(1, message.getHeaderValues("baz").length); 83 | } 84 | 85 | @Test 86 | public void testAppendBody() { 87 | message.appendBody("Should I have shut the server down before disconnecting the power?"); 88 | assertEquals( 89 | "\nShould I have shut the server down before disconnecting the power?\n", 90 | message.toString()); 91 | } 92 | 93 | @Test 94 | public void testAppendBodyKeepsNewlines() { 95 | message.appendBody("First line of text.\n"); 96 | message.appendBody("Now what should happen?\nShould this still work?\n"); 97 | message.appendBody("\n"); 98 | message.appendBody(""); 99 | assertEquals("\nFirst line of text.\n\nNow what should happen?\nShould this still work?\n\n\n", message.toString()); 100 | } 101 | 102 | @Test 103 | public void headersAndBody() { 104 | message.addHeader("foo", "bar1"); 105 | message.addHeader("foo", "bar2"); 106 | message.appendBody("Should I have shut the server down before disconnecting the power?"); 107 | assertEquals( 108 | "foo: bar1\nfoo: bar2\n\nShould I have shut the server down before disconnecting the power?\n", 109 | message.toString()); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/MailMessageImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Dumbster - a dummy SMTP server 3 | * Copyright 2004 Jason Paul Kitchen 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.dumbster.smtp; 18 | 19 | import java.util.Map; 20 | import java.util.HashMap; 21 | import java.util.Iterator; 22 | import java.util.List; 23 | import java.util.ArrayList; 24 | import java.util.Set; 25 | 26 | /** 27 | * Container for a complete SMTP message - headers and message body. 28 | */ 29 | public class MailMessageImpl implements MailMessage { 30 | private Map> headers; 31 | private StringBuffer body; 32 | 33 | public MailMessageImpl() { 34 | headers = new HashMap>(10); 35 | body = new StringBuffer(); 36 | } 37 | 38 | public Iterator getHeaderNames() { 39 | Set nameSet = headers.keySet(); 40 | return nameSet.iterator(); 41 | } 42 | 43 | public String[] getHeaderValues(String name) { 44 | List values = headers.get(name); 45 | if (values == null) { 46 | return new String[0]; 47 | } else { 48 | return values.toArray(new String[values.size()]); 49 | } 50 | } 51 | 52 | public String getFirstHeaderValue(String name) { 53 | List values = headers.get(name); 54 | if (values == null) { 55 | return null; 56 | } else { 57 | Iterator iterator = values.iterator(); 58 | return iterator.next(); 59 | } 60 | } 61 | 62 | public String getBody() { 63 | return body.toString(); 64 | } 65 | 66 | public void addHeader(String name, String value) { 67 | List valueList = headers.get(name); 68 | if (valueList == null) { 69 | valueList = new ArrayList(1); 70 | } 71 | valueList.add(value); 72 | headers.put(name, valueList); 73 | } 74 | 75 | public void appendHeader(String name, String value) { 76 | List values = headers.get(name); 77 | if (values == null) { 78 | addHeader(name, value); 79 | } else { 80 | String oldValue = values.get(values.size()-1); 81 | values.remove(oldValue); 82 | values.add(oldValue + value); 83 | headers.put(name, values); 84 | } 85 | } 86 | 87 | public void appendBody(String line) { 88 | if(shouldPrependNewline(line)) { 89 | body.append('\n'); 90 | } 91 | body.append(line); 92 | } 93 | 94 | private boolean shouldPrependNewline(String line) { 95 | return body.length() > 0 && line.length() > 0 && !"\n".equals(line); 96 | } 97 | 98 | public String toString() { 99 | StringBuffer msg = new StringBuffer(); 100 | for (Iterator i = headers.keySet().iterator(); i.hasNext();) { 101 | String name = i.next(); 102 | List values = headers.get(name); 103 | for (Iterator j = values.iterator(); j.hasNext();) { 104 | String value = j.next(); 105 | msg.append(name); 106 | msg.append(": "); 107 | msg.append(value); 108 | msg.append('\n'); 109 | } 110 | } 111 | msg.append('\n'); 112 | msg.append(body); 113 | msg.append('\n'); 114 | return msg.toString(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/ClientSession.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.PrintWriter; 6 | 7 | public class ClientSession implements Runnable { 8 | 9 | private IOSource socket; 10 | private volatile MailStore mailStore; 11 | private MailMessage msg; 12 | private Response smtpResponse; 13 | private PrintWriter out; 14 | private BufferedReader input; 15 | private SmtpState smtpState; 16 | private String line; 17 | private String lastHeaderName = null; 18 | 19 | 20 | public ClientSession(IOSource socket, MailStore mailStore) { 21 | this.socket = socket; 22 | this.mailStore = mailStore; 23 | this.msg = new MailMessageImpl(); 24 | Request request = Request.initialRequest(); 25 | smtpResponse = request.execute(this.mailStore, msg); 26 | } 27 | 28 | public void run() { 29 | try { 30 | prepareSessionLoop(); 31 | sessionLoop(); 32 | } catch (Exception ignored) { 33 | } finally { 34 | try { 35 | socket.close(); 36 | } catch (Exception ignored) { 37 | } 38 | } 39 | } 40 | 41 | private void prepareSessionLoop() throws IOException { 42 | prepareOutput(); 43 | prepareInput(); 44 | sendResponse(); 45 | updateSmtpState(); 46 | } 47 | 48 | private void prepareOutput() throws IOException { 49 | out = socket.getOutputStream(); 50 | out.flush(); 51 | } 52 | 53 | private void prepareInput() throws IOException { 54 | input = socket.getInputStream(); 55 | } 56 | 57 | private void sendResponse() { 58 | if (smtpResponse.getCode() > 0) { 59 | int code = smtpResponse.getCode(); 60 | String message = smtpResponse.getMessage(); 61 | out.print(code + " " + message + "\r\n"); 62 | out.flush(); 63 | } 64 | } 65 | 66 | private void updateSmtpState() { 67 | smtpState = smtpResponse.getNextState(); 68 | } 69 | 70 | private void sessionLoop() throws IOException { 71 | while (smtpState != SmtpState.CONNECT && readNextLineReady()) { 72 | Request request = Request.createRequest(smtpState, line); 73 | smtpResponse = request.execute(mailStore, msg); 74 | storeInputInMessage(request); 75 | sendResponse(); 76 | updateSmtpState(); 77 | saveAndRefreshMessageIfComplete(); 78 | } 79 | } 80 | 81 | private boolean readNextLineReady() throws IOException { 82 | readLine(); 83 | return line != null; 84 | } 85 | 86 | private void readLine() throws IOException { 87 | line = input.readLine(); 88 | } 89 | 90 | private void saveAndRefreshMessageIfComplete() { 91 | if (smtpState == SmtpState.QUIT) { 92 | mailStore.addMessage(msg); 93 | msg = new MailMessageImpl(); 94 | } 95 | } 96 | 97 | private void storeInputInMessage(Request request) { 98 | String params = request.getParams(); 99 | if (null == params) 100 | return; 101 | 102 | if (SmtpState.DATA_HDR.equals(smtpResponse.getNextState())) { 103 | addDataHeader(params); 104 | return; 105 | } 106 | 107 | if (SmtpState.DATA_BODY == smtpResponse.getNextState()) { 108 | msg.appendBody(params); 109 | return; 110 | } 111 | } 112 | 113 | private void addDataHeader(String params) { 114 | int headerNameEnd = params.indexOf(':'); 115 | if (headerNameEnd > 0 && !whiteSpacedLineStart(params)) { 116 | lastHeaderName = params.substring(0, headerNameEnd).trim(); 117 | String value = params.substring(headerNameEnd + 1).trim(); 118 | msg.addHeader(lastHeaderName, value); 119 | } else if (whiteSpacedLineStart(params) && lastHeaderName != null) { 120 | msg.appendHeader(lastHeaderName, params); 121 | } 122 | } 123 | 124 | private boolean whiteSpacedLineStart(String s) { 125 | if (s == null || "".equals(s)) 126 | return false; 127 | char c = s.charAt(0); 128 | return c == 32 || c == 0x0b || c == '\n' || 129 | c == '\r' || c == '\t' || c == '\f'; 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/Request.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Dumbster - a dummy SMTP server 3 | * Copyright 2004 Jason Paul Kitchen 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.dumbster.smtp; 19 | 20 | import com.dumbster.smtp.action.*; 21 | 22 | public class Request { 23 | private Action clientAction; 24 | private SmtpState state; 25 | private String params; 26 | 27 | Request(Action action, String params, SmtpState state) { 28 | this.clientAction = action; 29 | this.state = state; 30 | this.params = params; 31 | } 32 | 33 | private Request() { 34 | } 35 | 36 | public Response execute(MailStore mailStore, MailMessage message) { 37 | return clientAction.response(state, mailStore, message); 38 | } 39 | 40 | Action getClientAction() { 41 | return clientAction; 42 | } 43 | 44 | SmtpState getState() { 45 | return state; 46 | } 47 | 48 | public String getParams() { 49 | return params; 50 | } 51 | 52 | private boolean isInDataHeaderState() { 53 | return SmtpState.DATA_HDR == state; 54 | } 55 | 56 | private boolean isInDataBodyState() { 57 | return SmtpState.DATA_BODY == state; 58 | } 59 | 60 | public static Request initialRequest() { 61 | return new Request(new Connect(), "", SmtpState.CONNECT); 62 | } 63 | 64 | public static Request createRequest(SmtpState state, String message) { 65 | Request request = new Request(); 66 | request.state = state; 67 | 68 | if (request.isInDataHeaderState()) { 69 | return buildDataHeaderRequest(message, request); 70 | } 71 | 72 | if (request.isInDataBodyState()) { 73 | return buildDataBodyRequest(message, request); 74 | } 75 | return buildCommandRequest(message, request); 76 | } 77 | 78 | private static Request buildDataHeaderRequest(String message, Request request) { 79 | if (message.equals(".")) { 80 | request.clientAction = new DataEnd(); 81 | } else if (message.length() < 1) { 82 | request.clientAction = new BlankLine(); 83 | } else { 84 | request.clientAction = new Unrecognized(); 85 | request.params = message; 86 | } 87 | return request; 88 | } 89 | 90 | private static Request buildDataBodyRequest(String message, Request request) { 91 | if (message.equals(".")) { 92 | request.clientAction = new DataEnd(); 93 | } else { 94 | request.clientAction = new Unrecognized(); 95 | if (message.length() < 1) { 96 | request.params = "\n"; 97 | } else { 98 | request.params = message; 99 | } 100 | } 101 | return request; 102 | } 103 | 104 | private static Request buildCommandRequest(String message, Request request) { 105 | String su = message.toUpperCase(); 106 | if (su.startsWith("EHLO ") || su.startsWith("HELO")) { 107 | request.clientAction = new Ehlo(); 108 | extractParams(message, request); 109 | } else if (su.startsWith("MAIL FROM:")) { 110 | request.clientAction = new Mail(); 111 | request.params = message.substring(10); 112 | } else if (su.startsWith("RCPT TO:")) { 113 | request.clientAction = new Rcpt(); 114 | request.params = message.substring(8); 115 | } else if (su.startsWith("DATA")) { 116 | request.clientAction = new Data(); 117 | } else if (su.startsWith("QUIT")) { 118 | request.clientAction = new Quit(); 119 | } else if (su.startsWith("RSET")) { 120 | request.clientAction = new Rset(); 121 | } else if (su.startsWith("NOOP")) { 122 | request.clientAction = new NoOp(); 123 | } else if (su.startsWith("EXPN")) { 124 | request.clientAction = new Expn(); 125 | } else if (su.startsWith("VRFY")) { 126 | request.clientAction = new Vrfy(); 127 | } else if (su.startsWith("HELP")) { 128 | request.clientAction = new Help(); 129 | } else if (su.startsWith("LIST")) { 130 | extractParams(message, request); 131 | request.clientAction = new List(request.params); 132 | } else { 133 | request.clientAction = new Unrecognized(); 134 | } 135 | return request; 136 | } 137 | 138 | private static void extractParams(String message, Request request) { 139 | try { 140 | request.params = message.substring(5); 141 | } catch (StringIndexOutOfBoundsException ignored) { 142 | } 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/SmtpServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Dumbster - a dummy SMTP server 3 | * Copyright 2004 Jason Paul Kitchen 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.dumbster.smtp; 18 | 19 | import java.net.ServerSocket; 20 | import java.net.Socket; 21 | import java.io.IOException; 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.Executors; 24 | 25 | /** 26 | * Dummy SMTP server for testing purposes. 27 | */ 28 | public class SmtpServer implements Runnable { 29 | public static final int DEFAULT_SMTP_PORT = 25; 30 | private static final int SERVER_SOCKET_TIMEOUT = 0; 31 | private static final int MAX_THREADS = 10; 32 | 33 | private volatile MailStore mailStore; 34 | private volatile boolean stopped = true; 35 | private volatile boolean ready = false; 36 | private volatile boolean threaded = false; 37 | 38 | private ServerSocket serverSocket; 39 | private int port; 40 | 41 | public void run() { 42 | stopped = false; 43 | try { 44 | initializeServerSocket(); 45 | serverLoop(); 46 | } catch (Exception e) { 47 | e.printStackTrace(); 48 | } finally { 49 | ready = false; 50 | if (serverSocket != null) { 51 | try { 52 | serverSocket.close(); 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | } 58 | } 59 | 60 | private void initializeServerSocket() throws Exception { 61 | serverSocket = new ServerSocket(port); 62 | serverSocket.setSoTimeout(SERVER_SOCKET_TIMEOUT); 63 | } 64 | 65 | private void serverLoop() throws IOException { 66 | int poolSize = threaded ? MAX_THREADS : 1; 67 | ExecutorService threadExecutor = Executors.newFixedThreadPool(poolSize); 68 | while (!isStopped()) { 69 | Socket clientSocket; 70 | try { 71 | clientSocket = clientSocket(); 72 | } catch(IOException ex) { 73 | if (isStopped()) { 74 | break; 75 | } else { 76 | throw ex; 77 | } 78 | } 79 | SocketWrapper source = new SocketWrapper(clientSocket); 80 | ClientSession session = new ClientSession(source, mailStore); 81 | threadExecutor.execute(session); 82 | } 83 | threadExecutor.shutdown(); 84 | ready = false; 85 | } 86 | 87 | private Socket clientSocket() throws IOException { 88 | Socket socket = null; 89 | while (socket == null) { 90 | socket = accept(); 91 | } 92 | return socket; 93 | } 94 | 95 | private Socket accept() throws IOException { 96 | ready = true; 97 | return serverSocket.accept(); 98 | } 99 | 100 | public boolean isStopped() { 101 | return stopped; 102 | } 103 | 104 | public synchronized void stop() { 105 | stopped = true; 106 | try { 107 | serverSocket.close(); 108 | } catch (IOException e) { 109 | throw new SmtpServerException(e); 110 | } 111 | } 112 | 113 | public static class SmtpServerException extends RuntimeException { 114 | public SmtpServerException(Throwable cause) { 115 | super(cause); 116 | } 117 | } 118 | 119 | public MailMessage[] getMessages() { 120 | return mailStore.getMessages(); 121 | } 122 | 123 | public MailMessage getMessage(int i) { 124 | return mailStore.getMessage(i); 125 | } 126 | 127 | public int getEmailCount() { 128 | return mailStore.getEmailCount(); 129 | } 130 | 131 | public void anticipateMessageCountFor(int messageCount, int ticks) { 132 | int tickdown = ticks; 133 | while (mailStore.getEmailCount() < messageCount && tickdown > 0) { 134 | tickdown--; 135 | try { 136 | Thread.sleep(1); 137 | } catch (InterruptedException e) { 138 | return; 139 | } 140 | } 141 | } 142 | 143 | public boolean isReady() { 144 | return ready; 145 | } 146 | 147 | /** 148 | * Toggles if the SMTP server is single or multi-threaded for response to 149 | * SMTP sessions. 150 | * 151 | * @param threaded 152 | */ 153 | public void setThreaded(boolean threaded) { 154 | this.threaded = threaded; 155 | } 156 | 157 | public void setMailStore(MailStore mailStore) { 158 | this.mailStore = mailStore; 159 | } 160 | 161 | public void setPort(int port) { 162 | this.port = port; 163 | } 164 | 165 | public void clearMessages() { 166 | this.mailStore.clearMessages(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/com/dumbster/smtp/mailstores/EMLMailStore.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.mailstores; 2 | 3 | import java.io.File; 4 | import java.io.FileWriter; 5 | import java.io.FilenameFilter; 6 | import java.util.ArrayList; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import com.dumbster.smtp.MailMessage; 13 | import com.dumbster.smtp.MailStore; 14 | import com.dumbster.smtp.eml.EMLMailMessage; 15 | 16 | /** 17 | * Store messages as EML files. 18 | *
This class makes no guarantees as to the order of the received messages. 19 | * The messages are stored in order but getMessages won't return messages in the same order they were received. 20 | */ 21 | public class EMLMailStore implements MailStore { 22 | 23 | private boolean initialized; 24 | private int count = 0; 25 | private File directory = new File("eml_store"); 26 | private List messages = new ArrayList(); 27 | 28 | /** 29 | * Checks if mail mailStore is initialized and initializes it if it's not. 30 | */ 31 | private void checkInitialized() { 32 | if (!initialized) { 33 | if (!directory.exists()) { 34 | directory.mkdirs(); 35 | } else { 36 | loadMessages(); 37 | } 38 | initialized = true; 39 | } 40 | } 41 | 42 | /** 43 | * Load previous messages from directory. 44 | */ 45 | private void loadMessages() { 46 | File[] files = loadMessageFiles(); 47 | 48 | for (File file : files) { 49 | MailMessage message = new EMLMailMessage(file); 50 | messages.add(message); 51 | } 52 | count = files.length; 53 | } 54 | 55 | /** 56 | * Load message files from mailStore directory. 57 | * @return an array of {@code File} 58 | */ 59 | private File[] loadMessageFiles() { 60 | File[] files = this.directory.listFiles(new EMLFilenameFilter()); 61 | if (files == null) { 62 | System.err.println("Unable to load messages from eml mailStore directory: " + directory); 63 | return new File[0]; 64 | } 65 | return files; 66 | } 67 | 68 | /** 69 | * {@inheritDoc} 70 | */ 71 | @Override 72 | public int getEmailCount() { 73 | checkInitialized(); 74 | return count; 75 | } 76 | 77 | /** 78 | * {@inheritDoc} 79 | */ 80 | @Override 81 | public void addMessage(MailMessage message) { 82 | checkInitialized(); 83 | count++; 84 | messages.add(message); 85 | 86 | System.out.println("Received message: " + count); 87 | 88 | try { 89 | if (!directory.exists()) { 90 | System.out.println("Directory created: " + directory); 91 | directory.mkdirs(); 92 | } 93 | String filename = getFilename(message, count); 94 | File file = new File(directory, filename); 95 | FileWriter writer = new FileWriter(file); 96 | 97 | for (Iterator i = message.getHeaderNames(); i.hasNext();) { 98 | String name = i.next(); 99 | String[] values = message.getHeaderValues(name); 100 | for (String value : values) { 101 | writer.append(name); 102 | writer.append(": "); 103 | writer.append(value); 104 | writer.append('\n'); 105 | } 106 | } 107 | writer.append('\n'); 108 | writer.append(message.getBody()); 109 | writer.append('\n'); 110 | 111 | writer.close(); 112 | 113 | } catch (Exception e) { 114 | System.err.println(e.getMessage()); 115 | e.printStackTrace(); 116 | } 117 | } 118 | 119 | public String getFilename(MailMessage message, int count) { 120 | String filename = new StringBuilder().append(count).append("_") 121 | .append(message.getFirstHeaderValue("Subject")) 122 | .append(".eml").toString(); 123 | filename = filename.replaceAll("[\\\\/<>\\?>\\*\"\\|]", "_"); 124 | return filename; 125 | } 126 | 127 | /** 128 | * Return a list of messages stored by this mail mailStore. 129 | * @return a list of {@code EMLMailMessage} 130 | */ 131 | @Override 132 | public MailMessage[] getMessages() { 133 | checkInitialized(); 134 | 135 | return messages.toArray(new MailMessage[0]); 136 | } 137 | 138 | /** 139 | * {@inheritDoc} 140 | */ 141 | @Override 142 | public MailMessage getMessage(int index) { 143 | return getMessages()[index]; 144 | } 145 | 146 | /** 147 | * {@inheritDoc} 148 | */ 149 | @Override 150 | public void clearMessages() { 151 | for (File file : this.directory.listFiles(new EMLFilenameFilter())) { 152 | file.delete(); 153 | count--; 154 | } 155 | messages.clear(); 156 | } 157 | 158 | public void setDirectory(String directory) { 159 | setDirectory(new File(directory)); 160 | } 161 | 162 | public void setDirectory(File directory) { 163 | this.directory = directory; 164 | } 165 | 166 | /** 167 | * Filter only files matching name of files saved by EMLMailStore. 168 | */ 169 | public static class EMLFilenameFilter implements FilenameFilter { 170 | private final Pattern PATTERN = Pattern.compile("\\d+_.*\\.eml"); 171 | private final Matcher MATCHER = PATTERN.matcher(""); 172 | 173 | @Override 174 | public boolean accept(File dir, String name) { 175 | MATCHER.reset(name); 176 | return MATCHER.matches(); 177 | } 178 | 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /test-src/com/dumbster/smtp/eml/EMLMailStoreTest.java: -------------------------------------------------------------------------------- 1 | package com.dumbster.smtp.eml; 2 | 3 | import com.dumbster.smtp.MailMessage; 4 | import com.dumbster.smtp.MailMessageImpl; 5 | import com.dumbster.smtp.mailstores.EMLMailStore; 6 | import com.dumbster.smtp.mailstores.EMLMailStore.EMLFilenameFilter; 7 | import org.junit.After; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.util.Random; 14 | 15 | import static org.junit.Assert.*; 16 | 17 | public class EMLMailStoreTest { 18 | 19 | private EMLMailStore mailStore; 20 | private File emlStoreDir; 21 | 22 | @Before 23 | public void setup() { 24 | mailStore = new EMLMailStore(); 25 | emlStoreDir = new File("build/test/eml_store_test" + String.valueOf(new Random().nextInt(1000000))); 26 | mailStore.setDirectory(emlStoreDir); 27 | } 28 | 29 | @After 30 | public void tearDown() { 31 | int count = 1; 32 | for (MailMessage message : mailStore.getMessages()) { 33 | String filename = mailStore.getFilename(message, count++); 34 | new File(emlStoreDir, filename).delete(); 35 | } 36 | mailStore.clearMessages(); 37 | deleteTheTwoMessages(); 38 | 39 | emlStoreDir.delete(); 40 | } 41 | 42 | @Test 43 | public void testNewMailStoreHasNoMail() { 44 | givenMailStoreDirectoryExists(); 45 | 46 | givenMailStoreDirectoryIsEmpty(); 47 | 48 | assertEquals(0, mailStore.getEmailCount()); 49 | } 50 | 51 | @Test 52 | public void testNewMailStoreShouldLoadMessagesFromDirectory() { 53 | givenMailStoreDirectoryExists(); 54 | 55 | givenMailStoreDirectoryHasTwoMessages(); 56 | 57 | assertEquals(2, mailStore.getEmailCount()); 58 | } 59 | 60 | private void deleteTheTwoMessages() { 61 | new File(emlStoreDir, "1_message.eml").delete(); 62 | new File(emlStoreDir, "2_message.eml").delete(); 63 | } 64 | 65 | 66 | @Test 67 | public void testDirectoryShouldBeCreated() { 68 | givenMailStoreDirectoryDoesNotExist(); 69 | 70 | whenAMessageIsAdded(); 71 | 72 | directoryShouldBeCreatedAutomatically(); 73 | 74 | messageFileShouldExist(); 75 | } 76 | 77 | @Test 78 | public void testMessageIsLoadedProperly() { 79 | givenMailStoreDirectoryExists(); 80 | 81 | givenMailStoreDirectoryIsEmpty(); 82 | 83 | MailMessage message = new MailMessageImpl(); 84 | message.addHeader("Message-ID", "<10298244.21372804359732.JavaMail.test@localhost>"); 85 | message.addHeader("From", ""); 86 | message.addHeader("To", "email@localhost.com"); 87 | message.addHeader("Subject", "The email subject"); 88 | message.appendBody("This is the body"); 89 | 90 | mailStore.addMessage(message); 91 | 92 | MailMessage storedMessage = mailStore.getMessage(0); 93 | 94 | assertEquals("<10298244.21372804359732.JavaMail.test@localhost>", storedMessage.getFirstHeaderValue("Message-ID")); 95 | assertEquals("", storedMessage.getFirstHeaderValue("From")); 96 | assertEquals("email@localhost.com", storedMessage.getFirstHeaderValue("To")); 97 | assertEquals("The email subject", storedMessage.getFirstHeaderValue("Subject")); 98 | assertEquals("This is the body", storedMessage.getBody()); 99 | } 100 | 101 | @Test 102 | public void testEMLFilenameFilter() { 103 | EMLFilenameFilter filter = new EMLFilenameFilter(); 104 | 105 | assertTrue(filter.accept(null, "1_something.eml")); 106 | assertTrue(filter.accept(null, "1_something with spaces.eml")); 107 | assertTrue(filter.accept(null, "1_.eml")); 108 | assertTrue(filter.accept(null, "982734987235_.eml")); 109 | assertTrue(filter.accept(null, "982734987235_1234_something_1234.eml")); 110 | assertFalse(filter.accept(null, "_.eml")); 111 | assertFalse(filter.accept(null, "2 not_matching.eml")); 112 | assertFalse(filter.accept(null, "3 not_matching eml")); 113 | assertFalse(filter.accept(null, "not_matching.eml")); 114 | assertFalse(filter.accept(null, "1_something.txt")); 115 | assertFalse(filter.accept(null, "1_something with spaces.txt")); 116 | } 117 | 118 | @Test 119 | public void testAddOneMessageLeavesOneMail() { 120 | givenMailStoreDirectoryExists(); 121 | 122 | givenMailStoreDirectoryIsEmpty(); 123 | 124 | whenAMessageIsAdded(); 125 | 126 | assertEquals(1, mailStore.getEmailCount()); 127 | } 128 | 129 | @Test 130 | public void testNewMailStoreHasEmptyMailList() { 131 | givenMailStoreDirectoryExists(); 132 | 133 | givenMailStoreDirectoryIsEmpty(); 134 | 135 | assertEquals(0, mailStore.getMessages().length); 136 | } 137 | 138 | @Test 139 | public void testAddOneMessageLeavesOneMailInMailMessagesArray() { 140 | givenMailStoreDirectoryExists(); 141 | 142 | givenMailStoreDirectoryIsEmpty(); 143 | 144 | whenAMessageIsAdded(); 145 | 146 | assertEquals(1, mailStore.getMessages().length); 147 | } 148 | 149 | @Test(expected = IndexOutOfBoundsException.class) 150 | public void testGettingMailFromEmptyMailStoreThrowsIndexOutOfBounds() { 151 | givenMailStoreDirectoryExists(); 152 | 153 | givenMailStoreDirectoryIsEmpty(); 154 | 155 | mailStore.getMessage(0); 156 | } 157 | 158 | @Test 159 | public void testGettingMail0FromMailStoreWithAnItemWorks() { 160 | whenAMessageIsAdded(); 161 | assertNotNull(mailStore.getMessage(0)); 162 | } 163 | 164 | /* 165 | * BDD methods. 166 | */ 167 | 168 | private void givenMailStoreDirectoryIsEmpty() { 169 | File[] files = emlStoreDir.listFiles(); 170 | for (File file : files) { 171 | file.delete(); 172 | } 173 | } 174 | private void givenMailStoreDirectoryDoesNotExist() { 175 | if (emlStoreDir.exists()) { 176 | for (File file : emlStoreDir.listFiles()) { 177 | file.delete(); 178 | } 179 | emlStoreDir.delete(); 180 | } 181 | } 182 | private void givenMailStoreDirectoryExists() { 183 | if (!emlStoreDir.exists()) { 184 | emlStoreDir.mkdirs(); 185 | } 186 | } 187 | private void givenMailStoreDirectoryHasTwoMessages() { 188 | givenMailStoreDirectoryIsEmpty(); 189 | try { 190 | 191 | File file1 = new File(emlStoreDir, "1_message.eml"); 192 | file1.createNewFile(); 193 | 194 | File file2 = new File(emlStoreDir, "2_message.eml"); 195 | file2.createNewFile(); 196 | 197 | } catch (IOException e) { 198 | e.printStackTrace(); 199 | fail(e.getMessage()); 200 | } 201 | } 202 | 203 | private void whenAMessageIsAdded() { 204 | MailMessage message = new MailMessageImpl(); 205 | mailStore.addMessage(message); 206 | } 207 | 208 | private void directoryShouldBeCreatedAutomatically() { 209 | assertTrue(emlStoreDir.exists()); 210 | } 211 | 212 | private void messageFileShouldExist() { 213 | assertTrue(emlStoreDir.listFiles().length == 1); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | org.github.rjo1970 7 | dumbster 8 | jar 9 | 1.9.0.2-SNAPSHOT 10 | 11 | Dumbster 12 | 13 | fake SMTP server 14 | 15 | 16 | https://github.com/rjo1970/dumbster 17 | 18 | 19 | https://github.com/rjo1970/dumbster 20 | scm:git:https://github.com/rjo1970/dumbster.git 21 | scm:git:${githubUserAndHost}:rjo1970/dumbster.git 22 | 23 | 24 | 25 | 26 | Apache 2 27 | http://www.apache.org/licenses/LICENSE-2.0.txt 28 | repo 29 | 30 | 31 | 32 | 35 | 36 | 37 | repo 38 | Repository Name 39 | scp://host/path/to/repo 40 | 41 | 42 | repo 43 | Repository Name 44 | scp://host/path/to/repo 45 | 46 | 47 | 48 | github-project-site 49 | gitsite:${githubUserAndHost}/rjo1970/dumbster 50 | 51 | 52 | 53 | 54 | UTF-8 55 | UTF-8 56 | 1.6 57 | 58 | 59 | 60 | 61 | junit 62 | junit 63 | 4.8.2 64 | test 65 | 66 | 67 | javax.activation 68 | activation 69 | 1.1.1 70 | test 71 | 72 | 73 | javax.mail 74 | mail 75 | 1.4.4 76 | test 77 | 78 | 79 | 80 | 81 | ${project.basedir}/src 82 | ${project.basedir}/test-src 83 | 84 | 85 | 86 | org.apache.maven.scm 87 | maven-scm-provider-gitexe 88 | 1.3 89 | 90 | 91 | org.apache.maven.scm 92 | maven-scm-manager-plexus 93 | 1.3 94 | 95 | 96 | org.kathrynhuxtable.maven.wagon 97 | wagon-gitsite 98 | 0.3.1 99 | 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-jar-plugin 106 | 2.3.1 107 | 108 | 109 | 110 | true 111 | com.dumbster.smtp.Main 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-site-plugin 121 | 3.0-beta-3 122 | 123 | 124 | 125 | org.apache.maven.plugins 126 | maven-compiler-plugin 127 | 2.3.2 128 | 129 | ${jdk_version} 130 | ${jdk_version} 131 | true 132 | true 133 | 134 | 135 | 136 | 137 | org.codehaus.mojo 138 | cobertura-maven-plugin 139 | 2.5.1 140 | 141 | 142 | xml 143 | html 144 | 145 | 146 | 147 | 148 | 149 | clean 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | org.apache.maven.plugins 161 | maven-changelog-plugin 162 | 2.2 163 | 164 | 165 | org.apache.maven.plugins 166 | maven-javadoc-plugin 167 | 2.8 168 | 169 | true 170 | true 171 | src/main/javadoc/resources/spring-javadoc.css 172 | 173 | 174 | 175 | org.apache.maven.plugins 176 | maven-jxr-plugin 177 | 2.2 178 | 179 | 180 | org.apache.maven.plugins 181 | maven-pmd-plugin 182 | 2.5 183 | 184 | ${jdk_version} 185 | 186 | 187 | 188 | org.apache.maven.plugins 189 | maven-project-info-reports-plugin 190 | 2.4 191 | 192 | 193 | 194 | cim 195 | distribution-management 196 | index 197 | issue-tracking 198 | license 199 | mailing-list 200 | project-team 201 | scm 202 | summary 203 | 204 | 205 | 206 | 207 | 208 | org.apache.maven.plugins 209 | maven-surefire-report-plugin 210 | 2.8.1 211 | 212 | 213 | org.codehaus.mojo 214 | cobertura-maven-plugin 215 | 2.5.1 216 | 217 | 218 | org.codehaus.mojo 219 | findbugs-maven-plugin 220 | 2.3.2 221 | 222 | 223 | org.codehaus.mojo 224 | jdepend-maven-plugin 225 | 2.0-beta-2 226 | 227 | 228 | org.codehaus.mojo 229 | taglist-maven-plugin 230 | 2.4 231 | 232 | 233 | 234 | 235 | 236 | 237 | github-public-properties 238 | 239 | 240 | true 241 | 242 | 243 | 244 | git@github.com 245 | 246 | 247 | 248 | 249 | github-tunneled 250 | 251 | 252 | github_tun 253 | 254 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /test-src/com/dumbster/smtp/SmtpServerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Dumbster - a dummy SMTP server 3 | * Copyright 2004 Jason Paul Kitchen 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.dumbster.smtp; 18 | 19 | import org.junit.*; 20 | 21 | import com.dumbster.smtp.SmtpServer; 22 | 23 | import static org.junit.Assert.*; 24 | 25 | import javax.activation.DataHandler; 26 | import javax.activation.DataSource; 27 | import javax.mail.*; 28 | import javax.mail.internet.*; 29 | import java.util.Properties; 30 | import java.util.Date; 31 | 32 | public class SmtpServerTest { 33 | private static final int SMTP_PORT = 1081; 34 | 35 | private SmtpServer server; 36 | 37 | private final String SERVER = "localhost"; 38 | private final String FROM = "sender@here.com"; 39 | private final String TO = "receiver@there.com"; 40 | private final String SUBJECT = "Test"; 41 | private final String BODY = "Test Body"; 42 | private final String FileName = "license.txt"; 43 | 44 | private final int WAIT_TICKS = 10000; 45 | 46 | @Before 47 | public void setup() { 48 | ServerOptions options = new ServerOptions(); 49 | options.port = SMTP_PORT; 50 | server = SmtpServerFactory.startServer(options); 51 | } 52 | 53 | @After 54 | public void teardown() { 55 | server.stop(); 56 | } 57 | 58 | @Test 59 | public void testNoMessageSentButWaitingDoesNotHang() { 60 | server.anticipateMessageCountFor(1, 10); 61 | assertEquals(0, server.getEmailCount()); 62 | } 63 | 64 | @Test 65 | public void testSend() { 66 | sendMessage(SMTP_PORT, FROM, SUBJECT, BODY, TO); 67 | server.anticipateMessageCountFor(1, WAIT_TICKS); 68 | assertTrue(server.getEmailCount() == 1); 69 | MailMessage email = server.getMessage(0); 70 | assertEquals("Test", email.getFirstHeaderValue("Subject")); 71 | assertEquals("Test Body", email.getBody()); 72 | } 73 | 74 | @Test 75 | public void testClearMessages() { 76 | sendMessage(SMTP_PORT, FROM, SUBJECT, BODY, TO); 77 | server.anticipateMessageCountFor(1, WAIT_TICKS); 78 | assertTrue(server.getEmailCount() == 1); 79 | sendMessage(SMTP_PORT, FROM, SUBJECT, BODY, TO); 80 | server.anticipateMessageCountFor(1, WAIT_TICKS); 81 | assertTrue(server.getEmailCount() == 2); 82 | server.clearMessages(); 83 | assertTrue(server.getEmailCount() == 0); 84 | } 85 | 86 | @Test 87 | public void testSendWithLongSubject() { 88 | String longSubject = StringUtil.longString(500); 89 | sendMessage(SMTP_PORT, FROM, longSubject, BODY, TO); 90 | server.anticipateMessageCountFor(1, WAIT_TICKS); 91 | assertTrue(server.getEmailCount() == 1); 92 | MailMessage email = server.getMessage(0); 93 | assertEquals(longSubject, email.getFirstHeaderValue("Subject")); 94 | assertEquals(500, longSubject.length()); 95 | assertEquals("Test Body", email.getBody()); 96 | } 97 | 98 | @Test 99 | public void testSendWithFoldedSubject() { 100 | String subject = "This\r\n is a folded\r\n Subject line."; 101 | MailMessage email = sendMessageWithSubject(subject); 102 | assertEquals("This is a folded Subject line.", email.getFirstHeaderValue("Subject")); 103 | } 104 | 105 | private MailMessage sendMessageWithSubject(String subject) { 106 | sendMessage(SMTP_PORT, FROM, subject, BODY, TO); 107 | server.anticipateMessageCountFor(1, WAIT_TICKS); 108 | assertEquals(1, server.getEmailCount()); 109 | return server.getMessage(0); 110 | } 111 | 112 | @Test 113 | public void testSendWithFoldedSubjectLooksLikeHeader() { 114 | String subject = "This\r\n really: looks\r\n strange."; 115 | MailMessage email = sendMessageWithSubject(subject); 116 | assertEquals("This really: looks strange.", email.getFirstHeaderValue("Subject")); 117 | } 118 | 119 | @Test 120 | @Ignore 121 | // should this work? 122 | public void testSendMessageWithCarriageReturn() { 123 | String bodyWithCR = "\r\nKeep these pesky carriage returns\r\n"; 124 | sendMessage(SMTP_PORT, FROM, SUBJECT, bodyWithCR, TO); 125 | assertEquals(1, server.getEmailCount()); 126 | MailMessage email = server.getMessage(0); 127 | assertEquals(bodyWithCR, email.getBody()); 128 | } 129 | 130 | @Test 131 | public void testThreadedSend() { 132 | server.setThreaded(true); 133 | sendMessage(SMTP_PORT, FROM, SUBJECT, BODY, TO); 134 | server.anticipateMessageCountFor(1, WAIT_TICKS); 135 | assertTrue(server.getEmailCount() == 1); 136 | MailMessage email = server.getMessage(0); 137 | assertEquals("Test", email.getFirstHeaderValue("Subject")); 138 | assertEquals("Test Body", email.getBody()); 139 | } 140 | 141 | @Test 142 | public void testSendTwoMessagesSameConnection() { 143 | try { 144 | MimeMessage[] mimeMessages = new MimeMessage[2]; 145 | Properties mailProps = getMailProperties(SMTP_PORT); 146 | Session session = Session.getInstance(mailProps, null); 147 | 148 | mimeMessages[0] = createMessage(session, "sender@whatever.com", "receiver@home.com", "Doodle1", "Bug1"); 149 | mimeMessages[1] = createMessage(session, "sender@whatever.com", "receiver@home.com", "Doodle2", "Bug2"); 150 | 151 | Transport transport = session.getTransport("smtp"); 152 | transport.connect("localhost", SMTP_PORT, null, null); 153 | 154 | for (MimeMessage mimeMessage : mimeMessages) { 155 | transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients()); 156 | } 157 | 158 | transport.close(); 159 | } catch (MessagingException e) { 160 | e.printStackTrace(); 161 | fail("Unexpected exception: " + e); 162 | } 163 | server.anticipateMessageCountFor(2, WAIT_TICKS); 164 | assertEquals(2, server.getEmailCount()); 165 | } 166 | 167 | @Test 168 | public void testSendingFileAttachment() throws MessagingException { 169 | Properties props = getMailProperties(SMTP_PORT); 170 | props.put("mail.smtp.host", "localhost"); 171 | Session session = Session.getDefaultInstance(props, null); 172 | MimeMessage message = new MimeMessage(session); 173 | 174 | message.setFrom(new InternetAddress(FROM)); 175 | message.addRecipient(Message.RecipientType.TO, new InternetAddress(TO)); 176 | message.setSubject(SUBJECT); 177 | 178 | Multipart multipart = new MimeMultipart(); 179 | multipart.addBodyPart(buildMessageBody()); 180 | multipart.addBodyPart(buildFileAttachment()); 181 | message.setContent(multipart); 182 | Transport.send(message); 183 | server.anticipateMessageCountFor(1, WAIT_TICKS); 184 | assertTrue(server.getMessage(0).getBody().indexOf("Apache License") > 0); 185 | } 186 | 187 | private MimeBodyPart buildFileAttachment() throws MessagingException { 188 | MimeBodyPart messageBodyPart = new MimeBodyPart(); 189 | DataSource source = new javax.activation.FileDataSource(FileName); 190 | messageBodyPart.setDataHandler(new DataHandler(source)); 191 | messageBodyPart.setFileName(FileName); 192 | return messageBodyPart; 193 | } 194 | 195 | private MimeBodyPart buildMessageBody() throws MessagingException { 196 | MimeBodyPart messageBodyPart = new MimeBodyPart(); 197 | messageBodyPart.setText(BODY); 198 | return messageBodyPart; 199 | } 200 | 201 | @Test 202 | public void testSendTwoMsgsWithLogin() { 203 | try { 204 | 205 | Properties props = System.getProperties(); 206 | 207 | Session session = Session.getDefaultInstance(props, null); 208 | Message msg = new MimeMessage(session); 209 | 210 | msg.setFrom(new InternetAddress(FROM)); 211 | 212 | InternetAddress.parse(TO, false); 213 | msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(TO, false)); 214 | msg.setSubject(SUBJECT); 215 | 216 | msg.setText(BODY); 217 | msg.setHeader("X-Mailer", "musala"); 218 | msg.setSentDate(new Date()); 219 | msg.saveChanges(); 220 | 221 | Transport transport = null; 222 | 223 | try { 224 | transport = session.getTransport("smtp"); 225 | transport.connect(SERVER, SMTP_PORT, "ddd", "ddd"); 226 | transport.sendMessage(msg, InternetAddress.parse(TO, false)); 227 | transport.sendMessage(msg, InternetAddress.parse("dimiter.bakardjiev@musala.com", false)); 228 | } catch (javax.mail.MessagingException me) { 229 | me.printStackTrace(); 230 | } catch (Exception e) { 231 | e.printStackTrace(); 232 | } finally { 233 | if (transport != null) { 234 | transport.close(); 235 | } 236 | } 237 | } catch (Exception e) { 238 | e.printStackTrace(); 239 | } 240 | server.anticipateMessageCountFor(2, WAIT_TICKS); 241 | assertEquals(2, server.getEmailCount()); 242 | MailMessage email = server.getMessage(0); 243 | assertEquals("Test", email.getFirstHeaderValue("Subject")); 244 | assertEquals("Test Body", email.getBody()); 245 | } 246 | 247 | private Properties getMailProperties(int port) { 248 | Properties mailProps = new Properties(); 249 | mailProps.setProperty("mail.smtp.host", "localhost"); 250 | mailProps.setProperty("mail.smtp.port", "" + port); 251 | mailProps.setProperty("mail.smtp.sendpartial", "true"); 252 | return mailProps; 253 | } 254 | 255 | private void sendMessage(int port, String from, String subject, String body, String to) { 256 | try { 257 | Properties mailProps = getMailProperties(port); 258 | Session session = Session.getInstance(mailProps, null); 259 | 260 | MimeMessage msg = createMessage(session, from, to, subject, body); 261 | Transport.send(msg); 262 | } catch (Exception e) { 263 | e.printStackTrace(); 264 | fail("Unexpected exception: " + e); 265 | } 266 | } 267 | 268 | private MimeMessage createMessage(Session session, String from, String to, String subject, String body) throws MessagingException { 269 | MimeMessage msg = new MimeMessage(session); 270 | msg.setFrom(new InternetAddress(from)); 271 | msg.setSubject(subject); 272 | msg.setSentDate(new Date()); 273 | msg.setText(body); 274 | msg.setRecipient(Message.RecipientType.TO, new InternetAddress(to)); 275 | return msg; 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /test-src/com/dumbster/smtp/RequestTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Dumbster - a dummy SMTP server 3 | * Copyright 2004 Jason Paul Kitchen 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.dumbster.smtp; 19 | 20 | import com.dumbster.smtp.mailstores.RollingMailStore; 21 | import org.junit.*; 22 | 23 | import com.dumbster.smtp.action.*; 24 | 25 | import static org.junit.Assert.*; 26 | 27 | public class RequestTest { 28 | 29 | private static MailMessage message; 30 | private MailStore mailStore; 31 | 32 | @Before 33 | public void setup() { 34 | message = new MailMessageImpl(); 35 | mailStore = new RollingMailStore(); 36 | } 37 | 38 | @Test 39 | public void testUnrecognizedCommandConnectState() { 40 | Request request = Request.createRequest(SmtpState.GREET, "UNRECOGNIZED"); 41 | Response response = request.execute(mailStore, message); 42 | assertEquals(SmtpState.GREET, request.getState()); 43 | assertEquals("Unrecognized command / data", request.getClientAction().toString()); 44 | assertEquals(500, response.getCode()); 45 | } 46 | 47 | @Test 48 | public void testConnect_Connect() { 49 | Request request = Request.initialRequest(); 50 | Response response = request.execute(mailStore, message); 51 | assertEquals(220, response.getCode()); 52 | } 53 | 54 | @Test 55 | public void testConnect_NotConnectState() { 56 | Request request = new Request(new Connect(), null, SmtpState.GREET); 57 | Response response = request.execute(mailStore, message); 58 | assertEquals(503, response.getCode()); 59 | } 60 | 61 | @Test 62 | public void testEhlo_QuitState() { 63 | Request request = new Request(new Ehlo(), null, SmtpState.QUIT); 64 | Response response = request.execute(mailStore, message); 65 | assertEquals(503, response.getCode()); 66 | } 67 | 68 | @Test 69 | public void testEhlo_GreetState() { 70 | Request request = new Request(new Ehlo(), null, SmtpState.GREET); 71 | Response response = request.execute(mailStore, message); 72 | assertEquals(250, response.getCode()); 73 | } 74 | 75 | @Test 76 | public void testMail_MailState() { 77 | Request request = new Request(new Mail(), null, SmtpState.MAIL); 78 | Response response = request.execute(mailStore, message); 79 | assertEquals(250, response.getCode()); 80 | } 81 | 82 | @Test 83 | public void testMail_GreetState() { 84 | Request request = new Request(new Mail(), null, SmtpState.GREET); 85 | Response response = request.execute(mailStore, message); 86 | assertEquals(503, response.getCode()); 87 | } 88 | 89 | @Test 90 | public void testRcpt_RcptState() { 91 | Request request = new Request(new Rcpt(), null, SmtpState.RCPT); 92 | Response response = request.execute(mailStore, message); 93 | assertEquals(250, response.getCode()); 94 | } 95 | 96 | @Test 97 | public void testRcpt_Quit() { 98 | Request request = new Request(new Rcpt(), null, SmtpState.QUIT); 99 | Response response = request.execute(mailStore, message); 100 | assertEquals(503, response.getCode()); 101 | } 102 | 103 | @Test 104 | public void testData_Rcpt() { 105 | Request request = new Request(new Data(), null, SmtpState.RCPT); 106 | Response response = request.execute(mailStore, message); 107 | assertEquals(354, response.getCode()); 108 | } 109 | 110 | @Test 111 | public void testDataEnd_DataBody() { 112 | Request request = new Request(new DataEnd(), null, SmtpState.DATA_BODY); 113 | Response response = request.execute(mailStore, message); 114 | assertEquals(250, response.getCode()); 115 | } 116 | 117 | @Test 118 | public void testDataEnd_QUIT() { 119 | Request request = new Request(new DataEnd(), null, SmtpState.QUIT); 120 | Response response = request.execute(mailStore, message); 121 | assertEquals(503, response.getCode()); 122 | } 123 | 124 | @Test 125 | public void testQuit_QUIT() { 126 | Request request = new Request(new Quit(), null, SmtpState.QUIT); 127 | Response response = request.execute(mailStore, message); 128 | assertEquals(221, response.getCode()); 129 | } 130 | 131 | @Test 132 | public void testListNoParam() { 133 | Request request = new Request(new List(null), null, SmtpState.GREET); 134 | Response response = request.execute(mailStore, message); 135 | assertEquals(250, response.getCode()); 136 | assertEquals("There are 0 message(s).", response.getMessage()); 137 | assertEquals(SmtpState.GREET, response.getNextState()); 138 | } 139 | 140 | @Test 141 | public void testListWithMailListed() { 142 | message.appendBody("Hello, world!"); 143 | mailStore.addMessage(message); 144 | Request request = new Request(new List("0"), "0", SmtpState.GREET); 145 | Response response = request.execute(mailStore, message); 146 | assertEquals(250, response.getCode()); 147 | assertTrue(response.getMessage().contains("Hello, world!")); 148 | assertEquals(SmtpState.GREET, response.getNextState()); 149 | } 150 | 151 | @Test 152 | public void testData_RcptQuit() { 153 | Request request = new Request(new Data(), null, SmtpState.QUIT); 154 | Response response = request.execute(mailStore, message); 155 | assertEquals(503, response.getCode()); 156 | } 157 | 158 | @Test 159 | public void testBlankLine_DataHeader() { 160 | Request request = new Request(new BlankLine(), null, SmtpState.DATA_HDR); 161 | Response response = request.execute(mailStore, message); 162 | assertEquals(-1, response.getCode()); 163 | } 164 | 165 | @Test 166 | public void testDataHeaderAcceptsDot() { 167 | Request request = Request.createRequest(SmtpState.DATA_HDR, "."); 168 | assertEquals(".", request.getClientAction().toString()); 169 | } 170 | 171 | @Test 172 | public void testBlankLine_DataBody() { 173 | Request request = new Request(new BlankLine(), null, SmtpState.DATA_BODY); 174 | Response response = request.execute(mailStore, message); 175 | assertEquals(-1, response.getCode()); 176 | } 177 | 178 | @Test 179 | public void testBlankLine_Quit() { 180 | Request request = new Request(new BlankLine(), null, SmtpState.QUIT); 181 | Response response = request.execute(mailStore, message); 182 | assertEquals(503, response.getCode()); 183 | } 184 | 185 | @Test 186 | public void testRset() { 187 | Request request = new Request(new Rset(), null, null); 188 | Response response = request.execute(mailStore, message); 189 | assertEquals(250, response.getCode()); 190 | } 191 | 192 | @Test 193 | public void testVrfy() { 194 | Request request = new Request(new Vrfy(), null, null); 195 | Response response = request.execute(mailStore, message); 196 | assertEquals(252, response.getCode()); 197 | } 198 | 199 | @Test 200 | public void testExpn() { 201 | Request request = new Request(new Expn(), null, null); 202 | Response response = request.execute(mailStore, message); 203 | assertEquals(252, response.getCode()); 204 | } 205 | 206 | @Test 207 | public void testHelp() { 208 | Request request = new Request(new Help(), null, null); 209 | Response response = request.execute(mailStore, message); 210 | assertEquals(211, response.getCode()); 211 | } 212 | 213 | @Test 214 | public void testNoOp() { 215 | Request request = new Request(new NoOp(), null, null); 216 | Response response = request.execute(mailStore, message); 217 | assertEquals(250, response.getCode()); 218 | } 219 | 220 | @Test 221 | public void testUnrecognizedCommandGreetState() { 222 | Request request = new Request(new Unrecognized(), null, SmtpState.GREET); 223 | Response response = request.execute(mailStore, message); 224 | assertEquals(500, response.getCode()); 225 | } 226 | 227 | @Test 228 | public void testUnrecognizedCommandMailState() { 229 | Request request = new Request(new Unrecognized(), null, SmtpState.MAIL); 230 | Response response = request.execute(mailStore, message); 231 | assertEquals(500, response.getCode()); 232 | } 233 | 234 | @Test 235 | public void testUnrecognizedCommandQuitState() { 236 | Request request = new Request(new Unrecognized(), null, SmtpState.QUIT); 237 | Response response = request.execute(mailStore, message); 238 | assertEquals(500, response.getCode()); 239 | } 240 | 241 | @Test 242 | public void testUnrecognizedCommandRcptState() { 243 | Request request = new Request(new Unrecognized(), null, SmtpState.RCPT); 244 | Response response = request.execute(mailStore, message); 245 | assertEquals(500, response.getCode()); 246 | } 247 | 248 | @Test 249 | public void testUnrecognizedCommandDataBodyState() { 250 | Request request = new Request(new Unrecognized(), null, SmtpState.DATA_BODY); 251 | Response response = request.execute(mailStore, message); 252 | assertEquals(-1, response.getCode()); 253 | } 254 | 255 | @Test 256 | public void testUnrecognizedCommandDataHdrState() { 257 | Request request = new Request(new Unrecognized(), null, SmtpState.DATA_HDR); 258 | Response response = request.execute(mailStore, message); 259 | assertEquals(-1, response.getCode()); 260 | } 261 | 262 | @Test 263 | public void testRsetFromCreateRequest() { 264 | Request request = Request.createRequest(SmtpState.GREET, "RSET"); 265 | assertEquals("RSET", request.getClientAction().toString()); 266 | } 267 | 268 | @Test 269 | public void testNoopFromCreateRequest() { 270 | Request request = Request.createRequest(SmtpState.GREET, "NOOP"); 271 | assertEquals("NOOP", request.getClientAction().toString()); 272 | } 273 | 274 | @Test 275 | public void testExpnFromCreateRequest() { 276 | Request request = Request.createRequest(SmtpState.GREET, "EXPN"); 277 | assertEquals("EXPN", request.getClientAction().toString()); 278 | } 279 | 280 | @Test 281 | public void testVrfyFromCreateRequest() { 282 | Request request = Request.createRequest(SmtpState.GREET, "VRFY"); 283 | assertEquals("VRFY", request.getClientAction().toString()); 284 | } 285 | 286 | @Test 287 | public void testHelpFromCreateRequest() { 288 | Request request = Request.createRequest(SmtpState.GREET, "HELP"); 289 | assertEquals("HELP", request.getClientAction().toString()); 290 | } 291 | 292 | @Test 293 | public void testEhlo() { 294 | Request request = Request.createRequest(SmtpState.GREET, "EHLO "); 295 | assertEquals("EHLO", request.getClientAction().toString()); 296 | } 297 | 298 | @Test 299 | public void testHelo() { 300 | Request request = Request.createRequest(SmtpState.GREET, "HELO"); 301 | assertEquals("EHLO", request.getClientAction().toString()); 302 | } 303 | 304 | } 305 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 43 | 44 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 126 | 127 | 147 | 148 | 149 | 151 | 152 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 196 | 197 | 198 | 199 | 202 | 203 | 206 | 207 | 208 | 209 | 212 | 213 | 216 | 217 | 220 | 221 | 224 | 225 | 226 | 227 | 230 | 231 | 234 | 235 | 238 | 239 | 242 | 243 | 244 | 245 | 248 | 249 | 252 | 253 | 256 | 257 | 260 | 261 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 295 | 296 | 297 | 298 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 323 | 324 | 325 | 326 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 351 | 352 | 353 | 354 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 381 | 382 | 383 | 384 | 389 | 390 | 391 | 392 | 393 | 394 | 399 | 400 | 401 | 412 | 413 | 414 | 415 | 416 | 434 | 446 | 447 | 448 | 464 | 465 | 466 | 467 | 485 | 486 | 487 | 493 | 494 | 495 | 496 | 497 | 498 | 512 | 513 | 514 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | localhost 527 | 5050 528 | 529 | 530 | 531 | 532 | 548 | 549 | 550 | 551 | 1284146615346 552 | 1284146615346 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 609 | 610 | 611 | 612 | 613 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 754 | 755 | 756 | 1.6 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | --------------------------------------------------------------------------------