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 |
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 |
39 |
40 |
41 |
42 |
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 |
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 |
127 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
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 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 | localhost
527 | 5050
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 | $PROJECT_DIR$/../../.subversion_IDEA
545 | false
546 | 125
547 |
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 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
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 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 | 1.6
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
--------------------------------------------------------------------------------