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