25 import org.openjdk.skara.email.*;
26
27 import java.io.*;
28 import java.net.*;
29 import java.time.*;
30 import java.util.concurrent.ConcurrentLinkedDeque;
31 import java.util.regex.Pattern;
32
33 public class SMTPServer implements AutoCloseable {
34 private final ServerSocket serverSocket;
35 private final Thread acceptThread;
36 private final ConcurrentLinkedDeque<Email> emails = new ConcurrentLinkedDeque<>();
37
38 private static Pattern ehloPattern = Pattern.compile("^EHLO .*$");
39 private static Pattern mailFromPattern = Pattern.compile("^MAIL FROM:.*$");
40 private static Pattern rcptToPattern = Pattern.compile("^RCPT TO:<.*$");
41 private static Pattern dataPattern = Pattern.compile("^DATA$");
42 private static Pattern messageEndPattern = Pattern.compile("^\\.$");
43 private static Pattern quitPattern = Pattern.compile("^QUIT$");
44
45 private class AcceptThread implements Runnable {
46 private void handleSession(SMTPSession session) throws IOException {
47 session.sendCommand("220 localhost SMTP", ehloPattern);
48 session.sendCommand("250 HELP", mailFromPattern);
49 session.sendCommand("250 FROM OK", rcptToPattern);
50 session.sendCommand("250 RCPT OK", dataPattern);
51 session.sendCommand("354 Enter message now, end with .");
52 var message = session.readLinesUntil(messageEndPattern);
53 session.sendCommand("250 MESSAGE OK", quitPattern);
54
55 var email = Email.parse(String.join("\n", message));
56 emails.addLast(email);
57 }
58
59 @Override
60 public void run() {
61 while (!serverSocket.isClosed()) {
62 try {
63 try (var socket = serverSocket.accept();
64 var input = new InputStreamReader(socket.getInputStream());
65 var output = new OutputStreamWriter(socket.getOutputStream())) {
66 var session = new SMTPSession(input, output);
67 handleSession(session);
68 }
69 } catch (SocketException e) {
70 // Socket closed
71 } catch (IOException e) {
72 throw new UncheckedIOException(e);
73 }
74 }
75 }
|
25 import org.openjdk.skara.email.*;
26
27 import java.io.*;
28 import java.net.*;
29 import java.time.*;
30 import java.util.concurrent.ConcurrentLinkedDeque;
31 import java.util.regex.Pattern;
32
33 public class SMTPServer implements AutoCloseable {
34 private final ServerSocket serverSocket;
35 private final Thread acceptThread;
36 private final ConcurrentLinkedDeque<Email> emails = new ConcurrentLinkedDeque<>();
37
38 private static Pattern ehloPattern = Pattern.compile("^EHLO .*$");
39 private static Pattern mailFromPattern = Pattern.compile("^MAIL FROM:.*$");
40 private static Pattern rcptToPattern = Pattern.compile("^RCPT TO:<.*$");
41 private static Pattern dataPattern = Pattern.compile("^DATA$");
42 private static Pattern messageEndPattern = Pattern.compile("^\\.$");
43 private static Pattern quitPattern = Pattern.compile("^QUIT$");
44
45 private final static Pattern encodeQuotedPrintablePattern = Pattern.compile("([^\\x00-\\x7f]+)");
46
47 private class AcceptThread implements Runnable {
48 private void handleSession(SMTPSession session) throws IOException {
49 session.sendCommand("220 localhost SMTP", ehloPattern);
50 session.sendCommand("250 HELP", mailFromPattern);
51 session.sendCommand("250 FROM OK", rcptToPattern);
52 session.sendCommand("250 RCPT OK", dataPattern);
53 session.sendCommand("354 Enter message now, end with .");
54 var message = session.readLinesUntil(messageEndPattern);
55 session.sendCommand("250 MESSAGE OK", quitPattern);
56
57 // SMTP is only 7-bit safe, ensure that we break any high ascii passing through here
58 var quoteMatcher = encodeQuotedPrintablePattern.matcher(String.join("\n", message));
59 var ascii7message = quoteMatcher.replaceAll(mo -> "HIGH_ASCII");
60
61 var email = Email.parse(ascii7message);
62 emails.addLast(email);
63 }
64
65 @Override
66 public void run() {
67 while (!serverSocket.isClosed()) {
68 try {
69 try (var socket = serverSocket.accept();
70 var input = new InputStreamReader(socket.getInputStream());
71 var output = new OutputStreamWriter(socket.getOutputStream())) {
72 var session = new SMTPSession(input, output);
73 handleSession(session);
74 }
75 } catch (SocketException e) {
76 // Socket closed
77 } catch (IOException e) {
78 throw new UncheckedIOException(e);
79 }
80 }
81 }
|