Add POP3 implementation for receiving emails
This change adds code to retrieve and delete emails using POP3 as well as a lightweight implementation to receive the Message-ID from a raw email. It uses Greenmail for integration testing and tests if Gerrit can delete emails which implies that it can also read emails. Change-Id: Ifa20a5d77428759d85b24b2fe2c161e72f81c991
This commit is contained in:
@@ -49,10 +49,12 @@ java_library2(
|
||||
'//lib:truth',
|
||||
],
|
||||
deps = PROVIDED + [ # We want these deps to be exported_deps
|
||||
'//lib/greenmail:greenmail',
|
||||
'//lib:gwtorm',
|
||||
'//lib/guice:guice',
|
||||
'//lib/guice:guice-assistedinject',
|
||||
'//lib/guice:guice-servlet',
|
||||
'//lib/mail:mail',
|
||||
],
|
||||
visibility = ['//visibility:public'],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
include_defs('//gerrit-acceptance-tests/tests.defs')
|
||||
|
||||
acceptance_tests(
|
||||
group = 'server_mail',
|
||||
srcs = glob(['*IT.java']),
|
||||
labels = ['server'],
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
load("//gerrit-acceptance-tests:tests.bzl", "acceptance_tests")
|
||||
|
||||
acceptance_tests(
|
||||
srcs = glob(["*IT.java"]),
|
||||
group = "server_mail",
|
||||
labels = ["server"],
|
||||
deps = [
|
||||
"//lib/greenmail",
|
||||
"//lib/mail",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.acceptance.server.mail;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.GerritConfig;
|
||||
import com.google.gerrit.acceptance.GerritConfigs;
|
||||
import com.google.gerrit.acceptance.NoHttpd;
|
||||
import com.google.gerrit.server.mail.receive.MailReceiver;
|
||||
import com.google.gerrit.testutil.ConfigSuite;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import com.icegreen.greenmail.junit.GreenMailRule;
|
||||
import com.icegreen.greenmail.user.GreenMailUser;
|
||||
import com.icegreen.greenmail.util.GreenMail;
|
||||
import com.icegreen.greenmail.util.GreenMailUtil;
|
||||
import com.icegreen.greenmail.util.ServerSetupTest;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
||||
@NoHttpd
|
||||
@RunWith(ConfigSuite.class)
|
||||
public class MailIT extends AbstractDaemonTest {
|
||||
private final static String RECEIVEEMAIL = "receiveemail";
|
||||
private final static String HOST = "localhost";
|
||||
private final static String USERNAME = "user@domain.com";
|
||||
private final static String PASSWORD = "password";
|
||||
private final static String PROTOCOL = "POP3";
|
||||
|
||||
@Inject
|
||||
private MailReceiver mailReceiver;
|
||||
|
||||
@Inject
|
||||
private GreenMail greenMail;
|
||||
|
||||
@Rule
|
||||
public final GreenMailRule mockPop3Server = new GreenMailRule(
|
||||
ServerSetupTest.SMTP_POP3_IMAP);
|
||||
|
||||
@ConfigSuite.Default
|
||||
public static Config pop3Config() {
|
||||
Config cfg = new Config();
|
||||
cfg.setString(RECEIVEEMAIL, null, "host", HOST);
|
||||
cfg.setString(RECEIVEEMAIL, null, "port", "3110");
|
||||
cfg.setString(RECEIVEEMAIL, null, "username", USERNAME);
|
||||
cfg.setString(RECEIVEEMAIL, null, "password", PASSWORD);
|
||||
cfg.setString(RECEIVEEMAIL, null, "protocol", PROTOCOL);
|
||||
cfg.setString(RECEIVEEMAIL, null, "fetchInterval", "99");
|
||||
return cfg;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() throws Exception {
|
||||
GreenMailUser user = mockPop3Server.setUser(USERNAME, USERNAME, PASSWORD);
|
||||
user.deliver(createSimpleMessage());
|
||||
assertThat(mockPop3Server.getReceivedMessages().length).isEqualTo(1);
|
||||
// Let Gerrit handle emails
|
||||
mailReceiver.handleEmails();
|
||||
// Check that the message is still present
|
||||
assertThat(mockPop3Server.getReceivedMessages().length).isEqualTo(1);
|
||||
// Mark the message for deletion
|
||||
mailReceiver.requestDeletion(
|
||||
mockPop3Server.getReceivedMessages()[0].getMessageID());
|
||||
// Let Gerrit handle emails
|
||||
mailReceiver.handleEmails();
|
||||
// Check that the message was deleted
|
||||
assertThat(mockPop3Server.getReceivedMessages().length).isEqualTo(0);
|
||||
}
|
||||
|
||||
private MimeMessage createSimpleMessage() {
|
||||
return GreenMailUtil
|
||||
.createTextEmail(USERNAME, "from@localhost.com", "subject",
|
||||
"body",
|
||||
greenMail.getImap().getServerSetup());
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public class ImapMailReceiver extends MailReceiver {
|
||||
* where deletion is pending, read new email and close the connection.
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void handleEmails() {
|
||||
public synchronized void handleEmails() {
|
||||
// TODO(hiesel) Implement.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.server.mail.receive;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.gerrit.extensions.events.LifecycleListener;
|
||||
import com.google.gerrit.lifecycle.LifecycleModule;
|
||||
import com.google.gerrit.server.mail.EmailSettings;
|
||||
@@ -100,5 +101,6 @@ public abstract class MailReceiver implements LifecycleListener {
|
||||
* handleEmails will open a connection to the mail server, remove emails
|
||||
* where deletion is pending, read new email and close the connection.
|
||||
*/
|
||||
protected abstract void handleEmails();
|
||||
@VisibleForTesting
|
||||
public abstract void handleEmails();
|
||||
}
|
||||
|
||||
@@ -14,12 +14,29 @@
|
||||
|
||||
package com.google.gerrit.server.mail.receive;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.gerrit.server.mail.EmailSettings;
|
||||
import com.google.gerrit.server.mail.Encryption;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.net.pop3.POP3Client;
|
||||
import org.apache.commons.net.pop3.POP3MessageInfo;
|
||||
import org.apache.commons.net.pop3.POP3SClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
public class Pop3MailReceiver extends MailReceiver {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(Pop3MailReceiver.class);
|
||||
|
||||
@Inject
|
||||
public Pop3MailReceiver(EmailSettings mailSettings) {
|
||||
super(mailSettings);
|
||||
@@ -30,7 +47,102 @@ public class Pop3MailReceiver extends MailReceiver {
|
||||
* where deletion is pending, read new email and close the connection.
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void handleEmails() {
|
||||
// TODO(hiesel) Implement.
|
||||
public synchronized void handleEmails() {
|
||||
POP3Client pop3;
|
||||
if (mailSettings.encryption != Encryption.NONE) {
|
||||
pop3 = new POP3SClient(mailSettings.encryption.name());
|
||||
} else {
|
||||
pop3 = new POP3Client();
|
||||
}
|
||||
if (mailSettings.port > 0) {
|
||||
pop3.setDefaultPort(mailSettings.port);
|
||||
}
|
||||
try {
|
||||
pop3.connect(mailSettings.host);
|
||||
} catch (IOException e) {
|
||||
log.error("Could not connect to POP3 email server", e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
try {
|
||||
if (!pop3.login(mailSettings.username, mailSettings.password)) {
|
||||
log.error("Could not login to POP3 email server."
|
||||
+ " Check username and password");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
POP3MessageInfo[] messages = pop3.listMessages();
|
||||
if (messages == null) {
|
||||
log.error("Could not retrieve message list via POP3");
|
||||
return;
|
||||
}
|
||||
log.info("Received " + messages.length + " messages via POP3");
|
||||
// Fetch messages
|
||||
List<MailMessage> mailMessages = new ArrayList<>();
|
||||
for (POP3MessageInfo msginfo : messages) {
|
||||
if (msginfo == null) {
|
||||
// Message was deleted
|
||||
continue;
|
||||
}
|
||||
Reader reader = pop3.retrieveMessage(msginfo.number);
|
||||
if (reader == null) {
|
||||
log.error("Could not retrieve POP3 message header for message " +
|
||||
msginfo.identifier);
|
||||
return;
|
||||
}
|
||||
int[] message = fetchMessage(reader);
|
||||
try {
|
||||
MailMessage mailMessage = RawMailParser.parse(message);
|
||||
// Delete messages where deletion is pending. This requires
|
||||
// knowing the integer message ID of the email. We therefore parse
|
||||
// the message first and extract the Message-ID specified in RFC
|
||||
// 822 and delete the message if deletion is pending.
|
||||
if (pendingDeletion.contains(mailMessage.id())) {
|
||||
if (pop3.deleteMessage(msginfo.number)) {
|
||||
pendingDeletion.remove(mailMessage.id());
|
||||
} else {
|
||||
log.error("Could not delete message " + msginfo.number);
|
||||
}
|
||||
} else {
|
||||
// Process message further
|
||||
mailMessages.add(mailMessage);
|
||||
}
|
||||
} catch (MailParsingException e) {
|
||||
log.error("Could not parse message " + msginfo.number);
|
||||
}
|
||||
}
|
||||
// TODO(hiesel) Call processing logic with mailMessages
|
||||
} finally {
|
||||
pop3.logout();
|
||||
}
|
||||
} finally {
|
||||
pop3.disconnect();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error while issuing POP3 command", e);
|
||||
}
|
||||
}
|
||||
|
||||
public final int[] fetchMessage(Reader reader) throws IOException {
|
||||
BufferedReader bufferedReader;
|
||||
if (reader instanceof BufferedReader) {
|
||||
bufferedReader = (BufferedReader) reader;
|
||||
} else {
|
||||
bufferedReader = new BufferedReader(reader);
|
||||
}
|
||||
|
||||
try {
|
||||
List<Integer> character = new ArrayList<>();
|
||||
int ch;
|
||||
while ((ch = bufferedReader.read()) != -1) {
|
||||
character.add(ch);
|
||||
}
|
||||
return Ints.toArray(character);
|
||||
} finally {
|
||||
bufferedReader.close();
|
||||
if (bufferedReader != reader) {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user