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 bt...@apache.org on 2020/04/27 02:19:37 UTC
[james-project] 01/14: JAMES-2997 MessageParser &
MIMEMessageConverter should preserve charset
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 <bt...@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: <AM...@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: <AM...@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\": \"someone@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-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org