blob: d972f9bb84bd0d10ec228d89d05a422bf28a82b3 [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you under the Apache License, Version 2.0 (the *
* "License"); you may not use this file except in compliance *
* with the License. You may obtain a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, *
* software distributed under the License is distributed on an *
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
* KIND, either express or implied. See the License for the *
* specific language governing permissions and limitations *
* under the License. *
****************************************************************/
package org.apache.james.jmap.draft.methods;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Date;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.apache.james.core.Username;
import org.apache.james.jmap.draft.methods.ValueWithId.MessageWithId;
import org.apache.james.jmap.model.Attachment;
import org.apache.james.jmap.model.Blob;
import org.apache.james.jmap.model.BlobId;
import org.apache.james.jmap.draft.model.CreationMessage;
import org.apache.james.jmap.draft.model.CreationMessage.DraftEmailer;
import org.apache.james.jmap.draft.model.CreationMessageId;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.MailboxSessionUtil;
import org.apache.james.mailbox.model.StringBackedAttachmentId;
import org.apache.james.mime4j.dom.Entity;
import org.apache.james.mime4j.dom.Message;
import org.apache.james.mime4j.dom.Multipart;
import org.apache.james.mime4j.dom.SingleBody;
import org.apache.james.mime4j.dom.TextBody;
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.assertj.core.data.Index;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import com.github.fge.lambdas.Throwing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
class MIMEMessageConverterTest {
MailboxSession session;
@BeforeEach
void setUp() {
session = MailboxSessionUtil.create(Username.of("bob"));
}
@Test
void convertToMimeShouldAddInReplyToHeaderWhenProvided() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
String matchingMessageId = "unique-message-id";
CreationMessage messageHavingInReplyTo = CreationMessage.builder()
.from(DraftEmailer.builder().name("sender").build())
.inReplyToMessageId(matchingMessageId)
.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("In-Reply-To")).extracting(Field::getBody)
.containsOnly(matchingMessageId);
}
@Test
void convertToMimeShouldGenerateMessageId() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
CreationMessage message = CreationMessage.builder()
.mailboxIds(ImmutableList.of("dead-beef-1337"))
.subject("subject")
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), message), ImmutableList.of());
// Then
assertThat(result.getHeader().getFields("Message-ID")).extracting(Field::getBody)
.isNotNull();
}
@Test
void convertToMimeShouldGenerateMessageIdWhenSenderWithoutDomain() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
CreationMessage message = CreationMessage.builder()
.from(DraftEmailer.builder().email("sender").build())
.mailboxIds(ImmutableList.of("dead-beef-1337"))
.subject("subject")
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), message), ImmutableList.of());
// Then
assertThat(result.getHeader().getFields("Message-ID")).extracting(Field::getBody)
.isNotNull();
}
@Test
void convertToMimeShouldGenerateMessageIdContainingSenderDomain() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
CreationMessage message = CreationMessage.builder()
.from(DraftEmailer.builder().email("email@domain.com").build())
.mailboxIds(ImmutableList.of("dead-beef-1337"))
.subject("subject")
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), message), ImmutableList.of());
// Then
assertThat(result.getHeader().getFields("Message-ID")).hasSize(1);
assertThat(result.getHeader().getFields("Message-ID").get(0).getBody())
.contains("@domain.com");
}
@Test
void convertToMimeShouldAddHeaderWhenProvided() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
CreationMessage messageHavingInReplyTo = CreationMessage.builder()
.from(DraftEmailer.builder().name("sender").build())
.headers(ImmutableMap.of("FIRST", "first 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");
}
@Test
void convertToMimeShouldAddHeadersWhenProvided() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
CreationMessage messageHavingInReplyTo = CreationMessage.builder()
.from(DraftEmailer.builder().name("sender").build())
.headers(ImmutableMap.of("FIRST", "first value", "SECOND", "second 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");
assertThat(result.getHeader().getFields("SECOND")).extracting(Field::getBody)
.containsOnly("second value");
}
@Test
void convertToMimeShouldFilterGeneratedHeadersWhenProvided() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
String joesEmail = "joe@example.com";
CreationMessage messageHavingInReplyTo = CreationMessage.builder()
.from(DraftEmailer.builder().email(joesEmail).name("joe").build())
.headers(ImmutableMap.of("From", "hacker@example.com", "VALID", "valid header 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.getFrom()).extracting(Mailbox::getAddress)
.allMatch(f -> f.equals(joesEmail));
assertThat(result.getHeader().getFields("VALID")).extracting(Field::getBody)
.containsOnly("valid header value");
assertThat(result.getHeader().getFields("From")).extracting(Field::getBody)
.containsOnly("joe <joe@example.com>");
}
@Test
void convertToMimeShouldFilterGeneratedHeadersRegardlessOfCaseWhenProvided() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
String joesEmail = "joe@example.com";
CreationMessage messageHavingInReplyTo = CreationMessage.builder()
.from(DraftEmailer.builder().email(joesEmail).name("joe").build())
.headers(ImmutableMap.of("frOM", "hacker@example.com", "VALID", "valid header 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.getFrom()).extracting(Mailbox::getAddress)
.allMatch(f -> f.equals(joesEmail));
assertThat(result.getHeader().getFields("VALID")).extracting(Field::getBody)
.containsOnly("valid header value");
assertThat(result.getHeader().getFields("From")).extracting(Field::getBody)
.containsOnly("joe <joe@example.com>");
}
@Test
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
void convertToMimeShouldFilterEmptyHeaderNames() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
CreationMessage messageHavingInReplyTo = CreationMessage.builder()
.from(DraftEmailer.builder().name("joe").build())
.headers(ImmutableMap.of("", "empty header name 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("")).isEmpty();
}
@Test
void convertToMimeShouldFilterWhiteSpacesOnlyHeaderNames() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
CreationMessage messageHavingInReplyTo = CreationMessage.builder()
.from(DraftEmailer.builder().name("joe").build())
.headers(ImmutableMap.of(" ", "only spaces header name values"))
.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(" ")).isEmpty();
assertThat(result.getHeader().getFields("")).isEmpty();
}
@Test
void convertToMimeShouldThrowWhenMessageIsNull() {
MIMEMessageConverter sut = new MIMEMessageConverter();
assertThatThrownBy(() -> sut.convertToMime(
new ValueWithId.CreationMessageEntry(CreationMessageId.of("any"), null),
ImmutableList.of()))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void convertToMimeShouldSetBothFromAndSenderHeaders() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
String joesEmail = "joe@example.com";
CreationMessage testMessage = CreationMessage.builder()
.mailboxIds(ImmutableList.of("deadbeef-dead-beef-dead-beef"))
.subject("subject")
.from(DraftEmailer.builder().email(joesEmail).name("joe").build())
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getFrom()).extracting(Mailbox::getAddress).allMatch(f -> f.equals(joesEmail));
assertThat(result.getSender().getAddress()).isEqualTo(joesEmail);
}
@Test
void convertToMimeShouldSetCorrectLocalDate() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
Instant now = Instant.now();
ZonedDateTime messageDate = ZonedDateTime.ofInstant(now, ZoneId.systemDefault());
CreationMessage testMessage = CreationMessage.builder()
.mailboxId("dead-bada55")
.subject("subject")
.from(DraftEmailer.builder().name("sender").build())
.date(messageDate)
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getDate()).isEqualToIgnoringMillis(Date.from(now));
}
@Test
void convertToMimeShouldSetQuotedPrintableContentTransferEncodingWhenText() {
// 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();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getHeader()
.getField("Content-Transfer-Encoding")
.getBody())
.isEqualTo("quoted-printable");
}
@Test
void convertToMimeShouldSetTextBodyWhenProvided() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
TextBody expected = new BasicBodyFactory().textBody("Hello all!", StandardCharsets.UTF_8);
CreationMessage testMessage = CreationMessage.builder()
.mailboxId("dead-bada55")
.subject("subject")
.from(DraftEmailer.builder().name("sender").build())
.textBody("Hello all!")
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
}
@Test
void convertToMimeShouldSetEmptyBodyWhenNoBodyProvided() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
TextBody expected = new BasicBodyFactory().textBody("", StandardCharsets.UTF_8);
CreationMessage testMessage = CreationMessage.builder()
.mailboxId("dead-bada55")
.subject("subject")
.from(DraftEmailer.builder().name("sender").build())
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
}
@Test
void convertToMimeShouldSetHtmlBodyWhenProvided() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
TextBody expected = new BasicBodyFactory().textBody("Hello <b>all</b>!", StandardCharsets.UTF_8);
CreationMessage testMessage = CreationMessage.builder()
.mailboxId("dead-bada55")
.subject("subject")
.from(DraftEmailer.builder().name("sender").build())
.htmlBody("Hello <b>all</b>!")
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
}
@Test
void convertToMimeShouldGenerateMultipartWhenHtmlBodyAndTextBodyProvided() throws Exception {
// Given
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();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getBody()).isInstanceOf(Multipart.class);
assertThat(result.isMultipart()).isTrue();
assertThat(result.getMimeType()).isEqualTo("multipart/alternative");
Multipart typedResult = (Multipart)result.getBody();
assertThat(typedResult.getBodyParts()).hasSize(2);
}
@Test
void convertShouldGenerateExpectedMultipartWhenHtmlAndTextBodyProvided() throws Exception {
// Given
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();
String expectedHeaders = "MIME-Version: 1.0\r\n" +
"Content-Type: multipart/alternative;\r\n" +
" boundary=\"-=Part.";
String expectedPart1 = "Content-Type: text/plain; charset=UTF-8\r\n" +
"Content-Transfer-Encoding: quoted-printable\r\n" +
"\r\n" +
"Hello all!\r\n";
String expectedPart2 = "Content-Type: text/html; charset=UTF-8\r\n" +
"Content-Transfer-Encoding: quoted-printable\r\n" +
"\r\n" +
"Hello <b>all</b>!\r\n";
// When
byte[] convert = sut.convert(new MessageWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
String actual = new String(convert, StandardCharsets.UTF_8);
assertThat(actual).startsWith(expectedHeaders);
assertThat(actual).contains(expectedPart1);
assertThat(actual).contains(expectedPart2);
}
@Test
void convertToMimeShouldSetMimeTypeWhenTextBody() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
CreationMessage testMessage = CreationMessage.builder()
.mailboxId("dead-bada55")
.subject("subject")
.from(DraftEmailer.builder().name("sender").build())
.textBody("Hello all!")
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getMimeType()).isEqualTo("text/plain");
}
@Test
void convertToMimeShouldSetMimeTypeWhenHtmlBody() {
// 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();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getMimeType()).isEqualTo("text/html");
}
@Test
void convertToMimeShouldSetEmptyHtmlBodyWhenProvided() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
TextBody expected = new BasicBodyFactory().textBody("", StandardCharsets.UTF_8);
CreationMessage testMessage = CreationMessage.builder()
.mailboxId("dead-bada55")
.subject("subject")
.from(DraftEmailer.builder().name("sender").build())
.htmlBody("")
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
assertThat(result.getMimeType()).isEqualTo("text/html");
}
@Test
void convertToMimeShouldSetEmptyTextBodyWhenProvided() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
TextBody expected = new BasicBodyFactory().textBody("", StandardCharsets.UTF_8);
CreationMessage testMessage = CreationMessage.builder()
.mailboxId("dead-bada55")
.subject("subject")
.from(DraftEmailer.builder().name("sender").build())
.textBody("")
.build();
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
// Then
assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
assertThat(result.getMimeType()).isEqualTo("text/plain");
}
@Nested
class WithAttachments {
@Test
void convertToMimeShouldAddAttachment() throws Exception {
// 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();
String expectedCID = "cid";
String expectedMimeType = "image/png";
String text = "123456";
TextBody expectedBody = new BasicBodyFactory().textBody(text.getBytes(), StandardCharsets.UTF_8);
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
Attachment.WithBlob attachment = new Attachment.WithBlob(
Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid(expectedCID)
.type(expectedMimeType)
.isInline(true)
.size(text.getBytes().length)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(expectedMimeType)
.build());
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment));
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(expectedMimeType);
assertThat(part.getHeader().getField("Content-ID").getBody()).isEqualTo(expectedCID);
assertThat(part.getContentTransferEncoding()).isEqualTo("base64");
});
}
@Test
void convertToMimeShouldPreservePartCharset() throws Exception {
// 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();
String expectedCID = "cid";
String expectedMimeType = "text/calendar; charset=\"iso-8859-1\"";
String text = "123456";
TextBody expectedBody = new BasicBodyFactory().textBody(text.getBytes(), StandardCharsets.UTF_8);
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
Attachment.WithBlob attachment = new Attachment.WithBlob(
Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid(expectedCID)
.isInline(true)
.size(text.getBytes().length)
.type(expectedMimeType)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(expectedMimeType)
.build());
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment));
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();
CreationMessage testMessage = CreationMessage.builder()
.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);
String expectedCID = "cid";
String expectedMimeType = "image/png";
String text = "123456";
TextBody expectedAttachmentBody = new BasicBodyFactory().textBody(text.getBytes(), StandardCharsets.UTF_8);
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
Attachment.WithBlob attachment = new Attachment.WithBlob(
Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid(expectedCID)
.isInline(true)
.size(text.getBytes().length)
.type(expectedMimeType)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(expectedMimeType)
.build());
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment));
Multipart typedResult = (Multipart)result.getBody();
assertThat(typedResult.getBodyParts())
.hasSize(1)
.extracting(entity -> (Multipart) entity.getBody())
.flatExtracting(Multipart::getBodyParts)
.satisfies(Throwing.consumer(part -> {
assertThat(part.getBody()).isInstanceOf(Multipart.class);
assertThat(part.isMultipart()).isTrue();
assertThat(part.getMimeType()).isEqualTo("multipart/alternative");
assertThat(((Multipart)part.getBody()).getBodyParts()).hasSize(2);
SingleBody textPart = (SingleBody) ((Multipart)part.getBody()).getBodyParts().get(0).getBody();
SingleBody htmlPart = (SingleBody) ((Multipart)part.getBody()).getBodyParts().get(1).getBody();
assertThat(textPart.getInputStream()).hasBinaryContent("Hello all!".getBytes());
assertThat(htmlPart.getInputStream()).hasBinaryContent("Hello <b>all<b>!".getBytes());
}), 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
void convertShouldEncodeWhenNonASCIICharacters() {
// Given
MIMEMessageConverter sut = new MIMEMessageConverter();
CreationMessage testMessage = CreationMessage.builder()
.mailboxId("dead-bada55")
.subject("subject")
.from(DraftEmailer.builder().name("sender").build())
.htmlBody("Some non-ASCII characters: áÄÎßÿ")
.build();
// When
ImmutableList<Attachment.WithBlob> attachments = ImmutableList.of();
byte[] convert = sut.convert(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), attachments);
String expectedEncodedContent = "Some non-ASCII characters: =C3=A1=C3=84=C3=8E=C3=9F=C3=BF";
// Then
String actual = new String(convert, StandardCharsets.US_ASCII);
assertThat(actual).contains(expectedEncodedContent);
}
@Test
void convertToMimeShouldAddAttachmentAndContainsIndicationAboutTheWayToEncodeFilenamesAttachmentInTheInputStreamWhenSending() throws Exception {
// Given
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();
String expectedCID = "cid";
String expectedMimeType = "image/png";
String text = "123456";
String name = "ديناصور.png";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
Attachment.WithBlob attachment = new Attachment.WithBlob(
Attachment.builder()
.name(name)
.blobId(BlobId.of(blodId.getId()))
.cid(expectedCID)
.isInline(true)
.size(text.getBytes().length)
.type(expectedMimeType)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(expectedMimeType)
.build());
// When
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment));
Multipart typedResult = (Multipart)result.getBody();
assertThat(typedResult.getBodyParts())
.hasSize(1)
.extracting(entity -> (Multipart) entity.getBody())
.flatExtracting(Multipart::getBodyParts)
.anySatisfy(part -> assertThat(getNameParameterValue(part)).isEqualTo(name));
}
@Test
void convertToMimeShouldHaveMixedMultipart() throws Exception {
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();
String text = "123456";
String type = "image/png";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
Attachment.WithBlob attachment = new Attachment.WithBlob(
Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid")
.name("ديناصور.png")
.isInline(false)
.size(text.getBytes().length)
.type(type)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(type)
.build());
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment));
assertThat(result.getBody()).isInstanceOf(Multipart.class);
Multipart typedResult = (Multipart)result.getBody();
assertThat(typedResult.getSubType()).isEqualTo("mixed");
}
@Test
void convertToMimeShouldNotHaveInnerMultipartWhenNoInline() throws Exception {
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();
String text = "123456";
String type = "image/png";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
Attachment.WithBlob attachment = new Attachment.WithBlob(
Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid")
.name("ديناصور.png")
.isInline(false)
.size(text.getBytes().length)
.type(type)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(type)
.build());
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment));
Multipart typedResult = (Multipart)result.getBody();
assertThat(typedResult.getBodyParts())
.noneMatch(Entity::isMultipart);
}
@Test
void convertToMimeShouldHaveChildrenAttachmentParts() throws Exception {
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();
String text = "123456";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
String type = "image/png";
Attachment.WithBlob attachment = new Attachment.WithBlob(
Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid")
.name("ديناصور.png")
.isInline(false)
.size(text.getBytes().length)
.type(type)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(type)
.build());
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment));
Multipart typedResult = (Multipart)result.getBody();
assertThat(typedResult.getBodyParts())
.extracting(Entity::getDispositionType)
.anySatisfy(contentDisposition -> assertThat(contentDisposition).isEqualTo("attachment"));
}
@Test
void convertToMimeShouldNotThrowWhenNameInContentTypeFieldAndAttachmentMetadata() throws Exception {
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();
String text = "123456";
String type = "image/png; name=abc.png";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
Attachment.WithBlob attachment = new Attachment.WithBlob(
Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid")
.name("fgh.png")
.isInline(false)
.size(text.getBytes().length)
.type(type)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(type)
.build());
assertThatCode(() -> sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment)))
.doesNotThrowAnyException();
}
@Test
void attachmentNameShouldBeOverriddenWhenSpecified() throws Exception {
MIMEMessageConverter sut = new MIMEMessageConverter();
String text = "123456";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
String type = "image/png; name=abc.png; charset=\"iso-8859-1\"";
Attachment attachment = Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid")
.name("fgh.png")
.isInline(false)
.size(text.getBytes().length)
.type(type)
.build();
assertThat(sut.contentTypeField(attachment).getBody())
.isEqualTo("image/png; charset=iso-8859-1; name=\"=?US-ASCII?Q?fgh.png?=\"");
}
@Test
void nameShouldBeAddedToContentTypeWhenSpecified() throws Exception {
MIMEMessageConverter sut = new MIMEMessageConverter();
String text = "123456";
String type = "image/png";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
Attachment attachment = Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid")
.name("fgh.png")
.isInline(false)
.size(text.getBytes().length)
.type(type)
.build();
assertThat(sut.contentTypeField(attachment).getBody())
.isEqualTo("image/png; name=\"=?US-ASCII?Q?fgh.png?=\"");
}
@Test
void attachmentNameShouldBePreservedWhenNameNotSpecified() throws Exception {
MIMEMessageConverter sut = new MIMEMessageConverter();
String text = "123456";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
String type = "image/png; name=abc.png";
Attachment attachment = Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid")
.isInline(false)
.size(text.getBytes().length)
.type(type)
.build();
assertThat(sut.contentTypeField(attachment).getBody())
.isEqualTo(type);
}
@Test
void attachmentNameShouldBeUnspecifiedWhenNone() throws Exception {
MIMEMessageConverter sut = new MIMEMessageConverter();
String text = "123456";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
String type = "image/png";
Attachment attachment = Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid")
.isInline(false)
.size(text.getBytes().length)
.type(type)
.build();
assertThat(sut.contentTypeField(attachment).getBody())
.isEqualTo(type);
}
@Test
void convertToMimeShouldHaveChildMultipartWhenOnlyInline() throws Exception {
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();
String name = "ديناصور.png";
String text = "123456";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
String type = "image/png";
Attachment.WithBlob attachment = new Attachment.WithBlob(
Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid")
.name(name)
.isInline(true)
.size(text.getBytes().length)
.type(type)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(type)
.build());
Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment));
Multipart typedResult = (Multipart)result.getBody();
assertThat(typedResult.getBodyParts())
.hasSize(1)
.allMatch(Entity::isMultipart)
.extracting(entity -> (Multipart) entity.getBody())
.extracting(Multipart::getSubType)
.allSatisfy(subType -> assertThat(subType).isEqualTo("related"));
}
@Test
void convertToMimeShouldHaveChildMultipartWhenBothInlinesAndAttachments() throws Exception {
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();
String text = "inline data";
String type = "image/png";
StringBackedAttachmentId blodId = StringBackedAttachmentId.from("blodId");
Attachment.WithBlob inline = new Attachment.WithBlob(
Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid")
.name("ديناصور.png")
.isInline(true)
.size(text.getBytes().length)
.type(type)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(type)
.build());
String text2 = "attachment data";
final StringBackedAttachmentId blodId2 = StringBackedAttachmentId.from("blodId2");
Attachment.WithBlob attachment = new Attachment.WithBlob(
Attachment.builder()
.blobId(BlobId.of(blodId.getId()))
.cid("cid2")
.name("att.pdf")
.isInline(false)
.size(text.getBytes().length)
.type(type)
.build(),
Blob.builder()
.payload(() -> new ByteArrayInputStream(text.getBytes()))
.id(BlobId.of(blodId.getId()))
.size(text.getBytes().length)
.contentType(type)
.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");
}
}
}