JAMES-1717 Mailet sub-project should allow automatically generated mail detection
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/661c15ee Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/661c15ee Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/661c15ee Branch: refs/heads/master Commit: 661c15ee017dd42a0fbb9339b53ceb50fc25a780 Parents: be08b5a Author: Benoit Tellier <[email protected]> Authored: Thu May 19 18:05:34 2016 +0700 Committer: Benoit Tellier <[email protected]> Committed: Fri May 20 18:52:07 2016 +0700 ---------------------------------------------------------------------- mailet/base/pom.xml | 10 + .../base/AutomaticallySentMailDetector.java | 35 +++ .../base/AutomaticallySentMailDetectorImpl.java | 124 +++++++++ .../AutomaticallySentMailDetectorImplTest.java | 250 +++++++++++++++++++ mailet/pom.xml | 12 + 5 files changed, 431 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/661c15ee/mailet/base/pom.xml ---------------------------------------------------------------------- diff --git a/mailet/base/pom.xml b/mailet/base/pom.xml index ca3dd2e..e85a706 100644 --- a/mailet/base/pom.xml +++ b/mailet/base/pom.xml @@ -53,6 +53,16 @@ <artifactId>apache-mailet-api</artifactId> </dependency> <dependency> + <groupId>org.apache.james</groupId> + <artifactId>apache-mime4j-core</artifactId> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>${assertj-1.version}</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> http://git-wip-us.apache.org/repos/asf/james-project/blob/661c15ee/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetector.java ---------------------------------------------------------------------- diff --git a/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetector.java b/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetector.java new file mode 100644 index 0000000..e47c863 --- /dev/null +++ b/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetector.java @@ -0,0 +1,35 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 org.apache.mailet.base; + +import javax.mail.MessagingException; + +import org.apache.mailet.Mail; + +public interface AutomaticallySentMailDetector { + + boolean isAutomaticallySent(Mail mail) throws MessagingException; + + boolean isMailingList(Mail mail) throws MessagingException; + + boolean isAutoSubmitted(Mail mail) throws MessagingException; + + boolean isMdnSentAutomatically(Mail mail) throws MessagingException; +} http://git-wip-us.apache.org/repos/asf/james-project/blob/661c15ee/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetectorImpl.java ---------------------------------------------------------------------- diff --git a/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetectorImpl.java b/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetectorImpl.java new file mode 100644 index 0000000..032556a --- /dev/null +++ b/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetectorImpl.java @@ -0,0 +1,124 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 org.apache.mailet.base; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import javax.mail.MessagingException; + +import org.apache.james.mime4j.MimeException; +import org.apache.james.mime4j.parser.AbstractContentHandler; +import org.apache.james.mime4j.parser.MimeStreamParser; +import org.apache.james.mime4j.stream.BodyDescriptor; +import org.apache.james.mime4j.stream.MimeConfig; +import org.apache.mailet.Mail; + +public class AutomaticallySentMailDetectorImpl implements AutomaticallySentMailDetector { + + public boolean isAutomaticallySent(Mail mail) throws MessagingException { + return isMailingList(mail) || + isAutoSubmitted(mail) || + isMdnSentAutomatically(mail); + } + + public boolean isMailingList(Mail mail) throws MessagingException { + String localPart = mail.getSender().getLocalPart(); + return localPart.startsWith("owner-") + || localPart.endsWith("-request") + || localPart.equalsIgnoreCase("MAILER-DAEMON") + || localPart.equalsIgnoreCase("LISTSERV") + || localPart.equalsIgnoreCase("majordomo") + || mail.getMessage() + .getMatchingHeaders(new String[]{"List-Help", + "List-Subscribe", + "List-Unsubscribe", + "List-Owner", + "List-Post", + "List-Id", + "List-Archive"}) + .hasMoreElements(); + } + + public boolean isAutoSubmitted(Mail mail) throws MessagingException { + String[] headers = mail.getMessage().getHeader("Auto-Submitted"); + if (headers != null) { + for (String header : headers) { + if (header.equalsIgnoreCase("auto-replied")) { + return true; + } + } + } + return false; + } + + public boolean isMdnSentAutomatically(Mail mail) throws MessagingException { + ResultCollector resultCollector = new ResultCollector(false); + MimeConfig config = MimeConfig.custom().setMaxLineLen(-1).setMaxHeaderLen(-1).build(); + MimeStreamParser parser = new MimeStreamParser(config); + parser.setContentHandler(createMdnContentHandler(resultCollector)); + try { + parser.parse(mail.getMessage().getInputStream()); + } catch (MimeException e) { + throw new MessagingException("Can not parse Mime", e); + } catch (IOException e) { + throw new MessagingException("Can not read content", e); + } + return resultCollector.getResult(); + } + + private AbstractContentHandler createMdnContentHandler(final ResultCollector resultCollector) { + return new AbstractContentHandler() { + @Override + public void body(BodyDescriptor bodyDescriptor, InputStream inputStream) throws MimeException, IOException { + if (bodyDescriptor.getMimeType().equalsIgnoreCase("message/disposition-notification")) { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + while ((line = reader.readLine()) != null ) { + if (line.startsWith("Disposition:")) { + if (line.contains("MDN-sent-automatically") || line.contains("automatic-action")) { + resultCollector.setResult(true); + } + } + } + } + } + }; + } + + private static class ResultCollector { + private boolean result; + + public ResultCollector(boolean result) { + this.result = result; + } + + public boolean getResult() { + return result; + } + + public void setResult(boolean result) { + this.result = result; + } + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/661c15ee/mailet/base/src/test/java/org/apache/mailet/base/AutomaticallySentMailDetectorImplTest.java ---------------------------------------------------------------------- diff --git a/mailet/base/src/test/java/org/apache/mailet/base/AutomaticallySentMailDetectorImplTest.java b/mailet/base/src/test/java/org/apache/mailet/base/AutomaticallySentMailDetectorImplTest.java new file mode 100644 index 0000000..9581ac9 --- /dev/null +++ b/mailet/base/src/test/java/org/apache/mailet/base/AutomaticallySentMailDetectorImplTest.java @@ -0,0 +1,250 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 org.apache.mailet.base; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Properties; + +import javax.activation.DataHandler; +import javax.mail.Session; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.mail.util.ByteArrayDataSource; + +import org.apache.mailet.MailAddress; +import org.apache.mailet.base.test.FakeMail; +import org.junit.Test; + +public class AutomaticallySentMailDetectorImplTest { + + @Test + public void ownerIsAMailingListPrefix() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void requestIsAMailingListPrefix() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void mailerDaemonIsReserved() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void listservIsReserved() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void majordomoIsReserved() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void listIdShouldBeDetected() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + message.setHeader("List-Id", "any"); + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void listHelpShouldBeDetected() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + message.setHeader("List-Help", "any"); + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void listSubscribeShouldBeDetected() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + message.setHeader("List-Subscribe", "any"); + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void listUnsubscribeShouldBeDetected() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + message.setHeader("List-Unsubscribe", "any"); + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void listPostShouldBeDetected() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + message.setHeader("List-Post", "any"); + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void listOwnerShouldBeDetected() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + message.setHeader("List-Owner", "any"); + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void listArchiveShouldBeDetected() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + message.setHeader("List-Archive", "any"); + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isTrue(); + } + + @Test + public void normalMailShouldNotBeIdentifiedAsMailingList() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + fakeMail.setMessage(new MimeMessage(Session.getDefaultInstance(new Properties()))); + + assertThat(new AutomaticallySentMailDetectorImpl().isMailingList(fakeMail)).isFalse(); + } + + @Test + public void isAutoSubmittedShouldNotMatchNonAutoSubmittedMails() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setMessage(new MimeMessage(Session.getDefaultInstance(new Properties()))); + + assertThat(new AutomaticallySentMailDetectorImpl().isAutoSubmitted(fakeMail)).isFalse(); + } + + @Test + public void isAutoSubmittedShouldBeDetected() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + message.setHeader("Auto-Submitted", "auto-replied"); + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isAutoSubmitted(fakeMail)).isTrue(); + } + + @Test + public void isMdnSentAutomaticallyShouldBeDetected() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + MimeMultipart multipart = new MimeMultipart(); + MimeBodyPart scriptPart = new MimeBodyPart(); + scriptPart.setDataHandler( + new DataHandler( + new ByteArrayDataSource( + "Disposition: MDN-sent-automatically", + "message/disposition-notification;") + )); + scriptPart.setHeader("Content-Type", "message/disposition-notification"); + multipart.addBodyPart(scriptPart); + message.setContent(multipart); + + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isMdnSentAutomatically(fakeMail)).isTrue(); + } + + @Test + public void isMdnSentAutomaticallyShouldNotFilterManuallySentMdn() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + MimeMultipart multipart = new MimeMultipart(); + MimeBodyPart scriptPart = new MimeBodyPart(); + scriptPart.setDataHandler( + new DataHandler( + new ByteArrayDataSource( + "Disposition: MDN-sent-manually", + "message/disposition-notification; charset=UTF-8") + )); + scriptPart.setHeader("Content-Type", "message/disposition-notification"); + multipart.addBodyPart(scriptPart); + message.setContent(multipart); + + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isMdnSentAutomatically(fakeMail)).isFalse(); + } + + @Test + public void isMdnSentAutomaticallyShouldManageItsMimeType() throws Exception { + FakeMail fakeMail = new FakeMail(); + fakeMail.setSender(new MailAddress("[email protected]")); + MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties())); + MimeMultipart multipart = new MimeMultipart(); + MimeBodyPart scriptPart = new MimeBodyPart(); + scriptPart.setDataHandler( + new DataHandler( + new ByteArrayDataSource( + "Disposition: MDN-sent-automatically", + "text/plain") + )); + scriptPart.setHeader("Content-Type", "text/plain"); + multipart.addBodyPart(scriptPart); + message.setContent(multipart); + + fakeMail.setMessage(message); + + assertThat(new AutomaticallySentMailDetectorImpl().isMdnSentAutomatically(fakeMail)).isFalse(); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/661c15ee/mailet/pom.xml ---------------------------------------------------------------------- diff --git a/mailet/pom.xml b/mailet/pom.xml index 8cec493..f4ac702 100644 --- a/mailet/pom.xml +++ b/mailet/pom.xml @@ -54,6 +54,7 @@ <maven-reporting-impl.version>2.2</maven-reporting-impl.version> <maven-reporting-api.version>3.0</maven-reporting-api.version> <qdox.version>1.12.1</qdox.version> + <assertj-1.version>1.7.1</assertj-1.version> </properties> @@ -79,6 +80,11 @@ <version>${project.version}</version> </dependency> <dependency> + <groupId>org.apache.james</groupId> + <artifactId>apache-mime4j-core</artifactId> + <version>0.8.0-SNAPSHOT</version> + </dependency> + <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>${javax.version}</version> @@ -129,6 +135,12 @@ <version>${httpclient-osgi.version}</version> </dependency> <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>${assertj-1.version}</version> + <scope>test</scope> + </dependency> + <dependency> <groupId>com.thoughtworks.qdox</groupId> <artifactId>qdox</artifactId> <version>${qdox.version}</version> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
