You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ro...@apache.org on 2016/09/27 08:08:22 UTC

[2/2] james-project git commit: JAMES-1826 Handle multipart/related message content

JAMES-1826 Handle multipart/related message content


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/71ceb33d
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/71ceb33d
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/71ceb33d

Branch: refs/heads/master
Commit: 71ceb33db6d726542f189a862e85566a7f8af45f
Parents: 2bfe6ce
Author: Raphael Ouazana <ra...@linagora.com>
Authored: Thu Sep 22 15:14:05 2016 +0200
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Tue Sep 27 10:06:51 2016 +0200

----------------------------------------------------------------------
 .../cucumber/GetMessagesMethodStepdefs.java     |  5 ++
 .../test/resources/cucumber/GetMessages.feature | 11 ++++
 .../src/test/resources/eml/multipartRelated.eml | 38 +++++++++++++
 .../jmap/model/MessageContentExtractor.java     | 59 +++++++++++++++++---
 .../jmap/model/MessageContentExtractorTest.java | 30 ++++++++++
 5 files changed, 135 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/71ceb33d/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/GetMessagesMethodStepdefs.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/GetMessagesMethodStepdefs.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/GetMessagesMethodStepdefs.java
index 437078d..7deb095 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/GetMessagesMethodStepdefs.java
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/GetMessagesMethodStepdefs.java
@@ -136,6 +136,11 @@ public class GetMessagesMethodStepdefs {
         appendMessage("eml/htmlAndTextMultipartWithOneAttachment.eml");
     }
 
+    @Given("^the user has a multipart/related message in \"([^\"]*)\" mailbox$")
+    public void appendMultipartRelated(String arg1) throws Throwable {
+        appendMessage("eml/multipartRelated.eml");
+    }
+
     private void appendMessage(String emlFileName) throws Exception {
         ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
         mainStepdefs.jmapServer.serverProbe().appendMessage(userStepdefs.lastConnectedUser, 

http://git-wip-us.apache.org/repos/asf/james-project/blob/71ceb33d/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/GetMessages.feature
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/GetMessages.feature b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/GetMessages.feature
index 1a8271b..b01c137 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/GetMessages.feature
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/GetMessages.feature
@@ -188,3 +188,14 @@ Feature: GetMessages method
     And the preview of the message is "blabla\nbloblo\n"
     And the textBody of the message is "/blabla/\n*bloblo*\n"
     And the htmlBody of the message is "<i>blabla</i>\n<b>bloblo</b>\n"
+
+  Scenario: Retrieving message should return image and html body when multipart/alternative where first part is multipart/related with html and image
+    Given the user has a multipart/related message in "inbox" mailbox
+    When the user ask for messages "["username@domain.tld|inbox|1"]"
+    Then no error is returned
+    And the list should contain 1 message
+    And the hasAttachment of the message is "true"
+    And the list of attachments of the message contains 1 attachment
+    And the preview of the message is "multipart/related content"
+    And the property "textBody" of the message is null
+    And the htmlBody of the message is "<html>multipart/related content</html>\n"

http://git-wip-us.apache.org/repos/asf/james-project/blob/71ceb33d/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/eml/multipartRelated.eml
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/eml/multipartRelated.eml b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/eml/multipartRelated.eml
new file mode 100644
index 0000000..b2e8232
--- /dev/null
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/eml/multipartRelated.eml
@@ -0,0 +1,38 @@
+Return-Path: <li...@linagora.com>
+Received: from alderaan.linagora.com (smtp.linagora.dc1 [172.16.18.53])
+     by imap (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA;
+     Mon, 22 Aug 2016 14:06:47 +0200
+X-Sieve: CMU Sieve 2.2
+Received: from linshare1.linagora.dc1 (linshare1.linagora.dc1 [172.16.18.60])
+    by alderaan.linagora.com (Postfix) with ESMTP id 36470776
+    for <dd...@linagora.com>; Mon, 22 Aug 2016 14:06:47 +0200 (CEST)
+Date: Mon, 22 Aug 2016 14:06:47 +0200 (CEST)
+From: LinShare <li...@linagora.com>
+Reply-To: abenchi@linagora.com
+To: ddolcimascolo@linagora.com
+Message-ID: <20...@linshare1>
+Subject: A user Abdulkader BENCHI has just made a file available to you!
+MIME-Version: 1.0
+Content-Type: multipart/alternative; 
+    boundary="----=_Part_1786_771314706.1471867607209"
+
+------=_Part_1786_771314706.1471867607209
+Content-Type: multipart/related; 
+    boundary="----=_Part_1787_1274970450.1471867607209"
+
+------=_Part_1787_1274970450.1471867607209
+Content-Type: text/html; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+<html>multipart/related content</html>
+
+------=_Part_1787_1274970450.1471867607209
+Content-Type: image/png; name=gimp.png
+Content-Transfer-Encoding: base64
+Content-Disposition: inline; filename=gimp.png
+Content-ID: <im...@linshare.org>
+
+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
+------=_Part_1787_1274970450.1471867607209--
+
+------=_Part_1786_771314706.1471867607209--

http://git-wip-us.apache.org/repos/asf/james-project/blob/71ceb33d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageContentExtractor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageContentExtractor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageContentExtractor.java
index ecb9f48..035766b 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageContentExtractor.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageContentExtractor.java
@@ -21,6 +21,7 @@ package org.apache.james.jmap.model;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 
 import org.apache.commons.io.IOUtils;
@@ -30,6 +31,7 @@ import org.apache.james.mime4j.dom.Multipart;
 import org.apache.james.mime4j.dom.TextBody;
 
 import com.github.fge.lambdas.Throwing;
+import com.github.fge.lambdas.functions.ThrowingFunction;
 
 public class MessageContentExtractor {
     
@@ -53,10 +55,32 @@ public class MessageContentExtractor {
     }
 
     private MessageContent parseMultipart(Entity entity, Multipart multipart) throws IOException {
-        if ("multipart/alternative".equals(entity.getMimeType())) {
+        MessageContent messageContent = parseMultipartContent(entity, multipart);
+        if (!messageContent.isEmpty()) {
+            return messageContent;
+        }
+        return parseFirstFoundMultipart(multipart);
+    }
+
+    private MessageContent parseMultipartContent(Entity entity, Multipart multipart) throws IOException {
+        switch(entity.getMimeType()) {
+        case "multipart/alternative":
             return parseMultipartAlternative(multipart);
+        case "multipart/related":
+            return parseMultipartRelated(multipart);
+        default:
+            return parseMultipartMixed(multipart);
         }
-        return parseMultipartMixed(multipart);
+    }
+
+    private MessageContent parseFirstFoundMultipart(Multipart multipart) throws IOException {
+        ThrowingFunction<Entity, MessageContent> parseMultipart = firstPart -> parseMultipart(firstPart, (Multipart)firstPart.getBody());
+        return multipart.getBodyParts()
+            .stream()
+            .filter(part -> part.getBody() instanceof Multipart)
+            .findFirst()
+            .map(Throwing.function(parseMultipart).sneakyThrow())
+            .orElse(MessageContent.empty());
     }
 
     private String asString(TextBody textBody) throws IOException {
@@ -67,12 +91,8 @@ public class MessageContentExtractor {
         List<Entity> parts = multipart.getBodyParts();
         if (! parts.isEmpty()) {
             Entity firstPart = parts.get(0);
-            if (firstPart.getBody() instanceof Multipart && "multipart/alternative".equals(firstPart.getMimeType())) {
-                return parseMultipartAlternative((Multipart)firstPart.getBody());
-            } else {
-                if (firstPart.getBody() instanceof TextBody) {
-                    return parseTextBody(firstPart, (TextBody)firstPart.getBody());
-                }
+            if (firstPart.getBody() instanceof TextBody) {
+                return parseTextBody(firstPart, (TextBody)firstPart.getBody());
             }
         }
         return MessageContent.empty();
@@ -84,6 +104,15 @@ public class MessageContentExtractor {
         return new MessageContent(textBody, htmlBody);
     }
 
+    private MessageContent parseMultipartRelated(Multipart multipart) throws IOException {
+        Optional<String> textBody = Optional.empty();
+        Optional<String> htmlBody = getFirstMatchingTextBody(multipart, "text/html");
+        if (! htmlBody.isPresent()) {
+            textBody = getFirstMatchingTextBody(multipart, "text/plain");
+        }
+        return new MessageContent(textBody, htmlBody);
+    }
+
     private Optional<String> getFirstMatchingTextBody(Multipart multipart, String mimeType) throws IOException {
         return multipart.getBodyParts()
                 .stream()
@@ -123,5 +152,19 @@ public class MessageContentExtractor {
         public Optional<String> getHtmlBody() {
             return htmlBody;
         }
+        
+        public boolean isEmpty() {
+            return equals(empty());
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null || !(other instanceof MessageContent)) {
+                return false;
+            }
+            MessageContent otherMessageContent = (MessageContent)other;
+            return Objects.equals(this.textBody, otherMessageContent.textBody)
+                    && Objects.equals(this.htmlBody, otherMessageContent.htmlBody);
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/71ceb33d/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageContentExtractorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageContentExtractorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageContentExtractorTest.java
index 388f115..ba2f486 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageContentExtractorTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageContentExtractorTest.java
@@ -168,4 +168,34 @@ public class MessageContentExtractorTest {
         assertThat(actual.getTextBody()).contains(TEXT_CONTENT);
         assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
     }
+
+    @Test
+    public void extractShouldReturnHtmlWhenMultipartRelated() throws IOException {
+        Multipart multipart = MultipartBuilder.create("related")
+                .addBodyPart(htmlPart)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipart)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).isEmpty();
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+    }
+
+    @Test
+    public void extractShouldReturnHtmlAndTextWhenMultipartAlternativeAndFirstPartIsMultipartRelated() throws IOException {
+        BodyPart multipartRelated = BodyPartBuilder.create()
+            .setBody(MultipartBuilder.create("related")
+                    .addBodyPart(htmlPart)
+                    .build())
+            .build();
+        Multipart multipartAlternative = MultipartBuilder.create("alternative")
+                .addBodyPart(multipartRelated)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipartAlternative)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org