This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 411e330e8c7a84d8c46396e20acab6722fe96cd0 Author: Benoit Tellier <btell...@linagora.com> AuthorDate: Sun Apr 19 18:50:42 2020 +0700 JAMES-2997 MessageParser & MIMEMessageConverter should preserve charset Previous implementations where not preserving charset upon: - composing a message via JMAP - storing an attachment related to a mailbox Note that JMAP attachment upload was preserving charset. **master** is also affected by this issue but the problem was hidden as SetMessages was returning the expected attachment metadata and not the actual one. --- .../store/mail/model/impl/MessageParser.java | 21 ++++- .../store/mail/model/impl/MessageParserTest.java | 24 +++++- mailbox/store/src/test/resources/eml/charset.eml | 39 +++++++++ mailbox/store/src/test/resources/eml/charset2.eml | 52 ++++++++++++ .../methods/integration/SetMessagesMethodTest.java | 96 +++++++++++++++++++--- .../test/resources/cucumber/GetMessages.feature | 10 +-- .../jmap/draft/methods/MIMEMessageConverter.java | 20 ++--- .../draft/methods/MIMEMessageConverterTest.java | 48 +++++++++++ 8 files changed, 277 insertions(+), 33 deletions(-) diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java index 33d3406..53649fb 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java @@ -19,11 +19,14 @@ package org.apache.james.mailbox.store.mail.model.impl; +import static org.apache.james.mime4j.dom.field.ContentTypeField.PARAM_CHARSET; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.stream.Stream; @@ -38,6 +41,7 @@ import org.apache.james.mime4j.dom.field.ContentDispositionField; import org.apache.james.mime4j.dom.field.ContentIdField; import org.apache.james.mime4j.dom.field.ContentTypeField; import org.apache.james.mime4j.dom.field.ParsedField; +import org.apache.james.mime4j.field.Fields; import org.apache.james.mime4j.message.DefaultMessageBuilder; import org.apache.james.mime4j.message.DefaultMessageWriter; import org.apache.james.mime4j.stream.Field; @@ -156,7 +160,22 @@ public class MessageParser { } private Optional<String> contentType(Optional<ContentTypeField> contentTypeField) { - return contentTypeField.map(ContentTypeField::getMimeType); + return contentTypeField.map(this::contentTypePreserveCharset); + } + + private String contentTypePreserveCharset(ContentTypeField contentTypeField) { + Map<String, String> params = contentTypeField.getParameters() + .entrySet() + .stream() + .filter(param -> param.getKey().equals(PARAM_CHARSET)) + .collect(Guavate.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); + + try { + return Fields.contentType(contentTypeField.getMimeType(), params) + .getBody(); + } catch (IllegalArgumentException e) { + return contentTypeField.getMimeType(); + } } private Optional<String> name(Optional<ContentTypeField> contentTypeField, Optional<ContentDispositionField> contentDispositionField) { diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/MessageParserTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/MessageParserTest.java index 721b8b7..8fe2009 100644 --- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/MessageParserTest.java +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/MessageParserTest.java @@ -264,13 +264,33 @@ class MessageParserTest { } @Test + void getAttachmentsShouldRetrieveCharset() throws Exception { + List<ParsedAttachment> attachments = testee.retrieveAttachments( + ClassLoader.getSystemResourceAsStream("eml/charset.eml")); + + assertThat(attachments).hasSize(1) + .first() + .satisfies(attachment -> assertThat(attachment.getContentType()).isEqualTo("text/calendar; charset=iso-8859-1")); + } + + @Test + void getAttachmentsShouldRetrieveAllPartsCharset() throws Exception { + List<ParsedAttachment> attachments = testee.retrieveAttachments( + ClassLoader.getSystemResourceAsStream("eml/charset2.eml")); + + assertThat(attachments).hasSize(2) + .extracting(ParsedAttachment::getContentType) + .containsOnly("text/calendar; charset=iso-8859-1", "text/calendar; charset=iso-4444-5"); + } + + @Test void getAttachmentsShouldConsiderICSAsAttachments() throws Exception { List<ParsedAttachment> attachments = testee.retrieveAttachments( ClassLoader.getSystemResourceAsStream("eml/calendar.eml")); assertThat(attachments) .hasSize(1) - .allMatch(messageAttachment -> messageAttachment.getContentType().equals("text/calendar")); + .allMatch(messageAttachment -> messageAttachment.getContentType().equals("text/calendar; charset=utf-8")); } @Test @@ -306,6 +326,6 @@ class MessageParserTest { List<ParsedAttachment> result = testee.retrieveAttachments(new ByteArrayInputStream(DefaultMessageWriter.asBytes(message))); assertThat(result).hasSize(1) - .allMatch(attachment -> attachment.getContentType().equals(MDN.DISPOSITION_CONTENT_TYPE)); + .allMatch(attachment -> attachment.getContentType().equals("message/disposition-notification; charset=UTF-8")); } } diff --git a/mailbox/store/src/test/resources/eml/charset.eml b/mailbox/store/src/test/resources/eml/charset.eml new file mode 100644 index 0000000..957b4f1 --- /dev/null +++ b/mailbox/store/src/test/resources/eml/charset.eml @@ -0,0 +1,39 @@ +From: Test 1 <te...@linagora.com> +To: Test 2 <te...@linagora.com> +Subject: New Time Proposed: New event from Test: lole +Date: Tue, 13 Mar 2018 14:36:08 +0000 +Message-ID: <am5p190mb03542a58e344c68475a951af...@am5p190mb0354.eurp190.prod.outlook.com> +Accept-Language: en-US +Content-Language: en-US +Content-Type: multipart/alternative; + boundary="_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_" +MIME-Version: 1.0 + +--_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_ +Content-Type: text/plain; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +Will be better for me, thx + +--_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_ +Content-Type: text/html; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +<html> + <p>Will be better for me, thx</p> +</html> + +--_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_ +Content-Type: text/calendar; charset="iso-8859-1"; method=COUNTER +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSDQpNRVRIT0Q6Q09VTlRFUg0KUFJPRElEOk1pY3Jvc29mdCBFeGNoYW5n +ZSBTZXJ2ZXIgMjAxMA0KVkVSU0lPTjoyLjANCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpFdXJvcGUv +QmVybGluDQpCRUdJTjpTVEFOREFSRA0KRFRTVEFSVDoxNjAxMDEwMVQwMzAwMDANClRaT0ZGU0VU +RlJPTTorMDIwMA0KVFpPRkZTRVRUTzorMDEwMA0KUlJVTEU6RlJFUT1ZRUFSTFk7SU5URVJWQUw9 +MTtCWURBWT0tMVNVO0JZTU9OVEg9MTANCkVORDpTVEFOREFSRA0KQkVHSU46REFZTElHSFQNCkRU +U0VRVUVOQ0U6MA0KRFRTVEFNUDoyMDE4MDMxM1QxNDM2MDdaDQpDT01NRU5UO0xBTkdVQUdFPWVu +LVVTOldpbGwgYmUgYmV0dGVyIGZvciBtZVwsIHRoeFxuDQpFTkQ6VkVWRU5UDQpFTkQ6VkNBTEVO +REFSDQo= + +--_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_-- \ No newline at end of file diff --git a/mailbox/store/src/test/resources/eml/charset2.eml b/mailbox/store/src/test/resources/eml/charset2.eml new file mode 100644 index 0000000..7919d8c --- /dev/null +++ b/mailbox/store/src/test/resources/eml/charset2.eml @@ -0,0 +1,52 @@ +From: Test 1 <te...@linagora.com> +To: Test 2 <te...@linagora.com> +Subject: New Time Proposed: New event from Test: lole +Date: Tue, 13 Mar 2018 14:36:08 +0000 +Message-ID: <am5p190mb03542a58e344c68475a951af...@am5p190mb0354.eurp190.prod.outlook.com> +Accept-Language: en-US +Content-Language: en-US +Content-Type: multipart/alternative; + boundary="_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_" +MIME-Version: 1.0 + +--_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_ +Content-Type: text/plain; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +Will be better for me, thx + +--_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_ +Content-Type: text/html; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + +<html> + <p>Will be better for me, thx</p> +</html> + +--_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_ +Content-Type: text/calendar; charset="iso-8859-1"; method=COUNTER +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSDQpNRVRIT0Q6Q09VTlRFUg0KUFJPRElEOk1pY3Jvc29mdCBFeGNoYW5n +ZSBTZXJ2ZXIgMjAxMA0KVkVSU0lPTjoyLjANCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpFdXJvcGUv +QmVybGluDQpCRUdJTjpTVEFOREFSRA0KRFRTVEFSVDoxNjAxMDEwMVQwMzAwMDANClRaT0ZGU0VU +RlJPTTorMDIwMA0KVFpPRkZTRVRUTzorMDEwMA0KUlJVTEU6RlJFUT1ZRUFSTFk7SU5URVJWQUw9 +MTtCWURBWT0tMVNVO0JZTU9OVEg9MTANCkVORDpTVEFOREFSRA0KQkVHSU46REFZTElHSFQNCkRU +U0VRVUVOQ0U6MA0KRFRTVEFNUDoyMDE4MDMxM1QxNDM2MDdaDQpDT01NRU5UO0xBTkdVQUdFPWVu +LVVTOldpbGwgYmUgYmV0dGVyIGZvciBtZVwsIHRoeFxuDQpFTkQ6VkVWRU5UDQpFTkQ6VkNBTEVO +REFSDQo= + +--_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_ +Content-Type: text/calendar; charset="iso-4444-5"; method=COUNTER +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSDQpNRVRIT0Q6Q09VTlRFUg0KUFJPRElEOk1pY3Jvc29mdCBFeGNoYW5n +ZSBTZXJ2ZXIgMjAxMA0KVkVSU0lPTjoyLjANCkJFR0lOOlZUSU1FWk9ORQ0KVFpJRDpFdXJvcGUv +QmVybGluDQpCRUdJTjpTVEFOREFSRA0KRFRTVEFSVDoxNjAxMDEwMVQwMzAwMDANClRaT0ZGU0VU +RlJPTTorMDIwMA0KVFpPRkZTRVRUTzorMDEwMA0KUlJVTEU6RlJFUT1ZRUFSTFk7SU5URVJWQUw9 +MTtCWURBWT0tMVNVO0JZTU9OVEg9MTANCkVORDpTVEFOREFSRA0KQkVHSU46REFZTElHSFQNCkRU +U0VRVUVOQ0U6MA0KRFRTVEFNUDoyMDE4MDMxM1QxNDM2MDdaDQpDT01NRU5UO0xBTkdVQUdFPWVu +LVVTOldpbGwgYmUgYmV0dGVyIGZvciBtZVwsIHRoeFxuDQpFTkQ6VkVWRU5UDQpFTkQ6VkNBTEVO +REFSDQo= + +--_000_AM5P190MB03542A58E344F2C68475A951AFD20AM5P190MB0354EURP_-- \ No newline at end of file diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java index bf18630..c65bcda 100644 --- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java +++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java @@ -153,6 +153,7 @@ public abstract class SetMessagesMethodTest { private static final String NOT_UPDATED = ARGUMENTS + ".notUpdated"; private static final int BIG_MESSAGE_SIZE = 20 * 1024 * 1024; public static final String OCTET_CONTENT_TYPE = "application/octet-stream"; + public static final String OCTET_CONTENT_TYPE_UTF8 = "application/octet-stream; charset=UTF-8"; private AccessToken bobAccessToken; @@ -4016,12 +4017,81 @@ public abstract class SetMessagesMethodTest { createdPath + ".attachments[0].inlinedWithCid", createdPath + ".attachments[1].inlinedWithCid") .inPath(createdPath + ".attachments") .isEqualTo("[{" + - " \"type\":\"application/octet-stream\"," + + " \"type\":\"application/octet-stream; charset=UTF-8\"," + " \"size\":" + bytes1.length() + "," + " \"cid\":null," + " \"isInline\":false" + "}, {" + - " \"type\":\"application/octet-stream\"," + + " \"type\":\"application/octet-stream; charset=UTF-8\"," + + " \"size\":" + bytes2.length() + "," + + " \"cid\":\"123456789\"," + + " \"isInline\":true" + + "}]"); + } + + @Test + public void setMessagesShouldPreserveCharsetOfAttachment() throws Exception { + String bytes1 = "attachment"; + String bytes2 = "attachment2"; + AttachmentMetadata uploadedAttachment1 = uploadAttachment(OCTET_CONTENT_TYPE_UTF8, bytes1.getBytes(StandardCharsets.UTF_8)); + AttachmentMetadata uploadedAttachment2 = uploadAttachment(OCTET_CONTENT_TYPE_UTF8, bytes2.getBytes(StandardCharsets.UTF_8)); + + String messageCreationId = "creationId"; + String fromAddress = USERNAME.asString(); + String outboxId = getOutboxId(accessToken); + String requestBody = "[" + + " [" + + " \"setMessages\"," + + " {" + + " \"create\": { \"" + messageCreationId + "\" : {" + + " \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," + + " \"to\": [{ \"name\": \"BOB\", \"email\": \"some...@example.com\"}]," + + " \"subject\": \"Message with two attachments\"," + + " \"textBody\": \"Test body\"," + + " \"mailboxIds\": [\"" + outboxId + "\"], " + + " \"attachments\": [" + + " {\"blobId\" : \"" + uploadedAttachment1.getAttachmentId().getId() + "\", " + + " \"type\" : \"" + uploadedAttachment1.getType() + "\", " + + " \"size\" : " + uploadedAttachment1.getSize() + "}," + + " {\"blobId\" : \"" + uploadedAttachment2.getAttachmentId().getId() + "\", " + + " \"type\" : \"" + uploadedAttachment2.getType() + "\", " + + " \"size\" : " + uploadedAttachment2.getSize() + ", " + + " \"cid\" : \"123456789\", " + + " \"isInline\" : true }" + + " ]" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + String createdPath = ARGUMENTS + ".created[\"" + messageCreationId + "\"]"; + + String json = given() + .header("Authorization", accessToken.asString()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("messagesSet")) + .body(ARGUMENTS + ".notCreated", aMapWithSize(0)) + .body(ARGUMENTS + ".created", aMapWithSize(1)) + .body(createdPath + ".attachments", hasSize(2)) + .extract().asString(); + + assertThatJson(json) + .withOptions(new Options(Option.TREATING_NULL_AS_ABSENT, Option.IGNORING_ARRAY_ORDER, Option.IGNORING_EXTRA_FIELDS)) + .whenIgnoringPaths(createdPath + ".attachments[0].blobId", createdPath + ".attachments[1].blobId", + createdPath + ".attachments[0].inlinedWithCid", createdPath + ".attachments[1].inlinedWithCid") + .inPath(createdPath + ".attachments") + .isEqualTo("[{" + + " \"type\":\"application/octet-stream; charset=UTF-8\"," + + " \"size\":" + bytes1.length() + "," + + " \"cid\":null," + + " \"isInline\":false" + + "}, {" + + " \"type\":\"application/octet-stream; charset=UTF-8\"," + " \"size\":" + bytes2.length() + "," + " \"cid\":\"123456789\"," + " \"isInline\":true" + @@ -4302,7 +4372,7 @@ public abstract class SetMessagesMethodTest { .body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)) .body(firstMessage + ".attachments", hasSize(1)) - .body(firstAttachment + ".type", equalTo(OCTET_CONTENT_TYPE)) + .body(firstAttachment + ".type", equalTo(OCTET_CONTENT_TYPE_UTF8)) .body(firstAttachment + ".size", equalTo(rawBytes.length)) .body(firstAttachment + ".cid", equalTo("123456789")) .body(firstAttachment + ".isInline", equalTo(true)) @@ -4375,7 +4445,7 @@ public abstract class SetMessagesMethodTest { .body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)) .body(firstMessage + ".attachments", hasSize(1)) - .body(firstAttachment + ".type", equalTo(OCTET_CONTENT_TYPE)) + .body(firstAttachment + ".type", equalTo(OCTET_CONTENT_TYPE_UTF8)) .body(firstAttachment + ".size", equalTo(rawBytes.length)) .body(firstAttachment + ".cid", equalTo("123456789")) .body(firstAttachment + ".isInline", equalTo(true)) @@ -4472,7 +4542,7 @@ public abstract class SetMessagesMethodTest { .body(firstMessage + ".textBody", equalTo("Test body, plain text version")) .body(firstMessage + ".htmlBody", equalTo("Test <b>body</b>, HTML version")) .body(firstMessage + ".attachments", hasSize(1)) - .body(firstAttachment + ".type", equalTo("text/html")) + .body(firstAttachment + ".type", equalTo("text/html; charset=UTF-8")) .body(firstAttachment + ".size", equalTo(text.length())) .extract() .jsonPath() @@ -4547,7 +4617,7 @@ public abstract class SetMessagesMethodTest { .body(firstMessage + ".textBody", equalTo("Test body, plain text version")) .body(firstMessage + ".htmlBody", isEmptyOrNullString()) .body(firstMessage + ".attachments", hasSize(1)) - .body(firstAttachment + ".type", equalTo("text/html")) + .body(firstAttachment + ".type", equalTo("text/html; charset=UTF-8")) .body(firstAttachment + ".size", equalTo((int) uploadedAttachment.getSize())) .extract() .jsonPath() @@ -4632,7 +4702,7 @@ public abstract class SetMessagesMethodTest { .body(firstMessage + ".textBody", isEmptyOrNullString()) .body(firstMessage + ".htmlBody", isEmptyOrNullString()) .body(firstMessage + ".attachments", hasSize(1)) - .body(firstAttachment + ".type", equalTo("text/plain")) + .body(firstAttachment + ".type", equalTo("text/plain; charset=UTF-8")) .body(firstAttachment + ".size", equalTo((int) uploadedAttachment.getSize())) .extract() .jsonPath() @@ -5374,7 +5444,7 @@ public abstract class SetMessagesMethodTest { .body(ARGUMENTS + ".notCreated", aMapWithSize(0)) .body(ARGUMENTS + ".created", aMapWithSize(1)) .body(createdPath + ".attachments", hasSize(1)) - .body(singleAttachment + ".type", equalTo("text/html")) + .body(singleAttachment + ".type", equalTo("text/html; charset=UTF-8")) .body(singleAttachment + ".size", equalTo((int) uploadedAttachment.getSize())); } @@ -5493,9 +5563,9 @@ public abstract class SetMessagesMethodTest { @Test public void setMessagesShouldReturnAttachmentsWhenMessageHasInlinedAttachmentButNoCid() throws Exception { String bytes = "attachment"; - AttachmentMetadata uploadedAttachment1 = uploadAttachment(OCTET_CONTENT_TYPE, bytes.getBytes(StandardCharsets.UTF_8)); + AttachmentMetadata uploadedAttachment1 = uploadAttachment(OCTET_CONTENT_TYPE_UTF8, bytes.getBytes(StandardCharsets.UTF_8)); String bytes2 = "attachment2"; - AttachmentMetadata uploadedAttachment2 = uploadAttachment(OCTET_CONTENT_TYPE, bytes2.getBytes(StandardCharsets.UTF_8)); + AttachmentMetadata uploadedAttachment2 = uploadAttachment(OCTET_CONTENT_TYPE_UTF8, bytes2.getBytes(StandardCharsets.UTF_8)); String messageCreationId = "creationId"; String fromAddress = USERNAME.asString(); @@ -5545,11 +5615,11 @@ public abstract class SetMessagesMethodTest { .withOptions(new Options(Option.TREATING_NULL_AS_ABSENT, Option.IGNORING_ARRAY_ORDER, Option.IGNORING_EXTRA_FIELDS)) .inPath(createdPath + ".attachments") .isEqualTo("[{" + - " \"type\":\"application/octet-stream\"," + + " \"type\":\"application/octet-stream; charset=UTF-8\"," + " \"size\":" + bytes2.length() + "," + " \"isInline\":false" + "}, {" + - " \"type\":\"application/octet-stream\"," + + " \"type\":\"application/octet-stream; charset=UTF-8\"," + " \"size\":" + bytes.length() + "," + " \"isInline\":false" + // See JAMES-2258 inline should be false in case of no Content-ID for inlined attachment // Stored attachment will not be considered as having an inlined attachment. @@ -5684,7 +5754,7 @@ public abstract class SetMessagesMethodTest { .body(NAME, equalTo("messages")) .body(ARGUMENTS + ".list", hasSize(1)) .body(message + ".attachments", hasSize(1)) - .body(firstAttachment + ".type", equalTo("text/calendar")) + .body(firstAttachment + ".type", equalTo("text/calendar; charset=UTF-8")) .body(firstAttachment + ".blobId", not(isEmptyOrNullString())); } diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/resources/cucumber/GetMessages.feature b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/resources/cucumber/GetMessages.feature index d58c6a1..d65a73e 100644 --- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/resources/cucumber/GetMessages.feature +++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/resources/cucumber/GetMessages.feature @@ -445,8 +445,8 @@ Feature: GetMessages method And the hasAttachment of the message is "true" And the list of attachments of the message contains 1 attachments And the first attachment is: - |key | value | - |type |"text/calendar" | - |size |1096 | - |name |"event.ics" | - |isInline |false | + |key | value | + |type |"text/calendar; charset=UTF-8" | + |size |1096 | + |name |"event.ics" | + |isInline |false | diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MIMEMessageConverter.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MIMEMessageConverter.java index d5690f8..4864f05 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MIMEMessageConverter.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MIMEMessageConverter.java @@ -69,7 +69,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; import com.google.common.io.ByteStreams; import com.google.common.net.MediaType; @@ -329,25 +328,22 @@ public class MIMEMessageConverter { } private ContentTypeField contentTypeField(MessageAttachmentMetadata att) { - Builder<String, String> parameters = ImmutableMap.builder(); - if (att.getName().isPresent()) { - parameters.put("name", encode(att.getName().get())); - } String type = att.getAttachment().getType(); - if (type.contains(FIELD_PARAMETERS_SEPARATOR)) { - return Fields.contentType(contentTypeWithoutParameters(type), parameters.build()); + ContentTypeField typeAsField = Fields.contentType(type); + if (att.getName().isPresent()) { + return Fields.contentType(typeAsField.getMimeType(), + ImmutableMap.<String, String>builder() + .putAll(typeAsField.getParameters()) + .put("name", encode(att.getName().get())) + .build()); } - return Fields.contentType(type, parameters.build()); + return typeAsField; } private String encode(String name) { return EncoderUtil.encodeEncodedWord(name, Usage.TEXT_TOKEN); } - private String contentTypeWithoutParameters(String type) { - return Splitter.on(FIELD_PARAMETERS_SEPARATOR).splitToList(type).get(0); - } - private ContentDispositionField contentDispositionField(boolean isInline) { if (isInline) { return Fields.contentDisposition("inline"); diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/MIMEMessageConverterTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/MIMEMessageConverterTest.java index efca590..6cff89c 100644 --- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/MIMEMessageConverterTest.java +++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/MIMEMessageConverterTest.java @@ -646,6 +646,54 @@ class MIMEMessageConverterTest { } @Test + void convertToMimeShouldPreservePartCharset() throws Exception { + // Given + MIMEMessageConverter sut = new MIMEMessageConverter(attachmentContentLoader); + + CreationMessage testMessage = CreationMessage.builder() + .mailboxId("dead-bada55") + .subject("subject") + .from(DraftEmailer.builder().name("sender").build()) + .htmlBody("Hello <b>all<b>!") + .build(); + + String expectedCID = "cid"; + String expectedMimeType = "text/calendar; charset=\"iso-8859-1\""; + String text = "123456"; + TextBody expectedBody = new BasicBodyFactory().textBody(text.getBytes(), StandardCharsets.UTF_8); + AttachmentId blodId = AttachmentId.from("blodId"); + MessageAttachmentMetadata attachment = MessageAttachmentMetadata.builder() + .attachment(AttachmentMetadata.builder() + .attachmentId(blodId) + .size(text.getBytes().length) + .type(expectedMimeType) + .build()) + .cid(Cid.from(expectedCID)) + .isInline(true) + .build(); + when(attachmentContentLoader.load(attachment.getAttachment(), session)) + .thenReturn(new ByteArrayInputStream(text.getBytes())); + + // When + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( + CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment), session); + Multipart typedResult = (Multipart)result.getBody(); + + assertThat(typedResult.getBodyParts()) + .hasSize(1) + .extracting(entity -> (Multipart) entity.getBody()) + .flatExtracting(Multipart::getBodyParts) + .anySatisfy(part -> { + assertThat(part.getBody()).isEqualToComparingOnlyGivenFields(expectedBody, "content"); + assertThat(part.getDispositionType()).isEqualTo("inline"); + assertThat(part.getMimeType()).isEqualTo("text/calendar"); + assertThat(part.getCharset()).isEqualTo("iso-8859-1"); + assertThat(part.getHeader().getField("Content-ID").getBody()).isEqualTo(expectedCID); + assertThat(part.getContentTransferEncoding()).isEqualTo("base64"); + }); + } + + @Test void convertToMimeShouldAddAttachmentAndMultipartAlternativeWhenOneAttachementAndTextAndHtmlBody() throws Exception { // Given MIMEMessageConverter sut = new MIMEMessageConverter(attachmentContentLoader); --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org