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 ad...@apache.org on 2016/07/07 07:39:58 UTC

[2/2] james-project git commit: JAMES-1793 Generate MultiPart response when mixed text and html content provided

JAMES-1793 Generate MultiPart response when mixed text and html content provided


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

Branch: refs/heads/master
Commit: 812b9129f8d09ade820e8d966285889ab85c8c28
Parents: 16c2593
Author: Raphael Ouazana <ra...@linagora.com>
Authored: Wed Jul 6 18:45:31 2016 +0200
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Wed Jul 6 18:45:31 2016 +0200

----------------------------------------------------------------------
 .../integration/SetMessagesMethodTest.java      | 62 ++++++++++++++++++++
 .../jmap/methods/MIMEMessageConverter.java      | 50 ++++++++++++++--
 .../jmap/methods/MIMEMessageConverterTest.java  | 48 +++++++++++++--
 3 files changed, 150 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/812b9129/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
index 1b6dab5..f6c18b1 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
@@ -1355,6 +1355,68 @@ public abstract class SetMessagesMethodTest {
     }
     
     @Test
+    public void setMessagesShouldSendAReadableTextPlusHtmlMessage() throws Exception {
+        // Sender
+        jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent");
+        // Recipient
+        String recipientAddress = "recipient" + "@" + USERS_DOMAIN;
+        String password = "password";
+        jmapServer.serverProbe().addUser(recipientAddress, password);
+        jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, "inbox");
+        await();
+        AccessToken recipientToken = JmapAuthentication.authenticateJamesUser(recipientAddress, password);
+
+        String messageCreationId = "user|inbox|1";
+        String fromAddress = username;
+        String requestBody = "[" +
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"from\": { \"email\": \"" + fromAddress + "\"}," +
+                "        \"to\": [{ \"name\": \"BOB\", \"email\": \"" + recipientAddress + "\"}]," +
+                "        \"subject\": \"Thank you for joining example.com!\"," +
+                "        \"htmlBody\": \"Hello <b>someone</b>, and thank you for joining example.com!\"," +
+                "        \"textBody\": \"Hello someone, and thank you for joining example.com, text version!\"," +
+                "        \"mailboxIds\": [\"" + getOutboxId() + "\"]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        // Given
+        given()
+                .header("Authorization", this.accessToken.serialize())
+                .body(requestBody)
+        // When
+        .when()
+                .post("/jmap");
+
+        // Then
+        calmlyAwait.atMost(30, TimeUnit.SECONDS).until( () -> isTextPlusHtmlMessageReceived(recipientToken));
+    }
+
+    private boolean isTextPlusHtmlMessageReceived(AccessToken recipientToken) {
+        try {
+            with()
+                .header("Authorization", recipientToken.serialize())
+                .body("[[\"getMessageList\", {\"fetchMessages\": true, \"fetchMessageProperties\": [\"htmlBody\", \"textBody\"]}, \"#0\"]]")
+            .post("/jmap")
+            .then()
+                .statusCode(200)
+                .body(SECOND_NAME, equalTo("messages"))
+                .body(SECOND_ARGUMENTS + ".list", hasSize(1))
+                .body(SECOND_ARGUMENTS + ".list[0].htmlBody", equalTo("Hello <b>someone</b>, and thank you for joining example.com!"))
+                .body(SECOND_ARGUMENTS + ".list[0].textBody", equalTo("Hello someone, and thank you for joining example.com, text version!"))
+            ;
+            return true;
+        } catch(AssertionError e) {
+            return false;
+        }
+    }
+
+    @Test
     public void movingAMessageIsNotSupported() throws Exception {
         String newMailboxName = "heartFolder";
         jmapServer.serverProbe().createMailbox("#private", username, newMailboxName);

http://git-wip-us.apache.org/repos/asf/james-project/blob/812b9129/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
index 0240ccc..19e7c63 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
@@ -27,7 +27,6 @@ import java.util.TimeZone;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
-import org.apache.commons.lang.NotImplementedException;
 import org.apache.james.jmap.model.CreationMessage;
 import org.apache.james.jmap.model.CreationMessage.DraftEmailer;
 import org.apache.james.jmap.model.CreationMessageId;
@@ -35,21 +34,33 @@ import org.apache.james.mime4j.Charsets;
 import org.apache.james.mime4j.codec.DecodeMonitor;
 import org.apache.james.mime4j.dom.FieldParser;
 import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.dom.Multipart;
 import org.apache.james.mime4j.dom.TextBody;
 import org.apache.james.mime4j.dom.address.Mailbox;
 import org.apache.james.mime4j.dom.field.UnstructuredField;
 import org.apache.james.mime4j.field.UnstructuredFieldImpl;
 import org.apache.james.mime4j.message.BasicBodyFactory;
+import org.apache.james.mime4j.message.BodyPartBuilder;
 import org.apache.james.mime4j.message.DefaultMessageWriter;
 import org.apache.james.mime4j.message.MessageBuilder;
+import org.apache.james.mime4j.message.MultipartBuilder;
 import org.apache.james.mime4j.stream.Field;
 import org.apache.james.mime4j.stream.NameValuePair;
 import org.apache.james.mime4j.stream.RawField;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Throwables;
+import com.google.common.net.MediaType;
 
 public class MIMEMessageConverter {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MIMEMessageConverter.class);
+
+    private static final String PLAIN_TEXT_MEDIA_TYPE = MediaType.PLAIN_TEXT_UTF_8.withoutParameters().toString();
+    private static final String HTML_MEDIA_TYPE = MediaType.HTML_UTF_8.withoutParameters().toString();
+    private static final NameValuePair UTF_8_CHARSET = new NameValuePair("charset", Charsets.UTF_8.name());
+    private static final String MIXED_SUB_TYPE = "mixed";
 
     private final BasicBodyFactory bodyFactory;
 
@@ -75,7 +86,11 @@ public class MIMEMessageConverter {
         }
 
         MessageBuilder messageBuilder = MessageBuilder.create();
-        messageBuilder.setBody(createTextBody(creationMessageEntry.getMessage()));
+        if (mixedTextAndHtml(creationMessageEntry.getMessage())) {
+            messageBuilder.setBody(createMultipartBody(creationMessageEntry.getMessage()));
+        } else {
+            messageBuilder.setBody(createTextBody(creationMessageEntry.getMessage()));
+        }
         buildMimeHeaders(messageBuilder, creationMessageEntry.getCreationId(), creationMessageEntry.getMessage());
         return messageBuilder.build();
     }
@@ -106,7 +121,9 @@ public class MIMEMessageConverter {
         // note that date conversion probably lose milliseconds!
         messageBuilder.setDate(Date.from(newMessage.getDate().toInstant()), TimeZone.getTimeZone(newMessage.getDate().getZone()));
         newMessage.getInReplyToMessageId().ifPresent(addInReplyToHeader(messageBuilder::addField));
-        newMessage.getHtmlBody().ifPresent(x -> messageBuilder.setContentType("text/html", new NameValuePair("charset", "utf-8")));
+        if (!mixedTextAndHtml(newMessage)) {
+            newMessage.getHtmlBody().ifPresent(x -> messageBuilder.setContentType(HTML_MEDIA_TYPE, UTF_8_CHARSET));
+        }
     }
 
     private Consumer<String> addInReplyToHeader(Consumer<Field> headerAppender) {
@@ -117,16 +134,37 @@ public class MIMEMessageConverter {
         };
     }
 
+    private boolean mixedTextAndHtml(CreationMessage newMessage) {
+        return newMessage.getTextBody().isPresent() && newMessage.getHtmlBody().isPresent();
+    }
+
     private TextBody createTextBody(CreationMessage newMessage) {
-        if (newMessage.getTextBody().isPresent() && newMessage.getHtmlBody().isPresent()) {
-            throw new NotImplementedException("Converter can't handle yet htmlBody and textBody in the same message");
-        }
         String body = newMessage.getHtmlBody()
                         .orElse(newMessage.getTextBody()
                                 .orElse(""));
         return bodyFactory.textBody(body, Charsets.UTF_8);
     }
 
+    private Multipart createMultipartBody(CreationMessage newMessage) {
+        try {
+            return MultipartBuilder.create(MIXED_SUB_TYPE)
+                    .addBodyPart(BodyPartBuilder.create()
+                            .use(bodyFactory)
+                            .setBody(newMessage.getTextBody().get(), Charsets.UTF_8)
+                            .setContentType(PLAIN_TEXT_MEDIA_TYPE, UTF_8_CHARSET)
+                            .build())
+                    .addBodyPart(BodyPartBuilder.create()
+                            .use(bodyFactory)
+                            .setBody(newMessage.getHtmlBody().get(), Charsets.UTF_8)
+                            .setContentType(HTML_MEDIA_TYPE, UTF_8_CHARSET)
+                            .build())
+                    .build();
+        } catch (IOException e) {
+            LOGGER.error("Error while creating textBody \n"+ newMessage.getTextBody().get() +"\n or htmlBody \n" + newMessage.getHtmlBody().get(), e);
+            throw Throwables.propagate(e);
+        }
+    }
+
     private Mailbox convertEmailToMimeHeader(DraftEmailer address) {
         if (!address.hasValidEmail()) {
             throw new IllegalArgumentException("address");

http://git-wip-us.apache.org/repos/asf/james-project/blob/812b9129/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
index c9fb01d..c2cf064 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
@@ -26,12 +26,12 @@ import java.time.Instant;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 
-import org.apache.commons.lang.NotImplementedException;
 import org.apache.james.jmap.model.CreationMessage;
 import org.apache.james.jmap.model.CreationMessage.DraftEmailer;
 import org.apache.james.jmap.model.CreationMessageId;
 import org.apache.james.mime4j.Charsets;
 import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.dom.Multipart;
 import org.apache.james.mime4j.dom.TextBody;
 import org.apache.james.mime4j.dom.address.Mailbox;
 import org.apache.james.mime4j.message.BasicBodyFactory;
@@ -176,8 +176,8 @@ public class MIMEMessageConverterTest {
         assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
     }
 
-    @Test(expected=NotImplementedException.class)
-    public void convertToMimeShouldFailWhenHtmlBodyAndTxtBodyProvided() {
+    @Test
+    public void convertToMimeShouldGenerateMultipartWhenHtmlBodyAndTextBodyProvided() throws Exception {
         // Given
         MIMEMessageConverter sut = new MIMEMessageConverter();
 
@@ -190,8 +190,48 @@ public class MIMEMessageConverterTest {
                 .build();
 
         // When
-        sut.convertToMime(new MessageWithId.CreationMessageEntry(
+        Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry(
                 CreationMessageId.of("user|mailbox|1"), testMessage));
+
+        // Then
+        assertThat(result.getBody()).isInstanceOf(Multipart.class);
+        assertThat(result.isMultipart()).isTrue();
+        Multipart typedResult = (Multipart)result.getBody();
+        assertThat(typedResult.getBodyParts()).hasSize(2);
+    }
+
+    @Test
+    public void convertShouldGenerateExpectedMultipartWhenHtmlAndTextBodyProvided() throws Exception {
+        // Given
+        MIMEMessageConverter sut = new MIMEMessageConverter();
+
+        CreationMessage testMessage = CreationMessage.builder()
+                .mailboxIds(ImmutableList.of("dead-bada55"))
+                .subject("subject")
+                .from(DraftEmailer.builder().name("sender").build())
+                .textBody("Hello all!")
+                .htmlBody("Hello <b>all</b>!")
+                .build();
+
+        String expectedHeaders = "MIME-Version: 1.0\r\n" +
+                "Content-Type: multipart/mixed;\r\n" +
+                " boundary=\"-=Part.0.";
+        String expectedPart1 = "Content-Type: text/plain; charset=UTF-8\r\n" +
+                "\r\n" +
+                "Hello all!\r\n";
+        String expectedPart2 = "Content-Type: text/html; charset=UTF-8\r\n" +
+                "\r\n" +
+                "Hello <b>all</b>!\r\n";
+
+        // When
+        byte[] convert = sut.convert(new MessageWithId.CreationMessageEntry(
+                CreationMessageId.of("user|mailbox|1"), testMessage));
+
+        // Then
+        String actual = new String(convert, Charsets.UTF_8);
+        assertThat(actual).startsWith(expectedHeaders);
+        assertThat(actual).contains(expectedPart1);
+        assertThat(actual).contains(expectedPart2);
     }
 
     @Test


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