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 aae1e1183385cfc7fbbf7bdb790cda9a8ae84771 Author: Tran Tien Duc <dt...@linagora.com> AuthorDate: Wed Oct 30 13:23:56 2019 +0700 JAMES-2944 wrapping by multipart/related when inline attachments --- .../jmap/draft/methods/MIMEMessageConverter.java | 48 +++- .../draft/methods/MIMEMessageConverterTest.java | 253 ++++++++++++--------- 2 files changed, 191 insertions(+), 110 deletions(-) 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 350a4aa..6057e1f 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 @@ -76,6 +76,7 @@ public class MIMEMessageConverter { private static final NameValuePair UTF_8_CHARSET = new NameValuePair("charset", StandardCharsets.UTF_8.name()); private static final String ALTERNATIVE_SUB_TYPE = "alternative"; private static final String MIXED_SUB_TYPE = "mixed"; + private static final String RELATED_SUB_TYPE = "related"; private static final String FIELD_PARAMETERS_SEPARATOR = ";"; private static final String QUOTED_PRINTABLE = "quoted-printable"; private static final String BASE64 = "base64"; @@ -178,7 +179,7 @@ public class MIMEMessageConverter { 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); @@ -204,13 +205,7 @@ public class MIMEMessageConverter { private Multipart createMultipart(CreationMessage newMessage, ImmutableList<MessageAttachment> messageAttachments) { try { if (hasAttachment(messageAttachments)) { - MultipartBuilder builder = MultipartBuilder.create(MIXED_SUB_TYPE); - addBody(newMessage, builder); - - Consumer<MessageAttachment> addAttachment = addAttachment(builder); - messageAttachments.forEach(addAttachment); - - return builder.build(); + return createMultipartWithAttachments(newMessage, messageAttachments); } else { return createMultipartAlternativeBody(newMessage); } @@ -220,6 +215,43 @@ public class MIMEMessageConverter { } } + private Multipart createMultipartWithAttachments(CreationMessage newMessage, ImmutableList<MessageAttachment> messageAttachments) throws IOException { + MultipartBuilder mixedMultipartBuilder = MultipartBuilder.create(MIXED_SUB_TYPE); + List<MessageAttachment> inlineAttachments = messageAttachments.stream() + .filter(MessageAttachment::isInline) + .collect(Guavate.toImmutableList()); + List<MessageAttachment> besideAttachments = messageAttachments.stream() + .filter(attachment -> !attachment.isInline()) + .collect(Guavate.toImmutableList()); + + if (inlineAttachments.size() > 0) { + mixedMultipartBuilder.addBodyPart(relatedInnerMessage(newMessage, inlineAttachments)); + } else { + addBody(newMessage, mixedMultipartBuilder); + } + + addAttachments(besideAttachments, mixedMultipartBuilder); + + return mixedMultipartBuilder.build(); + } + + private Message relatedInnerMessage(CreationMessage newMessage, List<MessageAttachment> inlines) throws IOException { + MultipartBuilder relatedMultipart = MultipartBuilder.create(RELATED_SUB_TYPE); + addBody(newMessage, relatedMultipart); + + return Message.Builder.of() + .setBody(addAttachments(inlines, relatedMultipart) + .build()) + .build(); + } + + private MultipartBuilder addAttachments(List<MessageAttachment> messageAttachments, + MultipartBuilder multipartBuilder) { + messageAttachments.forEach(addAttachment(multipartBuilder)); + + return multipartBuilder; + } + private void addBody(CreationMessage newMessage, MultipartBuilder builder) throws IOException { if (newMessage.getHtmlBody().isPresent() && newMessage.getTextBody().isPresent()) { Multipart body = createMultipartAlternativeBody(newMessage); 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 94966da..2b27933 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 @@ -45,7 +45,7 @@ import org.apache.james.mime4j.dom.address.Mailbox; import org.apache.james.mime4j.dom.field.ContentTypeField; import org.apache.james.mime4j.message.BasicBodyFactory; import org.apache.james.mime4j.stream.Field; -import org.junit.jupiter.api.Disabled; +import org.assertj.core.data.Index; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -582,46 +582,47 @@ class MIMEMessageConverterTest { class WithAttachments { @Test - void convertToMimeShouldAddAttachmentWhenOne() { + void convertToMimeShouldAddAttachment() { // Given MIMEMessageConverter sut = new MIMEMessageConverter(); CreationMessage testMessage = CreationMessage.builder() - .mailboxId("dead-bada55") - .subject("subject") - .from(DraftEmailer.builder().name("sender").build()) - .htmlBody("Hello <b>all<b>!") - .build(); + .mailboxId("dead-bada55") + .subject("subject") + .from(DraftEmailer.builder().name("sender").build()) + .htmlBody("Hello <b>all<b>!") + .build(); String expectedCID = "cid"; String expectedMimeType = "image/png"; String text = "123456"; TextBody expectedBody = new BasicBodyFactory().textBody(text.getBytes(), StandardCharsets.UTF_8); MessageAttachment attachment = MessageAttachment.builder() - .attachment(org.apache.james.mailbox.model.Attachment.builder() - .attachmentId(AttachmentId.from("blodId")) - .bytes(text.getBytes()) - .type(expectedMimeType) - .build()) - .cid(Cid.from(expectedCID)) - .isInline(true) - .build(); + .attachment(org.apache.james.mailbox.model.Attachment.builder() + .attachmentId(AttachmentId.from("blodId")) + .bytes(text.getBytes()) + .type(expectedMimeType) + .build()) + .cid(Cid.from(expectedCID)) + .isInline(true) + .build(); // When Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( - CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment)); - - // Then - assertThat(result.getBody()).isInstanceOf(Multipart.class); - assertThat(result.isMultipart()).isTrue(); + CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment)); Multipart typedResult = (Multipart)result.getBody(); - assertThat(typedResult.getBodyParts()).hasSize(2); - Entity attachmentPart = typedResult.getBodyParts().get(1); - assertThat(attachmentPart.getBody()).isEqualToComparingOnlyGivenFields(expectedBody, "content"); - assertThat(attachmentPart.getDispositionType()).isEqualTo("inline"); - assertThat(attachmentPart.getMimeType()).isEqualTo(expectedMimeType); - assertThat(attachmentPart.getHeader().getField("Content-ID").getBody()).isEqualTo(expectedCID); - assertThat(attachmentPart.getContentTransferEncoding()).isEqualTo("base64"); + + 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(expectedMimeType); + assertThat(part.getHeader().getField("Content-ID").getBody()).isEqualTo(expectedCID); + assertThat(part.getContentTransferEncoding()).isEqualTo("base64"); + }); } @Test @@ -630,12 +631,12 @@ class MIMEMessageConverterTest { MIMEMessageConverter sut = new MIMEMessageConverter(); CreationMessage testMessage = CreationMessage.builder() - .mailboxId("dead-bada55") - .subject("subject") - .from(DraftEmailer.builder().name("sender").build()) - .textBody("Hello all!") - .htmlBody("Hello <b>all<b>!") - .build(); + .mailboxId("dead-bada55") + .subject("subject") + .from(DraftEmailer.builder().name("sender").build()) + .textBody("Hello all!") + .htmlBody("Hello <b>all<b>!") + .build(); TextBody expectedTextBody = new BasicBodyFactory().textBody("Hello all!".getBytes(), StandardCharsets.UTF_8); TextBody expectedHtmlBody = new BasicBodyFactory().textBody("Hello <b>all<b>!".getBytes(), StandardCharsets.UTF_8); @@ -644,39 +645,40 @@ class MIMEMessageConverterTest { String text = "123456"; TextBody expectedAttachmentBody = new BasicBodyFactory().textBody(text.getBytes(), StandardCharsets.UTF_8); MessageAttachment attachment = MessageAttachment.builder() - .attachment(org.apache.james.mailbox.model.Attachment.builder() - .attachmentId(AttachmentId.from("blodId")) - .bytes(text.getBytes()) - .type(expectedMimeType) - .build()) - .cid(Cid.from(expectedCID)) - .isInline(true) - .build(); + .attachment(org.apache.james.mailbox.model.Attachment.builder() + .attachmentId(AttachmentId.from("blodId")) + .bytes(text.getBytes()) + .type(expectedMimeType) + .build()) + .cid(Cid.from(expectedCID)) + .isInline(true) + .build(); // When Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( - CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment)); - - // Then - assertThat(result.getBody()).isInstanceOf(Multipart.class); - assertThat(result.isMultipart()).isTrue(); + CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment)); Multipart typedResult = (Multipart)result.getBody(); - assertThat(typedResult.getBodyParts()).hasSize(2); - Entity mainBodyPart = typedResult.getBodyParts().get(0); - assertThat(mainBodyPart.getBody()).isInstanceOf(Multipart.class); - assertThat(mainBodyPart.isMultipart()).isTrue(); - assertThat(mainBodyPart.getMimeType()).isEqualTo("multipart/alternative"); - assertThat(((Multipart)mainBodyPart.getBody()).getBodyParts()).hasSize(2); - Entity textPart = ((Multipart)mainBodyPart.getBody()).getBodyParts().get(0); - Entity htmlPart = ((Multipart)mainBodyPart.getBody()).getBodyParts().get(1); - assertThat(textPart.getBody()).isEqualToComparingOnlyGivenFields(expectedTextBody, "content"); - assertThat(htmlPart.getBody()).isEqualToComparingOnlyGivenFields(expectedHtmlBody, "content"); - - Entity attachmentPart = typedResult.getBodyParts().get(1); - assertThat(attachmentPart.getBody()).isEqualToComparingOnlyGivenFields(expectedAttachmentBody, "content"); - assertThat(attachmentPart.getDispositionType()).isEqualTo("inline"); - assertThat(attachmentPart.getMimeType()).isEqualTo(expectedMimeType); - assertThat(attachmentPart.getHeader().getField("Content-ID").getBody()).isEqualTo(expectedCID); + + assertThat(typedResult.getBodyParts()) + .hasSize(1) + .extracting(entity -> (Multipart) entity.getBody()) + .flatExtracting(Multipart::getBodyParts) + .satisfies(part -> { + assertThat(part.getBody()).isInstanceOf(Multipart.class); + assertThat(part.isMultipart()).isTrue(); + assertThat(part.getMimeType()).isEqualTo("multipart/alternative"); + assertThat(((Multipart)part.getBody()).getBodyParts()).hasSize(2); + Entity textPart = ((Multipart)part.getBody()).getBodyParts().get(0); + Entity htmlPart = ((Multipart)part.getBody()).getBodyParts().get(1); + assertThat(textPart.getBody()).isEqualToComparingOnlyGivenFields(expectedTextBody, "content"); + assertThat(htmlPart.getBody()).isEqualToComparingOnlyGivenFields(expectedHtmlBody, "content"); + }, Index.atIndex(0)) + .satisfies(part -> { + assertThat(part.getBody()).isEqualToComparingOnlyGivenFields(expectedAttachmentBody, "content"); + assertThat(part.getDispositionType()).isEqualTo("inline"); + assertThat(part.getMimeType()).isEqualTo(expectedMimeType); + assertThat(part.getHeader().getField("Content-ID").getBody()).isEqualTo(expectedCID); + }, Index.atIndex(1)); } @Test @@ -709,11 +711,11 @@ class MIMEMessageConverterTest { MIMEMessageConverter sut = new MIMEMessageConverter(); CreationMessage testMessage = CreationMessage.builder() - .mailboxIds(ImmutableList.of("dead-bada55")) - .subject("subject") - .from(DraftEmailer.builder().name("sender").build()) - .htmlBody("Hello <b>all<b>!") - .build(); + .mailboxIds(ImmutableList.of("dead-bada55")) + .subject("subject") + .from(DraftEmailer.builder().name("sender").build()) + .htmlBody("Hello <b>all<b>!") + .build(); String expectedCID = "cid"; String expectedMimeType = "image/png"; @@ -721,29 +723,26 @@ class MIMEMessageConverterTest { String name = "ديناصور.png"; String expectedName = EncoderUtil.encodeEncodedWord(name, Usage.TEXT_TOKEN); MessageAttachment attachment = MessageAttachment.builder() - .name(name) - .attachment(org.apache.james.mailbox.model.Attachment.builder() - .attachmentId(AttachmentId.from("blodId")) - .bytes(text.getBytes()) - .type(expectedMimeType) - .build()) - .cid(Cid.from(expectedCID)) - .isInline(true) - .build(); + .name(name) + .attachment(org.apache.james.mailbox.model.Attachment.builder() + .attachmentId(AttachmentId.from("blodId")) + .bytes(text.getBytes()) + .type(expectedMimeType) + .build()) + .cid(Cid.from(expectedCID)) + .isInline(true) + .build(); // When Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( - CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment)); - - // Then - assertThat(result.getBody()).isInstanceOf(Multipart.class); - assertThat(result.isMultipart()).isTrue(); + CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment)); Multipart typedResult = (Multipart)result.getBody(); - assertThat(typedResult.getBodyParts()).hasSize(2); - Entity attachmentPart = typedResult.getBodyParts().get(1); - String filename = getNameParameterValue(attachmentPart); - assertThat(filename).isEqualTo(expectedName); + assertThat(typedResult.getBodyParts()) + .hasSize(1) + .extracting(entity -> (Multipart) entity.getBody()) + .flatExtracting(Multipart::getBodyParts) + .anySatisfy(part -> assertThat(getNameParameterValue(part)).isEqualTo(expectedName)); } @@ -808,7 +807,7 @@ class MIMEMessageConverterTest { } @Test - void convertToMimeShouldNotHaveChildrenAttachmentParts() { + void convertToMimeShouldHaveChildrenAttachmentParts() { MIMEMessageConverter sut = new MIMEMessageConverter(); CreationMessage testMessage = CreationMessage.builder() @@ -838,29 +837,28 @@ class MIMEMessageConverterTest { .anySatisfy(contentDisposition -> assertThat(contentDisposition).isEqualTo("attachment")); } - @Disabled("Current structure is mixed -> alternative, attachments") @Test - void convertToMimeShouldHaveChildMultipartWhenInline() { + void convertToMimeShouldHaveChildMultipartWhenOnlyInline() { MIMEMessageConverter sut = new MIMEMessageConverter(); CreationMessage testMessage = CreationMessage.builder() - .mailboxIds(ImmutableList.of("dead-bada55")) - .subject("subject") - .from(DraftEmailer.builder().name("sender").build()) - .htmlBody("Hello <b>all<b>!") - .build(); + .mailboxIds(ImmutableList.of("dead-bada55")) + .subject("subject") + .from(DraftEmailer.builder().name("sender").build()) + .htmlBody("Hello <b>all<b>!") + .build(); String name = "ديناصور.png"; MessageAttachment attachment = MessageAttachment.builder() - .name(name) - .attachment(org.apache.james.mailbox.model.Attachment.builder() - .attachmentId(AttachmentId.from("blodId")) - .bytes("123456".getBytes()) - .type("image/png") - .build()) - .cid(Cid.from("cid")) - .isInline(true) - .build(); + .name(name) + .attachment(org.apache.james.mailbox.model.Attachment.builder() + .attachmentId(AttachmentId.from("blodId")) + .bytes("123456".getBytes()) + .type("image/png") + .build()) + .cid(Cid.from("cid")) + .isInline(true) + .build(); Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment)); @@ -874,6 +872,57 @@ class MIMEMessageConverterTest { .allSatisfy(subType -> assertThat(subType).isEqualTo("related")); } + @Test + void convertToMimeShouldHaveChildMultipartWhenBothInlinesAndAttachments() { + MIMEMessageConverter sut = new MIMEMessageConverter(); + + CreationMessage testMessage = CreationMessage.builder() + .mailboxIds(ImmutableList.of("dead-bada55")) + .subject("subject") + .from(DraftEmailer.builder().name("sender").build()) + .htmlBody("Hello <b>all<b>!") + .build(); + + MessageAttachment inline = MessageAttachment.builder() + .name("ديناصور.png") + .attachment(org.apache.james.mailbox.model.Attachment.builder() + .attachmentId(AttachmentId.from("blodId")) + .bytes("inline data".getBytes()) + .type("image/png") + .build()) + .cid(Cid.from("cid")) + .isInline(true) + .build(); + + MessageAttachment attachment = MessageAttachment.builder() + .name("att.pdf") + .attachment(org.apache.james.mailbox.model.Attachment.builder() + .attachmentId(AttachmentId.from("blodId2")) + .bytes("attachment data".getBytes()) + .type("image/png") + .build()) + .cid(Cid.from("cid2")) + .isInline(false) + .build(); + + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( + CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(inline, attachment)); + Multipart typedResult = (Multipart)result.getBody(); + + assertThat(typedResult.getBodyParts()) + .hasSize(2) + .satisfies(part -> { + Multipart multipartRelated = (Multipart) part.getBody(); + assertThat(multipartRelated.getSubType()).isEqualTo("related"); + assertThat(multipartRelated.getBodyParts()) + .extracting(Entity::getDispositionType) + .contains("inline"); + }, Index.atIndex(0)) + .satisfies(part -> { + assertThat(part.getDispositionType()).isEqualTo("attachment"); + }, Index.atIndex(1)); + } + private String getNameParameterValue(Entity attachmentPart) { return ((ContentTypeField) attachmentPart.getHeader().getField("Content-Type")).getParameter("name"); } --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org