JAMES-2135 text/calendar bodies should not be displayed in textBody
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/f4fc1393 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/f4fc1393 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/f4fc1393 Branch: refs/heads/master Commit: f4fc1393777ae92c065a2259be3df3754a9f173e Parents: daaa36a Author: quynhn <[email protected]> Authored: Fri Sep 1 14:18:22 2017 +0700 Committer: Matthieu Baechler <[email protected]> Committed: Mon Sep 11 11:28:15 2017 +0200 ---------------------------------------------------------------------- .../mailets/ICSAttachmentWorkflowTest.java | 30 ++- .../src/test/resources/eml/calendar.eml | 42 ++++ .../mailet/TextCalendarBodyToAttachment.java | 108 +++++++++ .../TextCalendarBodyToAttachmentTest.java | 243 +++++++++++++++++++ .../jmap/src/test/resources/calendar.eml | 42 ++++ 5 files changed, 462 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/f4fc1393/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ICSAttachmentWorkflowTest.java ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ICSAttachmentWorkflowTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ICSAttachmentWorkflowTest.java index 6247e2e..22d68a9 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ICSAttachmentWorkflowTest.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/transport/mailets/ICSAttachmentWorkflowTest.java @@ -36,8 +36,8 @@ import org.apache.james.modules.MailboxProbeImpl; import org.apache.james.probe.DataProbe; import org.apache.james.transport.mailets.amqp.AmqpRule; import org.apache.james.util.streams.SwarmGenericContainer; -import org.apache.james.utils.IMAPMessageReader; import org.apache.james.utils.DataProbeImpl; +import org.apache.james.utils.IMAPMessageReader; import org.apache.mailet.Mail; import org.apache.james.core.MailAddress; import org.apache.mailet.base.test.FakeMail; @@ -76,7 +76,7 @@ public class ICSAttachmentWorkflowTest { private static final String JSON_MAIL_ATTRIBUTE = "ical.json"; private static final String EXCHANGE_NAME = "myExchange"; private static final String ROUTING_KEY = "myRoutingKey"; - + private static final String ICS_UID = "f1514f44bf39311568d640727cff54e819573448d09d2e5677987ff29caa01a9e047feb2aab16e43439a608f28671ab7c10e754ce92be513f8e04ae9ff15e65a9819cf285a6962bc"; private static final String ICS_DTSTAMP = "20170106T115036Z"; private static final String ICS_SEQUENCE = "0"; @@ -161,7 +161,7 @@ public class ICSAttachmentWorkflowTest { " S-ACTION;X-OBM-ID=769:MAILTO:[email protected]\n" + "END:VEVENT\n" + "END:VCALENDAR\n"; - + private static final String ICS_3 = "BEGIN:VCALENDAR\n" + "PRODID:-//Aliasource Groupe LINAGORA//OBM Calendar 3.2.1-rc2//FR\n" + "CALSCALE:GREGORIAN\n" + @@ -491,6 +491,10 @@ public class ICSAttachmentWorkflowTest { .addProperty("destination", JSON_MAIL_ATTRIBUTE) .build()) .addMailet(MailetConfiguration.builder() + .match("All") + .clazz("org.apache.james.jmap.mailet.TextCalendarBodyToAttachment") + .build()) + .addMailet(MailetConfiguration.builder() .match("All") .clazz("AmqpForwardAttribute") .addProperty("uri", amqpRule.getAmqpUri()) @@ -823,4 +827,24 @@ public class ICSAttachmentWorkflowTest { assertThat(amqpRule.readContent()).isEmpty(); } + @Test + public void mailShouldNotContainCalendarContentInTextBodyButAttachment() throws Exception { + MimeMessage calendarMessage = MimeMessageBuilder.mimeMessageFromStream(ClassLoader.getSystemResourceAsStream("eml/calendar.eml")); + Mail mail = FakeMail.builder() + .mimeMessage(calendarMessage) + .sender(new MailAddress(FROM)) + .recipient(new MailAddress(RECIPIENT)) + .build(); + + try (SMTPMessageSender messageSender = SMTPMessageSender.noAuthentication(LOCALHOST_IP, SMTP_PORT, JAMES_APACHE_ORG); + IMAPMessageReader imapMessageReader = new IMAPMessageReader(LOCALHOST_IP, IMAP_PORT)) { + messageSender.sendMessage(mail); + calmlyAwait.atMost(Duration.ONE_MINUTE).until(messageSender::messageHasBeenSent); + calmlyAwait.atMost(Duration.ONE_MINUTE).until(() -> imapMessageReader.userReceivedMessage(RECIPIENT, PASSWORD)); + + String receivedMessage = imapMessageReader.readFirstMessageInInbox(RECIPIENT, PASSWORD); + + assertThat(receivedMessage).containsSequence("Content-Type: multipart/mixed", "Content-Disposition: attachment"); + } + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/f4fc1393/server/mailet/integration-testing/src/test/resources/eml/calendar.eml ---------------------------------------------------------------------- diff --git a/server/mailet/integration-testing/src/test/resources/eml/calendar.eml b/server/mailet/integration-testing/src/test/resources/eml/calendar.eml new file mode 100644 index 0000000..2ae4533 --- /dev/null +++ b/server/mailet/integration-testing/src/test/resources/eml/calendar.eml @@ -0,0 +1,42 @@ +Return-Path: <[email protected]> +MIME-Version: 1.0 +Delivered-To: [email protected] +From: User From <[email protected]> +Message-ID: <[email protected]> +To: User To <[email protected]> +Date: Wed, 26 Jul 2017 10:34:02 +0200 +Subject: Any subject +Content-class: urn:content-classes:calendarmessage +Content-Type: text/calendar; method=REPLY; charset=UTF-8 +Content-transfer-encoding: 8BIT + +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +CREATED:20170726T081145Z +LAST-MODIFIED:20170726T083402Z +DTSTAMP:20170726T083402Z +UID:f1514f44bf39311568d6407249cb76c48103fcd1fb398c3c501cb72b2d293f36e04 + b2aab16e43439a608f28671ab7c10e754cbc85ddee4a07fa8cf8f4f7af05bfb502b8f6 + +SUMMARY:[A CONFIRMER] Call OVH +PRIORITY:5 +ORGANIZER;CN=User;X-OBM-ID=id:mailto:[email protected] +ATTENDEE;CN=User;PARTSTAT=ACCEPTED;CUTYPE=INDIVIDUAL;X-OBM-ID=810: + mailto:[email protected] +DTSTART:20170727T130000Z +DURATION:PT1H +TRANSP:OPAQUE +SEQUENCE:2 +X-LIC-ERROR;X-LIC-ERRORTYPE=VALUE-PARSE-ERROR:No value for X property. Rem + oving entire property: +CLASS:PUBLIC +X-OBM-DOMAIN:linagora.com +X-OBM-DOMAIN-UUID:uuid_value +LOCATION:01.78.14.42.61 / 1586 +X-OBM-ALERT;X-OBM-ID=486:600 +END:VEVENT +END:VCALENDAR + http://git-wip-us.apache.org/repos/asf/james-project/blob/f4fc1393/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/TextCalendarBodyToAttachment.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/TextCalendarBodyToAttachment.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/TextCalendarBodyToAttachment.java new file mode 100644 index 0000000..e43a941 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/TextCalendarBodyToAttachment.java @@ -0,0 +1,108 @@ +/**************************************************************** + * 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.james.jmap.mailet; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +import javax.mail.Header; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Part; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import org.apache.mailet.Mail; +import org.apache.mailet.base.GenericMailet; + +import com.github.steveash.guavate.Guavate; +import com.google.common.annotations.VisibleForTesting; + +/** + * This mailet converts Content-Type of MimeMessage from text/calendar to mulitpart/mixed + * + * The BodyPart should be retrieved from content of text/calendar with all the same "Content-*" headers from original message + * and those "Content-" header are removed from original message + * + * <br /> + * It does not takes any parameter + * + * Sample configuration: + * <p/> + * <pre><code> + * <mailet match="All" class="TextCalendarBodyToAttachment"/> + * </code></pre> + * + */ +public class TextCalendarBodyToAttachment extends GenericMailet { + private static final String TEXT_CALENDAR_TYPE = "text/calendar"; + private static final String CONTENT_HEADER_PREFIX = "Content-"; + + @Override + public String getMailetInfo() { + return "Moves body part of content type text/calendar to attachment"; + } + + @Override + public void service(Mail mail) throws MessagingException { + MimeMessage mimeMessage = mail.getMessage(); + if (mimeMessage.isMimeType(TEXT_CALENDAR_TYPE)) { + processTextBodyAsAttachment(mimeMessage); + } + } + + @VisibleForTesting + void processTextBodyAsAttachment(MimeMessage mimeMessage) throws MessagingException { + List<Header> contentHeaders = getContentHeadersFromMimeMessage(mimeMessage); + + removeAllContentHeaderFromMimeMessage(mimeMessage, contentHeaders); + + Multipart multipart = new MimeMultipart(); + multipart.addBodyPart(createMimeBodyPartWithContentHeadersFromMimeMessage(mimeMessage, contentHeaders)); + + mimeMessage.setContent(multipart); + mimeMessage.saveChanges(); + } + + private List<Header> getContentHeadersFromMimeMessage(MimeMessage mimeMessage) throws MessagingException { + return Collections.list((Enumeration<Header>) mimeMessage.getAllHeaders()) + .stream() + .filter(header -> header.getName().startsWith(CONTENT_HEADER_PREFIX)) + .collect(Guavate.toImmutableList()); + } + + private MimeBodyPart createMimeBodyPartWithContentHeadersFromMimeMessage(MimeMessage mimeMessage, List<Header> contentHeaders) throws MessagingException { + MimeBodyPart fileBody = new MimeBodyPart(mimeMessage.getRawInputStream()); + for (Header header : contentHeaders) { + fileBody.setHeader(header.getName(), header.getValue()); + } + + fileBody.setDisposition(Part.ATTACHMENT); + return fileBody; + } + + private void removeAllContentHeaderFromMimeMessage(MimeMessage mimeMessage, List<Header> contentHeaders) throws MessagingException { + for (Header header : contentHeaders) { + mimeMessage.removeHeader(header.getName()); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/f4fc1393/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/TextCalendarBodyToAttachmentTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/TextCalendarBodyToAttachmentTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/TextCalendarBodyToAttachmentTest.java new file mode 100644 index 0000000..d43fe2a --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/TextCalendarBodyToAttachmentTest.java @@ -0,0 +1,243 @@ +/**************************************************************** + * 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.james.jmap.mailet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; + +import javax.mail.BodyPart; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.internet.MimeMessage; + +import org.apache.mailet.Mail; +import org.apache.mailet.base.test.FakeMail; +import org.apache.mailet.base.test.FakeMailetConfig; +import org.apache.mailet.base.test.MimeMessageBuilder; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class TextCalendarBodyToAttachmentTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private TextCalendarBodyToAttachment mailet = new TextCalendarBodyToAttachment(); + + private MimeMessage calendarMessage; + + @Before + public void setUp() throws Exception { + mailet.init(FakeMailetConfig.builder() + .mailetName("TextCalendarBodyToAttachment") + .build()); + + calendarMessage = MimeMessageBuilder.mimeMessageFromStream(ClassLoader.getSystemResourceAsStream("calendar.eml")); + } + + @Test + public void getMailetInformationShouldReturnInformation() throws Exception { + assertThat(mailet.getMailetInfo()).isEqualTo("Moves body part of content type text/calendar to attachment"); + } + + @Test + public void serviceShouldThrowWhenCanNotGetMessageFromMail() throws Exception { + expectedException.expect(MessagingException.class); + Mail mail = mock(Mail.class); + when(mail.getMessage()) + .thenThrow(MessagingException.class); + + mailet.service(mail); + } + + @Test + public void serviceShouldThrowWhenMessageCanNotGetContentType() throws Exception { + expectedException.expect(MessagingException.class); + + MimeMessage message = mock(MimeMessage.class); + Mail mail = FakeMail.from(message); + + when(message.isMimeType(anyString())).thenThrow(MessagingException.class); + + mailet.service(mail); + } + + @Test + public void serviceShouldKeepMessageAsItIsWhenMessageIsNotTextCalendar() throws Exception { + String messageContent = "Content-type: text/html; method=REPLY; charset=UTF-8\n" + + "Content-transfer-encoding: 8BIT\n" + + "\n" + + "BEGIN:VCALENDAR"; + MimeMessage message = MimeMessageBuilder.mimeMessageFromStream(new ByteArrayInputStream(messageContent.getBytes(StandardCharsets.US_ASCII))); + + Mail mail = FakeMail.builder() + .mimeMessage(message) + .build(); + + mailet.service(mail); + + assertThat(mail.getMessage().isMimeType("text/html")).isTrue(); + assertThat(mail.getMessage().getDisposition()).isNull(); + } + + @Test + public void serviceShouldChangeMessageContentTypeToMultipartWhenTextCalendarMessage() throws Exception { + String messageContent = "Content-type: text/calendar; method=REPLY; charset=UTF-8\n" + + "Content-transfer-encoding: 8BIT\n" + + "\n" + + "BEGIN:VCALENDAR"; + MimeMessage message = MimeMessageBuilder.mimeMessageFromStream(new ByteArrayInputStream(messageContent.getBytes(StandardCharsets.US_ASCII))); + + Mail mail = FakeMail.builder() + .mimeMessage(message) + .build(); + + mailet.service(mail); + + assertThat(mail.getMessage().isMimeType("multipart/*")).isTrue(); + } + + @Test + public void serviceShouldConvertTextBodyOfMessageToAttachmentWhenTextCalendar() throws Exception { + String messageContent = "Content-type: text/calendar; method=REPLY; charset=UTF-8\n" + + "Content-transfer-encoding: 8BIT\n" + + "\n" + + "BEGIN:VCALENDAR\n" + + "END:VEVENT\n" + + "END:VCALENDAR"; + MimeMessage message = MimeMessageBuilder.mimeMessageFromStream(new ByteArrayInputStream(messageContent.getBytes(StandardCharsets.US_ASCII))); + + Mail mail = FakeMail.builder() + .mimeMessage(message) + .build(); + + mailet.service(mail); + + MimeMessage actual = mail.getMessage(); + Multipart multipart = (Multipart)actual.getContent(); + + assertThat(multipart.getCount()).isEqualTo(1); + assertThat(multipart.getBodyPart(0).getDisposition()).isEqualTo("attachment"); + } + + @Test + public void contentTypeOfAttachmentShouldBeTakenFromOriginalMessage() throws Exception { + Mail mail = FakeMail.builder() + .mimeMessage(calendarMessage) + .build(); + + mailet.service(mail); + + Multipart multipart = (Multipart)mail.getMessage().getContent(); + + int firstBodyPartIndex = 0; + BodyPart firstBodyPart = multipart.getBodyPart(firstBodyPartIndex); + assertThat(firstBodyPart.getContentType()).isEqualTo("text/calendar; method=REPLY; charset=UTF-8"); + } + + @Test + public void contentTransferEncodingOfAttachmentShouldBeTakenFromOriginalMessage() throws Exception { + Mail mail = FakeMail.builder() + .mimeMessage(calendarMessage) + .build(); + + mailet.service(mail); + + Multipart multipart = (Multipart)mail.getMessage().getContent(); + + int firstBodyPartIndex = 0; + BodyPart firstBodyPart = multipart.getBodyPart(firstBodyPartIndex); + assertThat(firstBodyPart.getHeader("Content-transfer-encoding")).containsExactly("8BIT"); + } + + @Test + public void contentClassOfAttachmentShouldBeTakenFromOriginalMessage() throws Exception { + Mail mail = FakeMail.builder() + .mimeMessage(calendarMessage) + .build(); + + mailet.service(mail); + + Multipart multipart = (Multipart)mail.getMessage().getContent(); + + int firstBodyPartIndex = 0; + BodyPart firstBodyPart = multipart.getBodyPart(firstBodyPartIndex); + assertThat(firstBodyPart.getHeader("Content-class")).containsExactly("urn:content-classes:calendarmessage"); + } + + @Test + public void contentClassHeaderShouldBeRemoveFromOriginalMessage() throws Exception { + Mail mail = FakeMail.builder() + .mimeMessage(calendarMessage) + .build(); + + mailet.service(mail); + + assertThat(mail.getMessage().getHeader("Content-class")).isNullOrEmpty(); + } + + @Test + public void contentDispositionHeaderShouldBeRemoveFromOriginalMessage() throws Exception { + String messageContent = "Content-type: text/calendar; method=REPLY; charset=UTF-8\n" + + "Content-Disposition: inline\n" + + "\n" + + "BEGIN:VCALENDAR\n" + + "END:VEVENT\n" + + "END:VCALENDAR"; + MimeMessage message = MimeMessageBuilder.mimeMessageFromStream(new ByteArrayInputStream(messageContent.getBytes(StandardCharsets.US_ASCII))); + + Mail mail = FakeMail.builder() + .mimeMessage(message) + .build(); + + mailet.service(mail); + + assertThat(mail.getMessage().getHeader("Content-Disposition")).isNullOrEmpty(); + } + + @Test + public void contentDispositionOfAttachmentShouldBeOverwrittenWhenOriginalMessageHasContentDisposition() throws Exception { + String messageContent = "Content-type: text/calendar; method=REPLY; charset=UTF-8\n" + + "Content-Disposition: inline\n" + + "\n" + + "BEGIN:VCALENDAR\n" + + "END:VEVENT\n" + + "END:VCALENDAR"; + MimeMessage message = MimeMessageBuilder.mimeMessageFromStream(new ByteArrayInputStream(messageContent.getBytes(StandardCharsets.US_ASCII))); + + Mail mail = FakeMail.builder() + .mimeMessage(message) + .build(); + + mailet.service(mail); + + Multipart multipart = (Multipart)mail.getMessage().getContent(); + + int firstBodyPartIndex = 0; + BodyPart firstBodyPart = multipart.getBodyPart(firstBodyPartIndex); + assertThat(firstBodyPart.getHeader("Content-Disposition")).containsExactly("attachment"); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/f4fc1393/server/protocols/jmap/src/test/resources/calendar.eml ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/resources/calendar.eml b/server/protocols/jmap/src/test/resources/calendar.eml new file mode 100644 index 0000000..2ae4533 --- /dev/null +++ b/server/protocols/jmap/src/test/resources/calendar.eml @@ -0,0 +1,42 @@ +Return-Path: <[email protected]> +MIME-Version: 1.0 +Delivered-To: [email protected] +From: User From <[email protected]> +Message-ID: <[email protected]> +To: User To <[email protected]> +Date: Wed, 26 Jul 2017 10:34:02 +0200 +Subject: Any subject +Content-class: urn:content-classes:calendarmessage +Content-Type: text/calendar; method=REPLY; charset=UTF-8 +Content-transfer-encoding: 8BIT + +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +CREATED:20170726T081145Z +LAST-MODIFIED:20170726T083402Z +DTSTAMP:20170726T083402Z +UID:f1514f44bf39311568d6407249cb76c48103fcd1fb398c3c501cb72b2d293f36e04 + b2aab16e43439a608f28671ab7c10e754cbc85ddee4a07fa8cf8f4f7af05bfb502b8f6 + +SUMMARY:[A CONFIRMER] Call OVH +PRIORITY:5 +ORGANIZER;CN=User;X-OBM-ID=id:mailto:[email protected] +ATTENDEE;CN=User;PARTSTAT=ACCEPTED;CUTYPE=INDIVIDUAL;X-OBM-ID=810: + mailto:[email protected] +DTSTART:20170727T130000Z +DURATION:PT1H +TRANSP:OPAQUE +SEQUENCE:2 +X-LIC-ERROR;X-LIC-ERRORTYPE=VALUE-PARSE-ERROR:No value for X property. Rem + oving entire property: +CLASS:PUBLIC +X-OBM-DOMAIN:linagora.com +X-OBM-DOMAIN-UUID:uuid_value +LOCATION:01.78.14.42.61 / 1586 +X-OBM-ALERT;X-OBM-ID=486:600 +END:VEVENT +END:VCALENDAR + --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
