Skip to content

Commit 3431a9e

Browse files
committedJun 17, 2020
427: Allow bridging PR updates to multiple recipients
Reviewed-by: ehelin
1 parent 9af4807 commit 3431a9e

File tree

13 files changed

+339
-108
lines changed

13 files changed

+339
-108
lines changed
 

‎bots/mlbridge/src/main/java/org/openjdk/skara/bots/mlbridge/ArchiveWorkItem.java

+22-3
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,10 @@ public Collection<WorkItem> run(Path scratchPath) {
239239
var mbox = MailingListServerFactory.createMboxFileServer(mboxBasePath);
240240
var reviewArchiveList = mbox.getList(pr.id());
241241
var sentMails = parseArchive(reviewArchiveList);
242+
var labels = new HashSet<>(pr.labels());
242243

243244
// First determine if this PR should be inspected further or not
244245
if (sentMails.isEmpty()) {
245-
var labels = new HashSet<>(pr.labels());
246-
247246
if (pr.state() == Issue.State.OPEN) {
248247
for (var readyLabel : bot.readyLabels()) {
249248
if (!labels.contains(readyLabel)) {
@@ -281,6 +280,25 @@ public Collection<WorkItem> run(Path scratchPath) {
281280
}
282281
}
283282

283+
// Determine recipient list(s)
284+
var recipients = new ArrayList<EmailAddress>();
285+
for (var candidateList : bot.lists()) {
286+
if (candidateList.labels().isEmpty()) {
287+
recipients.add(candidateList.list());
288+
continue;
289+
}
290+
for (var label : labels) {
291+
if (candidateList.labels().contains(label)) {
292+
recipients.add(candidateList.list());
293+
break;
294+
}
295+
}
296+
}
297+
if (recipients.isEmpty()) {
298+
log.severe("PR does not match any recipient list: " + pr.repository().name() + "#" + pr.id());
299+
return List.of();
300+
}
301+
284302
var census = CensusInstance.create(bot.censusRepo(), bot.censusRef(), scratchPath.resolve("census"), pr);
285303
var jbs = census.configuration().general().jbs();
286304
if (jbs == null) {
@@ -297,7 +315,7 @@ public Collection<WorkItem> run(Path scratchPath) {
297315

298316
var webrevPath = scratchPath.resolve("mlbridge-webrevs");
299317
var listServer = MailingListServerFactory.createMailmanServer(bot.listArchive(), bot.smtpServer(), bot.sendInterval());
300-
var list = listServer.getList(bot.listAddress().address());
318+
var list = listServer.getList(recipients.get(0).toString());
301319

302320
var archiver = new ReviewArchive(pr, bot.emailAddress());
303321

@@ -354,6 +372,7 @@ public Collection<WorkItem> run(Path scratchPath) {
354372
var filteredEmail = Email.from(newMail)
355373
.replaceHeaders(filteredHeaders)
356374
.headers(bot.headers())
375+
.recipients(recipients)
357376
.build();
358377
list.post(filteredEmail);
359378
}

‎bots/mlbridge/src/main/java/org/openjdk/skara/bots/mlbridge/MailingListBridgeBot.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class MailingListBridgeBot implements Bot {
4040
private final String archiveRef;
4141
private final HostedRepository censusRepo;
4242
private final String censusRef;
43-
private final EmailAddress listAddress;
43+
private final List<MailingListConfiguration> lists;
4444
private final Set<String> ignoredUsers;
4545
private final Set<Pattern> ignoredComments;
4646
private final URI listArchive;
@@ -64,7 +64,7 @@ public class MailingListBridgeBot implements Bot {
6464
private ZonedDateTime lastFullUpdate;
6565

6666
MailingListBridgeBot(EmailAddress from, HostedRepository repo, HostedRepository archive, String archiveRef,
67-
HostedRepository censusRepo, String censusRef, EmailAddress list,
67+
HostedRepository censusRepo, String censusRef, List<MailingListConfiguration> lists,
6868
Set<String> ignoredUsers, Set<Pattern> ignoredComments, URI listArchive, String smtpServer,
6969
HostedRepository webrevStorageRepository, String webrevStorageRef,
7070
Path webrevStorageBase, URI webrevStorageBaseUri, Set<String> readyLabels,
@@ -77,7 +77,7 @@ public class MailingListBridgeBot implements Bot {
7777
this.archiveRef = archiveRef;
7878
this.censusRepo = censusRepo;
7979
this.censusRef = censusRef;
80-
listAddress = list;
80+
this.lists = lists;
8181
this.ignoredUsers = ignoredUsers;
8282
this.ignoredComments = ignoredComments;
8383
this.listArchive = listArchive;
@@ -126,8 +126,8 @@ EmailAddress emailAddress() {
126126
return emailAddress;
127127
}
128128

129-
EmailAddress listAddress() {
130-
return listAddress;
129+
List<MailingListConfiguration> lists() {
130+
return lists;
131131
}
132132

133133
Duration sendInterval() {

‎bots/mlbridge/src/main/java/org/openjdk/skara/bots/mlbridge/MailingListBridgeBotBuilder.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class MailingListBridgeBotBuilder {
3838
private String archiveRef = "master";
3939
private HostedRepository censusRepo;
4040
private String censusRef = "master";
41-
private EmailAddress list;
41+
private List<MailingListConfiguration> lists;
4242
private Set<String> ignoredUsers = Set.of();
4343
private Set<Pattern> ignoredComments = Set.of();
4444
private URI listArchive;
@@ -90,8 +90,8 @@ public MailingListBridgeBotBuilder censusRef(String censusRef) {
9090
return this;
9191
}
9292

93-
public MailingListBridgeBotBuilder list(EmailAddress list) {
94-
this.list = list;
93+
public MailingListBridgeBotBuilder lists(List<MailingListConfiguration> lists) {
94+
this.lists = lists;
9595
return this;
9696
}
9797

@@ -181,7 +181,7 @@ public MailingListBridgeBotBuilder seedStorage(Path seedStorage) {
181181
}
182182

183183
public MailingListBridgeBot build() {
184-
return new MailingListBridgeBot(from, repo, archive, archiveRef, censusRepo, censusRef, list,
184+
return new MailingListBridgeBot(from, repo, archive, archiveRef, censusRepo, censusRef, lists,
185185
ignoredUsers, ignoredComments, listArchive, smtpServer,
186186
webrevStorageRepository, webrevStorageRef, webrevStorageBase, webrevStorageBaseUri,
187187
readyLabels, readyComments, issueTracker, headers, sendInterval, cooldown,

‎bots/mlbridge/src/main/java/org/openjdk/skara/bots/mlbridge/MailingListBridgeBotFactory.java

+26-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ public String name() {
4141
return "mlbridge";
4242
}
4343

44+
private MailingListConfiguration parseList(JSONObject configuration) {
45+
var listAddress = EmailAddress.parse(configuration.get("email").asString());
46+
Set<String> labels = configuration.contains("labels") ?
47+
configuration.get("labels").stream()
48+
.map(JSONValue::asString)
49+
.collect(Collectors.toSet()) :
50+
Set.of();
51+
return new MailingListConfiguration(listAddress, labels);
52+
}
53+
54+
private List<MailingListConfiguration> parseLists(JSONValue configuration) {
55+
if (configuration.isArray()) {
56+
return configuration.stream()
57+
.map(JSONValue::asObject)
58+
.map(this::parseList)
59+
.collect(Collectors.toList());
60+
} else {
61+
return List.of(parseList(configuration.asObject()));
62+
}
63+
}
64+
4465
@Override
4566
public List<Bot> create(BotConfiguration configuration) {
4667
var ret = new ArrayList<Bot>();
@@ -87,16 +108,15 @@ public List<Bot> create(BotConfiguration configuration) {
87108
repoConfig.get("headers").fields().stream()
88109
.collect(Collectors.toMap(JSONObject.Field::name, field -> field.value().asString())) :
89110
Map.of();
90-
91-
var list = EmailAddress.parse(repoConfig.get("list").asString());
111+
var lists = parseLists(repoConfig.get("lists"));
92112
var folder = repoConfig.contains("folder") ? repoConfig.get("folder").asString() : configuration.repositoryName(repo);
93113
var botBuilder = MailingListBridgeBot.newBuilder().from(from)
94114
.repo(configuration.repository(repo))
95115
.archive(archiveRepo)
96116
.archiveRef(archiveRef)
97117
.censusRepo(censusRepo)
98118
.censusRef(censusRef)
99-
.list(list)
119+
.lists(lists)
100120
.ignoredUsers(ignoredUsers)
101121
.ignoredComments(ignoredComments)
102122
.listArchive(listArchive)
@@ -122,7 +142,9 @@ public List<Bot> create(BotConfiguration configuration) {
122142
ret.add(botBuilder.build());
123143

124144
if (!repoConfig.contains("bidirectional") || repoConfig.get("bidirectional").asBoolean()) {
125-
listNamesForReading.add(list);
145+
for (var list : lists) {
146+
listNamesForReading.add(list.list());
147+
}
126148
}
127149
allRepositories.add(configuration.repository(repo));
128150
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
package org.openjdk.skara.bots.mlbridge;
24+
25+
import org.openjdk.skara.email.EmailAddress;
26+
27+
import java.util.*;
28+
29+
class MailingListConfiguration {
30+
private EmailAddress list;
31+
private Set<String> labels;
32+
33+
MailingListConfiguration(EmailAddress list, Set<String> labels) {
34+
this.list = list;
35+
this.labels = labels;
36+
}
37+
38+
EmailAddress list() {
39+
return list;
40+
}
41+
42+
Set<String> labels() {
43+
return labels;
44+
}
45+
}

‎bots/mlbridge/src/test/java/org/openjdk/skara/bots/mlbridge/MailingListArchiveReaderBotTests.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,20 @@
3838
import static org.junit.jupiter.api.Assertions.*;
3939

4040
class MailingListArchiveReaderBotTests {
41-
private void addReply(Conversation conversation, MailingList mailingList, PullRequest pr, String reply) {
41+
private void addReply(Conversation conversation, EmailAddress recipient, MailingList mailingList, PullRequest pr, String reply) {
4242
var first = conversation.first();
4343
var references = first.id().toString();
4444
var email = Email.create(EmailAddress.from("Commenter", "c@test.test"), "Re: RFR: " + pr.title(), reply)
45-
.recipient(first.author())
45+
.recipient(recipient)
4646
.id(EmailAddress.from(UUID.randomUUID() + "@id.id"))
4747
.header("In-Reply-To", first.id().toString())
4848
.header("References", references)
4949
.build();
5050
mailingList.post(email);
5151
}
5252

53-
private void addReply(Conversation conversation, MailingList mailingList, PullRequest pr) {
54-
addReply(conversation, mailingList, pr, "Looks good");
53+
private void addReply(Conversation conversation, EmailAddress recipient, MailingList mailingList, PullRequest pr) {
54+
addReply(conversation, recipient, mailingList, pr, "Looks good");
5555
}
5656

5757
@Test
@@ -72,7 +72,7 @@ void simpleArchive(TestInfo testInfo) throws IOException {
7272
.repo(author)
7373
.archive(archive)
7474
.censusRepo(censusBuilder.build())
75-
.list(listAddress)
75+
.lists(List.of(new MailingListConfiguration(listAddress, Set.of())))
7676
.ignoredUsers(Set.of(ignored.forge().currentUser().userName()))
7777
.listArchive(listServer.getArchive())
7878
.smtpServer(listServer.getSMTP())
@@ -113,7 +113,7 @@ void simpleArchive(TestInfo testInfo) throws IOException {
113113
// Post a reply directly to the list
114114
var conversations = mailmanList.conversations(Duration.ofDays(1));
115115
assertEquals(1, conversations.size());
116-
addReply(conversations.get(0), mailmanList, pr);
116+
addReply(conversations.get(0), listAddress, mailmanList, pr);
117117
listServer.processIncoming();
118118

119119
// Another archive reader pass - has to be done twice
@@ -147,7 +147,7 @@ void rememberBridged(TestInfo testInfo) throws IOException {
147147
.repo(author)
148148
.archive(archive)
149149
.censusRepo(censusBuilder.build())
150-
.list(listAddress)
150+
.lists(List.of(new MailingListConfiguration(listAddress, Set.of())))
151151
.ignoredUsers(Set.of(ignored.forge().currentUser().userName()))
152152
.listArchive(listServer.getArchive())
153153
.smtpServer(listServer.getSMTP())
@@ -185,7 +185,7 @@ void rememberBridged(TestInfo testInfo) throws IOException {
185185
// Post a reply directly to the list
186186
var conversations = mailmanList.conversations(Duration.ofDays(1));
187187
assertEquals(1, conversations.size());
188-
addReply(conversations.get(0), mailmanList, pr);
188+
addReply(conversations.get(0), listAddress, mailmanList, pr);
189189
listServer.processIncoming();
190190

191191
// Another archive reader pass - has to be done twice
@@ -224,7 +224,7 @@ void largeEmail(TestInfo testInfo) throws IOException {
224224
.repo(author)
225225
.archive(archive)
226226
.censusRepo(censusBuilder.build())
227-
.list(listAddress)
227+
.lists(List.of(new MailingListConfiguration(listAddress, Set.of())))
228228
.ignoredUsers(Set.of(ignored.forge().currentUser().userName()))
229229
.listArchive(listServer.getArchive())
230230
.smtpServer(listServer.getSMTP())
@@ -267,7 +267,7 @@ void largeEmail(TestInfo testInfo) throws IOException {
267267
assertEquals(1, conversations.size());
268268

269269
var replyBody = "This line is about 30 bytes long\n".repeat(1000 * 10);
270-
addReply(conversations.get(0), mailmanList, pr, replyBody);
270+
addReply(conversations.get(0), listAddress, mailmanList, pr, replyBody);
271271
listServer.processIncoming();
272272

273273
// Another archive reader pass - has to be done twice

‎bots/mlbridge/src/test/java/org/openjdk/skara/bots/mlbridge/MailingListBridgeBotTests.java

+125-47
Large diffs are not rendered by default.

‎email/src/main/java/org/openjdk/skara/email/Email.java

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public static Email parse(String raw) {
9797
if (message.headers.containsKey("To")) {
9898
recipients = Arrays.stream(message.headers.get("To").split(","))
9999
.map(MimeText::decode)
100+
.map(String::strip)
100101
.map(EmailAddress::parse)
101102
.collect(Collectors.toList());
102103
} else {

‎email/src/main/java/org/openjdk/skara/email/SMTP.java

+14-5
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,24 @@ public class SMTP {
4242
private static Pattern dataReply = Pattern.compile("^354 .*");
4343
private static Pattern doneReply = Pattern.compile("^250 .*");
4444

45-
public static void send(String server, EmailAddress recipient, Email email) throws IOException {
46-
send(server, recipient, email, Duration.ofMinutes(30));
45+
public static void send(String server, Email email) throws IOException {
46+
send(server, email, Duration.ofMinutes(30));
4747
}
4848

49-
public static void send(String server, EmailAddress recipient, Email email, Duration timeout) throws IOException {
49+
public static void send(String server, Email email, Duration timeout) throws IOException {
50+
if (email.recipients().isEmpty()) {
51+
throw new IllegalArgumentException("Attempting to send an email without recipients");
52+
}
5053
var port = 25;
5154
if (server.contains(":")) {
5255
var parts = server.split(":", 2);
5356
server = parts[0];
5457
port = Integer.parseInt(parts[1]);
5558
}
59+
var recipientList = email.recipients().stream()
60+
.map(EmailAddress::toString)
61+
.map(MimeText::encode)
62+
.collect(Collectors.joining(", "));
5663
try (var socket = new Socket(server, port);
5764
var out = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8);
5865
var in = new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)) {
@@ -62,13 +69,15 @@ public static void send(String server, EmailAddress recipient, Email email, Dura
6269
session.waitForPattern(initReply);
6370
session.sendCommand("EHLO " + email.sender().domain(), ehloReply);
6471
session.sendCommand("MAIL FROM:" + email.sender().address(), mailReply);
65-
session.sendCommand("RCPT TO:<" + recipient.address() + ">", rcptReply);
72+
for (var recipient : email.recipients()) {
73+
session.sendCommand("RCPT TO:<" + recipient.address() + ">", rcptReply);
74+
}
6675
session.sendCommand("DATA", dataReply);
6776
session.sendCommand("From: " + MimeText.encode(email.author().toString()));
6877
session.sendCommand("Message-Id: " + email.id());
6978
session.sendCommand("Date: " + email.date().format(DateTimeFormatter.RFC_1123_DATE_TIME));
7079
session.sendCommand("Sender: " + MimeText.encode(email.sender().toString()));
71-
session.sendCommand("To: " + MimeText.encode(recipient.toString()));
80+
session.sendCommand("To: " + recipientList);
7281
for (var header : email.headers()) {
7382
session.sendCommand(header + ": " + MimeText.encode(email.headerValue(header)));
7483
}

‎email/src/test/java/org/openjdk/skara/email/SMTPTests.java

+23-7
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
*/
2323
package org.openjdk.skara.email;
2424

25-
import org.openjdk.skara.test.SMTPServer;
26-
2725
import org.junit.jupiter.api.Test;
2826
import org.junit.jupiter.api.condition.*;
27+
import org.openjdk.skara.test.SMTPServer;
2928

3029
import java.io.IOException;
3130
import java.time.Duration;
31+
import java.util.List;
3232

3333
import static org.junit.jupiter.api.Assertions.*;
3434

@@ -40,7 +40,7 @@ void simple() throws IOException {
4040
var recipient = EmailAddress.from("Dest", "dest@dest.email");
4141
var sentMail = Email.create(sender, "Subject", "Body").recipient(recipient).build();
4242

43-
SMTP.send(server.address(), recipient, sentMail);
43+
SMTP.send(server.address(), sentMail);
4444
var email = server.receive(Duration.ofSeconds(10));
4545
assertEquals(sentMail, email);
4646
}
@@ -58,7 +58,7 @@ void withHeader() throws IOException {
5858
.header("Something", "Other")
5959
.build();
6060

61-
SMTP.send(server.address(), recipient, sentMail);
61+
SMTP.send(server.address(), sentMail);
6262
var email = server.receive(Duration.ofSeconds(10));
6363
assertEquals(sentMail, email);
6464
}
@@ -75,7 +75,7 @@ void encoded() throws IOException {
7575
.header("Something", "Öthè®")
7676
.build();
7777

78-
SMTP.send(server.address(), recipient, sentMail);
78+
SMTP.send(server.address(), sentMail);
7979
var email = server.receive(Duration.ofSeconds(10));
8080
assertEquals(sentMail, email);
8181
}
@@ -88,7 +88,7 @@ void timeout() throws IOException {
8888
var recipient = EmailAddress.from("Dest", "dest@dest.email");
8989
var sentMail = Email.create(sender, "Subject", "Body").recipient(recipient).build();
9090

91-
assertThrows(RuntimeException.class, () -> SMTP.send(server.address(), recipient, sentMail, Duration.ZERO));
91+
assertThrows(RuntimeException.class, () -> SMTP.send(server.address(), sentMail, Duration.ZERO));
9292
}
9393
}
9494

@@ -99,7 +99,23 @@ void withDot() throws IOException {
9999
var recipient = EmailAddress.from("Dest", "dest@dest.email");
100100
var sentMail = Email.create(sender, "Subject", "Body\n.\nMore text").recipient(recipient).build();
101101

102-
SMTP.send(server.address(), recipient, sentMail);
102+
SMTP.send(server.address(), sentMail);
103+
var email = server.receive(Duration.ofSeconds(10));
104+
assertEquals(sentMail, email);
105+
}
106+
}
107+
108+
@Test
109+
void multipleRecipients() throws IOException {
110+
try (var server = new SMTPServer()) {
111+
var sender = EmailAddress.from("Test", "test@test.email");
112+
var recipient1 = EmailAddress.from("Dest1", "dest1@dest.email");
113+
var recipient2 = EmailAddress.from("Dest2", "dest2@dest.email");
114+
var sentMail = Email.create(sender, "Subject", "Body")
115+
.recipients(List.of(recipient1, recipient2))
116+
.build();
117+
118+
SMTP.send(server.address(), sentMail);
103119
var email = server.receive(Duration.ofSeconds(10));
104120
assertEquals(sentMail, email);
105121
}

‎mailinglist/src/main/java/org/openjdk/skara/mailinglist/mailman/MailmanList.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public String toString() {
5656

5757
@Override
5858
public void post(Email email) {
59-
server.sendMessage(listAddress, email);
59+
server.sendMessage(email);
6060
}
6161

6262
private List<ZonedDateTime> getMonthRange(Duration maxAge) {

‎mailinglist/src/main/java/org/openjdk/skara/mailinglist/mailman/MailmanServer.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ URI getMbox(String listName, ZonedDateTime month) {
5050
return URIBuilder.base(archive).appendPath(listName + "/" + dateStr + ".txt").build();
5151
}
5252

53-
void sendMessage(EmailAddress recipientList, Email message) {
53+
void sendMessage(Email message) {
5454
while (lastSend.plus(sendInterval).isAfter(Instant.now())) {
5555
try {
5656
Thread.sleep(sendInterval.dividedBy(10).toMillis());
@@ -59,7 +59,7 @@ void sendMessage(EmailAddress recipientList, Email message) {
5959
}
6060
lastSend = Instant.now();
6161
try {
62-
SMTP.send(smtpServer, recipientList, message);
62+
SMTP.send(smtpServer, message);
6363
} catch (IOException e) {
6464
throw new UncheckedIOException(e);
6565
}

‎test/src/main/java/org/openjdk/skara/test/SMTPServer.java

+61-20
Original file line numberDiff line numberDiff line change
@@ -27,33 +27,76 @@
2727
import java.io.*;
2828
import java.net.*;
2929
import java.time.*;
30+
import java.util.ArrayList;
3031
import java.util.concurrent.ConcurrentLinkedDeque;
32+
import java.util.logging.Logger;
3133
import java.util.regex.Pattern;
3234

3335
public class SMTPServer implements AutoCloseable {
3436
private final ServerSocket serverSocket;
35-
private final Thread acceptThread;
3637
private final ConcurrentLinkedDeque<Email> emails = new ConcurrentLinkedDeque<>();
3738

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-
39+
private final static Logger log = Logger.getLogger("org.openjdk.skara.test");;
40+
private final static Pattern commandPattern = Pattern.compile("^([A-Z]+) ?(.*)$");
4541
private final static Pattern encodeQuotedPrintablePattern = Pattern.compile("([^\\x00-\\x7f]+)");
4642
private final static Pattern headerPattern = Pattern.compile("[^A-Za-z0-9-]+: .+");
4743

4844
private class AcceptThread implements Runnable {
49-
private void handleSession(SMTPSession session) throws IOException {
50-
session.sendCommand("220 localhost SMTP", ehloPattern);
51-
session.sendCommand("250 HELP", mailFromPattern);
52-
session.sendCommand("250 FROM OK", rcptToPattern);
53-
session.sendCommand("250 RCPT OK", dataPattern);
54-
session.sendCommand("354 Enter message now, end with .");
55-
var message = session.readLinesUntil(messageEndPattern);
56-
session.sendCommand("250 MESSAGE OK", quitPattern);
45+
private void sendLine(String line, BufferedWriter out) throws IOException {
46+
log.fine("> " + line);
47+
out.write(line + "\n");
48+
out.flush();
49+
}
50+
51+
private String readLine(BufferedReader in) throws IOException {
52+
while (!in.ready()) {
53+
try {
54+
Thread.sleep(10);
55+
} catch (InterruptedException ignored) {
56+
}
57+
}
58+
var line = in.readLine();
59+
log.fine("< " + line);
60+
return line;
61+
}
62+
63+
private void handleSession(BufferedReader in, BufferedWriter out) throws IOException {
64+
sendLine("220 localhost SMTP", out);
65+
var message = new ArrayList<String>();
66+
var done = false;
67+
while (!done) {
68+
var line = readLine(in);
69+
var commandMatcher = commandPattern.matcher(line);
70+
if (!commandMatcher.matches()) {
71+
throw new RuntimeException("Illegal input: " + line);
72+
}
73+
switch (commandMatcher.group(1)) {
74+
case "EHLO":
75+
sendLine("250 HELP", out);
76+
break;
77+
case "MAIL":
78+
sendLine("250 FROM OK", out);
79+
break;
80+
case "RCPT":
81+
sendLine("250 RCPT OK", out);
82+
break;
83+
case "DATA":
84+
sendLine("354 Enter message now, end with .", out);
85+
while (true) {
86+
var messageLine = readLine(in);
87+
if (messageLine.equals(".")) {
88+
sendLine("250 MESSAGE OK", out);
89+
break;
90+
}
91+
message.add(messageLine);
92+
}
93+
break;
94+
case "QUIT":
95+
sendLine("BYE", out);
96+
done = true;
97+
break;
98+
}
99+
}
57100

58101
// Email headers are only 7-bit safe, ensure that we break any high ascii passing through
59102
var inHeader = true;
@@ -89,8 +132,7 @@ public void run() {
89132
try (var socket = serverSocket.accept();
90133
var input = new InputStreamReader(socket.getInputStream());
91134
var output = new OutputStreamWriter(socket.getOutputStream())) {
92-
var session = new SMTPSession(input, output, Duration.ofMinutes(10));
93-
handleSession(session);
135+
handleSession(new BufferedReader(input), new BufferedWriter(output));
94136
}
95137
} catch (SocketException e) {
96138
// Socket closed
@@ -103,12 +145,11 @@ public void run() {
103145

104146
public SMTPServer() throws IOException {
105147
serverSocket = new ServerSocket(0);
106-
acceptThread = new Thread(new AcceptThread());
148+
var acceptThread = new Thread(new AcceptThread());
107149
acceptThread.start();
108150
}
109151

110152
public String address() {
111-
var host = serverSocket.getInetAddress();
112153
return InetAddress.getLoopbackAddress().getHostAddress() + ":" + serverSocket.getLocalPort();
113154
}
114155

1 commit comments

Comments
 (1)

bridgekeeper[bot] commented on Jun 17, 2020

@bridgekeeper[bot]
Please sign in to comment.