Skip to content

Commit

Permalink
JAMES-1828 Ignore attachments in text parts
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphael Ouazana committed Sep 27, 2016
1 parent 71ceb33 commit a8fe12f
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 21 deletions.
Expand Up @@ -2265,4 +2265,66 @@ public void attachmentsAndBodyShouldBeRetrievedWhenChainingSetMessagesAndGetMess
.body(firstAttachment + ".type", equalTo("text/html")) .body(firstAttachment + ".type", equalTo("text/html"))
.body(firstAttachment + ".size", equalTo((int) attachment.getSize())); .body(firstAttachment + ".size", equalTo((int) attachment.getSize()));
} }
@Test
public void attachmentAndEmptyBodyShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesWithTextAttachmentWithoutMailBody() throws Exception {
jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent");

Attachment attachment = Attachment.builder()
.bytes(("some text").getBytes(Charsets.UTF_8))
.type("text/plain; charset=UTF-8")
.build();
uploadTextAttachment(attachment);

String messageCreationId = "creationId";
String fromAddress = username;
String outboxId = getOutboxId(accessToken);
String requestBody = "[" +
" [" +
" \"setMessages\","+
" {" +
" \"create\": { \"" + messageCreationId + "\" : {" +
" \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
" \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," +
" \"subject\": \"Message with an attachment\"," +
" \"mailboxIds\": [\"" + outboxId + "\"], " +
" \"attachments\": [" +
" {\"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " +
" \"type\" : \"" + attachment.getType() + "\", " +
" \"size\" : " + attachment.getSize() + ", " +
" \"isInline\" : false }" +
" ]" +
" }}" +
" }," +
" \"#0\"" +
" ]" +
"]";

given()
.header("Authorization", accessToken.serialize())
.body(requestBody)
.when()
.post("/jmap");

calmlyAwait.atMost(30, TimeUnit.SECONDS).until( () -> isAnyMessageFoundInInbox(accessToken));

String firstMessage = ARGUMENTS + ".list[0]";
String firstAttachment = firstMessage + ".attachments[0]";
String presumedMessageId = "username@domain.tld|INBOX|1";
given()
.header("Authorization", accessToken.serialize())
.body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
.when()
.post("/jmap")
.then()
.statusCode(200)
.log().ifValidationFails()
.body(NAME, equalTo("messages"))
.body(ARGUMENTS + ".list", hasSize(1))
.body(firstMessage + ".textBody", isEmptyOrNullString())
.body(firstMessage + ".htmlBody", isEmptyOrNullString())
.body(firstMessage + ".attachments", hasSize(1))
.body(firstAttachment + ".blobId", equalTo(attachment.getAttachmentId().getId()))
.body(firstAttachment + ".type", equalTo("text/plain"))
.body(firstAttachment + ".size", equalTo((int) attachment.getSize()));
}
} }
Expand Up @@ -20,7 +20,6 @@
package org.apache.james.jmap.model; package org.apache.james.jmap.model;


import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;


Expand Down Expand Up @@ -65,11 +64,9 @@ private MessageContent parseMultipart(Entity entity, Multipart multipart) throws
private MessageContent parseMultipartContent(Entity entity, Multipart multipart) throws IOException { private MessageContent parseMultipartContent(Entity entity, Multipart multipart) throws IOException {
switch(entity.getMimeType()) { switch(entity.getMimeType()) {
case "multipart/alternative": case "multipart/alternative":
return parseMultipartAlternative(multipart); return retrieveHtmlAndPlainTextContent(multipart);
case "multipart/related":
return parseMultipartRelated(multipart);
default: default:
return parseMultipartMixed(multipart); return retrieveFirstHtmlOrPlainTextContent(multipart);
} }
} }


Expand All @@ -87,24 +84,13 @@ private String asString(TextBody textBody) throws IOException {
return IOUtils.toString(textBody.getInputStream(), textBody.getMimeCharset()); return IOUtils.toString(textBody.getInputStream(), textBody.getMimeCharset());
} }


private MessageContent parseMultipartMixed(Multipart multipart) throws IOException { private MessageContent retrieveHtmlAndPlainTextContent(Multipart multipart) throws IOException {
List<Entity> parts = multipart.getBodyParts();
if (! parts.isEmpty()) {
Entity firstPart = parts.get(0);
if (firstPart.getBody() instanceof TextBody) {
return parseTextBody(firstPart, (TextBody)firstPart.getBody());
}
}
return MessageContent.empty();
}

private MessageContent parseMultipartAlternative(Multipart multipart) throws IOException {
Optional<String> textBody = getFirstMatchingTextBody(multipart, "text/plain"); Optional<String> textBody = getFirstMatchingTextBody(multipart, "text/plain");
Optional<String> htmlBody = getFirstMatchingTextBody(multipart, "text/html"); Optional<String> htmlBody = getFirstMatchingTextBody(multipart, "text/html");
return new MessageContent(textBody, htmlBody); return new MessageContent(textBody, htmlBody);
} }


private MessageContent parseMultipartRelated(Multipart multipart) throws IOException { private MessageContent retrieveFirstHtmlOrPlainTextContent(Multipart multipart) throws IOException {
Optional<String> textBody = Optional.empty(); Optional<String> textBody = Optional.empty();
Optional<String> htmlBody = getFirstMatchingTextBody(multipart, "text/html"); Optional<String> htmlBody = getFirstMatchingTextBody(multipart, "text/html");
if (! htmlBody.isPresent()) { if (! htmlBody.isPresent()) {
Expand All @@ -117,13 +103,18 @@ private Optional<String> getFirstMatchingTextBody(Multipart multipart, String mi
return multipart.getBodyParts() return multipart.getBodyParts()
.stream() .stream()
.filter(part -> mimeType.equals(part.getMimeType())) .filter(part -> mimeType.equals(part.getMimeType()))
.filter(this::isNotAttachment)
.map(Entity::getBody) .map(Entity::getBody)
.filter(TextBody.class::isInstance) .filter(TextBody.class::isInstance)
.map(TextBody.class::cast) .map(TextBody.class::cast)
.findFirst() .findFirst()
.map(Throwing.function(this::asString).sneakyThrow()); .map(Throwing.function(this::asString).sneakyThrow());
} }


private boolean isNotAttachment(Entity part) {
return part.getDispositionType() == null;
}

public static class MessageContent { public static class MessageContent {
private final Optional<String> textBody; private final Optional<String> textBody;
private final Optional<String> htmlBody; private final Optional<String> htmlBody;
Expand Down
Expand Up @@ -39,17 +39,23 @@ public class MessageContentExtractorTest {
private static final String BINARY_CONTENT = "binary"; private static final String BINARY_CONTENT = "binary";
private static final String TEXT_CONTENT = "text content"; private static final String TEXT_CONTENT = "text content";
private static final String HTML_CONTENT = "<b>html</b> content"; private static final String HTML_CONTENT = "<b>html</b> content";
private static final String ATTACHMENT_CONTENT = "attachment content";


private MessageContentExtractor testee; private MessageContentExtractor testee;


private BodyPart htmlPart; private BodyPart htmlPart;
private BodyPart textPart; private BodyPart textPart;
private BodyPart textAttachment;


@Before @Before
public void setup() throws IOException { public void setup() throws IOException {
testee = new MessageContentExtractor(); testee = new MessageContentExtractor();
textPart = BodyPartBuilder.create().setBody(TEXT_CONTENT, "plain", Charsets.UTF_8).build(); textPart = BodyPartBuilder.create().setBody(TEXT_CONTENT, "plain", Charsets.UTF_8).build();
htmlPart = BodyPartBuilder.create().setBody(HTML_CONTENT, "html", Charsets.UTF_8).build(); htmlPart = BodyPartBuilder.create().setBody(HTML_CONTENT, "html", Charsets.UTF_8).build();
textAttachment = BodyPartBuilder.create()
.setBody(ATTACHMENT_CONTENT, "plain", Charsets.UTF_8)
.setContentDisposition("attachment")
.build();
} }


@Test @Test
Expand Down Expand Up @@ -123,16 +129,30 @@ public void extractShouldReturnTextWhenMultipartAlternativeWithoutHtmlPart() thr
} }


@Test @Test
public void extractShouldReturnFirstPartOnlyWhenMultipartMixedAndFirstPartIsText() throws IOException { public void extractShouldReturnFirstNonAttachmentPartWhenMultipartMixed() throws IOException {
Multipart multipart = MultipartBuilder.create("mixed") Multipart multipart = MultipartBuilder.create("mixed")
.addBodyPart(textPart) .addBodyPart(textAttachment)
.addBodyPart(htmlPart) .addBodyPart(htmlPart)
.addBodyPart(textPart)
.build(); .build();
Message message = MessageBuilder.create() Message message = MessageBuilder.create()
.setBody(multipart) .setBody(multipart)
.build(); .build();
MessageContent actual = testee.extract(message); MessageContent actual = testee.extract(message);
assertThat(actual.getTextBody()).contains(TEXT_CONTENT); assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
assertThat(actual.getTextBody()).isEmpty();
}

@Test
public void extractShouldReturnEmptyWhenMultipartMixedAndFirstPartIsATextAttachment() throws IOException {
Multipart multipart = MultipartBuilder.create("mixed")
.addBodyPart(textAttachment)
.build();
Message message = MessageBuilder.create()
.setBody(multipart)
.build();
MessageContent actual = testee.extract(message);
assertThat(actual.getTextBody()).isEmpty();
assertThat(actual.getHtmlBody()).isEmpty(); assertThat(actual.getHtmlBody()).isEmpty();
} }


Expand Down

0 comments on commit a8fe12f

Please sign in to comment.