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 ma...@apache.org on 2017/09/27 08:00:12 UTC

[3/6] james-project git commit: JAMES-2160 Following the JMAP specification, multi-valued fields should be separated by \n

JAMES-2160 Following the JMAP specification, multi-valued fields should be separated by \n


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

Branch: refs/heads/master
Commit: 4258cc6adbe78b14d8f5f963d47d73ac519c348e
Parents: 93cef1d
Author: Raphael Ouazana <ra...@linagora.com>
Authored: Mon Sep 25 16:43:38 2017 +0200
Committer: Matthieu Baechler <ma...@apache.org>
Committed: Wed Sep 27 09:58:16 2017 +0200

----------------------------------------------------------------------
 .../integration/SetMessagesMethodTest.java      | 125 ++++++++++++++++---
 .../java/org/apache/james/jmap/JMAPServlet.java |   2 +
 .../jmap/methods/MIMEMessageConverter.java      |   8 +-
 .../apache/james/jmap/model/MessageFactory.java |   4 +-
 .../jmap/methods/MIMEMessageConverterTest.java  |  21 ++++
 .../james/jmap/model/MessageFactoryTest.java    |  49 ++++++++
 6 files changed, 187 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/4258cc6a/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 f22472b..0e2e40f 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
@@ -77,6 +77,7 @@ import org.apache.james.modules.MailboxProbeImpl;
 import org.apache.james.probe.DataProbe;
 import org.apache.james.util.ZeroedInputStream;
 import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.IMAPMessageReader;
 import org.apache.james.utils.JmapGuiceProbe;
 import org.apache.james.utils.MessageIdProbe;
 import org.apache.james.utils.SMTPMessageSender;
@@ -101,11 +102,13 @@ import com.jayway.restassured.RestAssured;
 import com.jayway.restassured.builder.RequestSpecBuilder;
 import com.jayway.restassured.builder.ResponseSpecBuilder;
 import com.jayway.restassured.http.ContentType;
+import com.jayway.restassured.parsing.Parser;
 import com.jayway.restassured.specification.ResponseSpecification;
 
 public abstract class SetMessagesMethodTest {
     private static final String LOCALHOST_IP = "127.0.0.1";
     private static final int SMTP_PORT = 1025;
+    private static final int IMAP_PORT = 1143;
     private static final String FORWARDED = "$Forwarded";
     private static final int _1MB = 1024*1024;
     private static final String NAME = "[0][0]";
@@ -114,7 +117,8 @@ public abstract class SetMessagesMethodTest {
     private static final String SECOND_ARGUMENTS = "[1][1]";
     private static final String USERS_DOMAIN = "domain.tld";
     private static final String USERNAME = "username@" + USERS_DOMAIN;
-    public static final MailboxPath USER_MAILBOX = new MailboxPath(MailboxConstants.USER_NAMESPACE, USERNAME, "mailbox");
+    private static final String PASSWORD = "password";
+    private static final MailboxPath USER_MAILBOX = new MailboxPath(MailboxConstants.USER_NAMESPACE, USERNAME, "mailbox");
     private static final String NOT_UPDATED = ARGUMENTS + ".notUpdated";
 
     private ConditionFactory calmlyAwait;
@@ -146,12 +150,12 @@ public abstract class SetMessagesMethodTest {
                 .setPort(jmapServer.getProbe(JmapGuiceProbe.class).getJmapPort())
                 .build();
         RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
+        RestAssured.defaultParser = Parser.JSON;
 
-        String password = "password";
         dataProbe.addDomain(USERS_DOMAIN);
-        dataProbe.addUser(USERNAME, password);
+        dataProbe.addUser(USERNAME, PASSWORD);
         mailboxProbe.createMailbox("#private", USERNAME, DefaultMailboxes.INBOX);
-        accessToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(), USERNAME, password);
+        accessToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(), USERNAME, PASSWORD);
 
         mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME, DefaultMailboxes.OUTBOX);
         mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME, DefaultMailboxes.TRASH);
@@ -3433,23 +3437,23 @@ public abstract class SetMessagesMethodTest {
         String messageCreationId = "creationId1337";
         String fromAddress = USERNAME;
         String requestBody = "[" +
-            "  [" +
-            "    \"setMessages\","+
-            "    {" +
-            "      \"create\": { \"" + messageCreationId  + "\" : {" +
-            "        \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
-            "        \"to\": [{ \"name\": \"BOB\", \"email\": \"" + toUsername + "\"}]," +
-            "        \"headers\": { \"X-MY-SPECIAL-HEADER\": \"first header value\", \"OTHER-HEADER\": \"other value\"}," +
-            "        \"subject\": \"Thank you for joining example.com!\"," +
-            "        \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
-            "        \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
-            "      }}" +
-            "    }," +
-            "    \"#0\"" +
-            "  ]" +
-            "]";
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
+                "        \"to\": [{ \"name\": \"BOB\", \"email\": \"" + toUsername + "\"}]," +
+                "        \"headers\": { \"X-MY-SPECIAL-HEADER\": \"first header value\", \"OTHER-HEADER\": \"other value\"}," +
+                "        \"subject\": \"Thank you for joining example.com!\"," +
+                "        \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
+                "        \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
 
-        given()
+        with()
             .header("Authorization", accessToken.serialize())
             .body(requestBody)
         .post("/jmap");
@@ -3475,6 +3479,87 @@ public abstract class SetMessagesMethodTest {
     }
 
     @Test
+    public void setMessagesShouldSetMultivaluedUserAddedHeaders() throws Exception {
+        String messageCreationId = "creationId1337";
+        String requestBody = "[" +
+            "  [" +
+            "    \"setMessages\","+
+            "    {" +
+            "      \"create\": { \"" + messageCreationId  + "\" : {" +
+            "        \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME + "\"}," +
+            "        \"to\": [{ \"name\": \"Me\", \"email\": \"" + USERNAME + "\"}]," +
+            "        \"headers\": { \"X-MY-MULTIVALUATED-HEADER\": \"first value\nsecond value\"}," +
+            "        \"subject\": \"Thank you for joining example.com!\"," +
+            "        \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
+            "        \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
+            "      }}" +
+            "    }," +
+            "    \"#0\"" +
+            "  ]" +
+            "]";
+
+        String messageId = given()
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
+        // When
+        .post("/jmap")
+        .then()
+            .extract()
+            .body()
+            .<String>path(ARGUMENTS + ".created."+ messageCreationId +".id");
+
+        calmlyAwait.atMost(30, TimeUnit.SECONDS).until( () -> isAnyMessageFoundInInbox(accessToken));
+
+        String message = ARGUMENTS + ".list[0]";
+
+        with()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMessages\", {\"ids\": [\"" + messageId + "\"]}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .log().ifValidationFails()
+            .body(NAME, equalTo("messages"))
+            .body(ARGUMENTS + ".list", hasSize(1))
+            .body(message + ".headers", hasEntry("X-MY-MULTIVALUATED-HEADER", "first value\nsecond value"));
+    }
+
+    @Test
+    public void setMessagesShouldRenderCorrectlyInIMAPMultivaluedUserAddedHeaders() throws Exception {
+        String messageCreationId = "creationId1337";
+        String requestBody = "[" +
+            "  [" +
+            "    \"setMessages\","+
+            "    {" +
+            "      \"create\": { \"" + messageCreationId  + "\" : {" +
+            "        \"from\": { \"name\": \"Me\", \"email\": \"" + USERNAME + "\"}," +
+            "        \"to\": [{ \"name\": \"Me\", \"email\": \"" + USERNAME + "\"}]," +
+            "        \"headers\": { \"X-MY-MULTIVALUATED-HEADER\": \"first value\nsecond value\"}," +
+            "        \"subject\": \"Thank you for joining example.com!\"," +
+            "        \"textBody\": \"Hello someone, and thank you for joining example.com!\"," +
+            "        \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" +
+            "      }}" +
+            "    }," +
+            "    \"#0\"" +
+            "  ]" +
+            "]";
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
+        .post("/jmap");
+
+        calmlyAwait.atMost(30, TimeUnit.SECONDS).until( () -> isAnyMessageFoundInInbox(accessToken));
+
+        try (IMAPMessageReader imapMessageReader = new IMAPMessageReader(LOCALHOST_IP, IMAP_PORT);) {
+            assertThat(imapMessageReader.readFirstMessageHeadersInInbox(USERNAME, PASSWORD))
+                .contains("X-MY-MULTIVALUATED-HEADER: first value")
+                .contains("X-MY-MULTIVALUATED-HEADER: second value");
+        }
+    }
+
+    @Test
     public void setMessagesShouldFilterComputedHeadersFromUserAddedHeaders() throws Exception {
         String messageCreationId = "creationId1337";
         String requestBody = "[" +

http://git-wip-us.apache.org/repos/asf/james-project/blob/4258cc6a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServlet.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServlet.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServlet.java
index 8ace83e..c1f1fea 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServlet.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServlet.java
@@ -42,6 +42,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser.Feature;
 import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -62,6 +63,7 @@ public class JMAPServlet extends HttpServlet {
         this.requestHandler = requestHandler;
         this.metricFactory = metricFactory;
         this.objectMapper = new ObjectMapper();
+        objectMapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/james-project/blob/4258cc6a/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 c964c7d..cf6ce7e 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
@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
 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.jmap.model.MessageFactory;
 import org.apache.james.mailbox.model.MessageAttachment;
 import org.apache.james.mime4j.Charsets;
 import org.apache.james.mime4j.codec.DecodeMonitor;
@@ -166,9 +167,14 @@ public class MIMEMessageConverter {
         newMessage.getHeaders().entrySet().stream()
             .filter(header -> ! header.getKey().trim().isEmpty())
             .filter(header -> ! LOWERCASED_COMPUTED_HEADERS.contains(header.getKey().toLowerCase(Locale.ENGLISH)))
-            .forEach(header -> addHeader(messageBuilder, header.getKey(), header.getValue()));
+            .forEach(header -> addMultivaluedHeader(messageBuilder, header.getKey(), header.getValue()));
     }
 
+    private void addMultivaluedHeader(Message.Builder messageBuilder, String fieldName, String multipleValues) {
+        Splitter.on(MessageFactory.JMAP_MULTIVALUED_FIELD_DELIMITER).split(multipleValues)
+            .forEach(value -> addHeader(messageBuilder, fieldName, value));
+    }
+    
     private void addHeader(Message.Builder messageBuilder, String fieldName, String value) {
         FieldParser<UnstructuredField> parser = UnstructuredFieldImpl.PARSER;
         RawField rawField = new RawField(fieldName, value);

http://git-wip-us.apache.org/repos/asf/james-project/blob/4258cc6a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java
index 236be87..05f2589 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java
@@ -63,6 +63,8 @@ import com.google.common.collect.Sets;
 
 public class MessageFactory {
 
+    public static final String JMAP_MULTIVALUED_FIELD_DELIMITER = "\n";
+
     private static final MimeConfig MIME_ENTITY_CONFIG = MimeConfig.custom()
         .setMaxContentLen(-1)
         .setMaxHeaderCount(-1)
@@ -196,7 +198,7 @@ public class MessageFactory {
                 .map(MimeUtil::unscrambleHeaderValue)
                 .collect(Collectors.toList())
                 .stream()
-                .collect(Collectors.joining(","));
+                .collect(Collectors.joining(JMAP_MULTIVALUED_FIELD_DELIMITER));
         return Multimaps.index(fields, Field::getName)
                 .asMap()
                 .entrySet()

http://git-wip-us.apache.org/repos/asf/james-project/blob/4258cc6a/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 9e9a2cf..167408b 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
@@ -169,6 +169,27 @@ public class MIMEMessageConverterTest {
     }
 
     @Test
+    public void convertToMimeShouldAddMultivaluedHeadersWhenProvided() {
+        // Given
+        MIMEMessageConverter sut = new MIMEMessageConverter();
+
+        CreationMessage messageHavingInReplyTo = CreationMessage.builder()
+                .from(DraftEmailer.builder().name("sender").build())
+                .headers(ImmutableMap.of("FIRST", "first value\nsecond value"))
+                .mailboxIds(ImmutableList.of("dead-beef-1337"))
+                .subject("subject")
+                .build();
+
+        // When
+        Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
+                CreationMessageId.of("user|mailbox|1"), messageHavingInReplyTo), ImmutableList.of());
+
+        // Then
+        assertThat(result.getHeader().getFields("FIRST")).extracting(Field::getBody)
+            .containsOnly("first value", "second value");
+    }
+
+    @Test
     public void convertToMimeShouldFilterEmptyHeaderNames() {
         // Given
         MIMEMessageConverter sut = new MIMEMessageConverter();

http://git-wip-us.apache.org/repos/asf/james-project/blob/4258cc6a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageFactoryTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageFactoryTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageFactoryTest.java
index 2d86294..24b0a1e 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageFactoryTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageFactoryTest.java
@@ -233,6 +233,55 @@ public class MessageFactoryTest {
     }
 
     @Test
+    public void multivaluedHeadersShouldBeSeparatedByLineFeed() throws Exception {
+        Flags flags = new Flags(Flag.SEEN);
+        String headers = "From: user <us...@domain>\n"
+            + "Subject: test subject\n"
+            + "Multi-header: first value\n"
+            + "To: user1 <us...@domain>\n"
+            + "Multi-header: second value\n";
+        MetaDataWithContent testMail = MetaDataWithContent.builder()
+            .uid(MessageUid.of(2))
+            .flags(flags)
+            .size(headers.length())
+            .internalDate(INTERNAL_DATE)
+            .content(new ByteArrayInputStream(headers.getBytes(Charsets.UTF_8)))
+            .attachments(ImmutableList.of())
+            .mailboxId(MAILBOX_ID)
+            .messageId(TestMessageId.of(2))
+            .build();
+
+        Emailer user = Emailer.builder().name("user").email("user@domain").build();
+        Emailer user1 = Emailer.builder().name("user1").email("user1@domain").build();
+        ImmutableMap<String, String> headersMap = ImmutableMap.<String, String>builder()
+            .put("From", "user <us...@domain>")
+            .put("Subject", "test subject")
+            .put("Multi-header", "first value\nsecond value")
+            .put("To", "user1 <us...@domain>")
+            .put("MIME-Version", "1.0")
+            .build();
+        Message testee = messageFactory.fromMetaDataWithContent(testMail);
+        Message expected = Message.builder()
+            .id(TestMessageId.of(2))
+            .blobId(BlobId.of("blobId"))
+            .threadId("2")
+            .mailboxId(MAILBOX_ID)
+            .headers(headersMap)
+            .from(user)
+            .to(ImmutableList.of(user1))
+            .subject("test subject")
+            .date(Instant.parse("2012-02-03T14:30:42.000Z"))
+            .size(headers.length())
+            .preview("(Empty)")
+            .textBody(Optional.of(""))
+            .htmlBody(Optional.empty())
+            .flags(flags)
+            .build();
+
+        assertThat(testee).isEqualToComparingFieldByField(expected);
+    }
+
+    @Test
     public void textBodyShouldBeSetIntoMessage() throws Exception {
         String headers = "Subject: test subject\n";
         String body = "Mail body";


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