From 528d5c4bec72b4787a221abd7720168a8cd6af42 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Fri, 28 Oct 2022 16:43:13 +0700 Subject: [PATCH 1/6] [UPGRADE] Adopt MIME4J 0.8.8 --- pom.xml | 2 +- .../james/util/mime/MessageContentExtractor.java | 15 +++++---------- .../apache/james/jmap/mail/EmailBodyPart.scala | 10 ++++------ 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index e9ab5c94342..fbd70b9d612 100644 --- a/pom.xml +++ b/pom.xml @@ -598,7 +598,7 @@ org.apache.james ${james.groupId}.protocols 5.17.2 - 0.8.7 + 0.8.8 3.2.0 10.14.2.0 2.19.0 diff --git a/server/container/util/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java b/server/container/util/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java index 7685f548d10..dabbab12d12 100644 --- a/server/container/util/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java +++ b/server/container/util/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java @@ -20,7 +20,6 @@ package org.apache.james.util.mime; import java.io.IOException; -import java.nio.charset.Charset; import java.util.Objects; import java.util.Optional; import java.util.function.Function; @@ -83,7 +82,7 @@ private MessageContent parseMultipartContent(Entity entity, Multipart multipart) } } - private MessageContent parseFirstFoundMultipart(Multipart multipart) throws IOException { + private MessageContent parseFirstFoundMultipart(Multipart multipart) { ThrowingFunction parseMultipart = firstPart -> parseMultipart(firstPart, (Multipart)firstPart.getBody()); return multipart.getBodyParts() .stream() @@ -94,13 +93,9 @@ private MessageContent parseFirstFoundMultipart(Multipart multipart) throws IOEx } private Optional asString(TextBody textBody) throws IOException { - return Optional.ofNullable(IOUtils.toString(textBody.getInputStream(), charset(Optional.ofNullable(textBody.getMimeCharset())))); - } - - private Charset charset(Optional charset) { - return charset - .map(Charset::forName) - .orElse(org.apache.james.mime4j.Charsets.DEFAULT_CHARSET); + return Optional.ofNullable(IOUtils.toString(textBody.getInputStream(), + Optional.ofNullable(textBody.getCharset()) + .orElse(org.apache.james.mime4j.Charsets.DEFAULT_CHARSET))); } private MessageContent retrieveHtmlAndPlainTextContent(Multipart multipart) throws IOException { @@ -114,7 +109,7 @@ private MessageContent retrieveHtmlAndPlainTextContent(Multipart multipart) thro return directChildTextBodies; } - private MessageContent retrieveFirstReadablePart(Multipart multipart) throws IOException { + private MessageContent retrieveFirstReadablePart(Multipart multipart) { return retrieveFirstReadablePartMatching(multipart, this::isNotAttachment) .orElseGet(() -> retrieveFirstReadablePartMatching(multipart, this::isInlinedWithoutCid) .orElse(MessageContent.empty())); diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala index b1fa6363a0e..78e0d98a295 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala @@ -19,6 +19,8 @@ package org.apache.james.jmap.mail +import java.io.OutputStream + import cats.implicits._ import com.google.common.io.CountingOutputStream import eu.timepit.refined.api.Refined @@ -31,6 +33,7 @@ import org.apache.james.jmap.core.Properties import org.apache.james.jmap.mail.EmailBodyPart.{FILENAME_PREFIX, MULTIPART_ALTERNATIVE, TEXT_HTML, TEXT_PLAIN} import org.apache.james.jmap.mail.PartId.PartIdValue import org.apache.james.mailbox.model.{Cid, MessageId, MessageResult} +import org.apache.james.mime4j.Charsets.DEFAULT_CHARSET import org.apache.james.mime4j.codec.{DecodeMonitor, DecoderUtil} import org.apache.james.mime4j.dom.field.{ContentLanguageField, ContentTypeField, FieldName} import org.apache.james.mime4j.dom.{Entity, Message, Multipart, TextBody => Mime4JTextBody} @@ -38,7 +41,6 @@ import org.apache.james.mime4j.message.{DefaultMessageBuilder, DefaultMessageWri import org.apache.james.mime4j.stream.{Field, MimeConfig, RawField} import org.apache.james.util.html.HtmlTextExtractor -import java.io.OutputStream import scala.jdk.CollectionConverters._ import scala.util.{Failure, Success, Try} @@ -229,7 +231,7 @@ case class EmailBodyPart(partId: PartId, def bodyContent: Try[Option[EmailBodyValue]] = entity.getBody match { case textBody: Mime4JTextBody => for { - value <- Try(IOUtils.toString(textBody.getInputStream, charset(Option(textBody.getMimeCharset)))) + value <- Try(IOUtils.toString(textBody.getInputStream, Option(textBody.getCharset).getOrElse(DEFAULT_CHARSET))) } yield { Some(EmailBodyValue(value = value, isEncodingProblem = IsEncodingProblem(false), @@ -246,10 +248,6 @@ case class EmailBodyPart(partId: PartId, case _ => bodyContent } - private def charset(charset: Option[String]): java.nio.charset.Charset = charset - .map(java.nio.charset.Charset.forName) - .getOrElse(org.apache.james.mime4j.Charsets.DEFAULT_CHARSET) - def textBody: List[EmailBodyPart] = selfBody ++ textBodyOfMultipart def htmlBody: List[EmailBodyPart] = selfBody ++ htmlBodyOfMultipart From be1d320664e2703c5bb889f993d0f37feb83e641 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Sat, 12 Nov 2022 13:47:19 +0700 Subject: [PATCH 2/6] [MIME4J-0.8.8] Systematically dispose Mime4J messages Allows leveraging "recycling" benefits... --- .../openjpa/AbstractJPAMailboxMessage.java | 6 +- .../james/vault/DeletedMessageConverter.java | 4 +- .../james/mailbox/store/MessageStorer.java | 18 +- .../store/mail/model/impl/MessageParser.java | 45 +++-- .../mailbox/store/search/MessageSearches.java | 18 -- .../mail/model/impl/MessageParserTest.java | 157 +++++++++--------- .../apache/james/jmap/api/model/Preview.java | 7 +- .../jmap/draft/methods/SendMDNProcessor.java | 1 + .../message/view/MessageFastViewFactory.java | 4 +- .../message/view/MessageFullViewFactory.java | 3 +- .../view/MessageHeaderViewFactory.java | 12 +- .../ExtractMDNOriginalJMAPMessageId.java | 1 + .../org/apache/james/jmap/mail/Email.scala | 23 ++- .../james/jmap/mail/EmailBodyPart.scala | 4 +- .../org/apache/james/jmap/mail/MDNParse.scala | 8 +- .../james/jmap/method/EmailImportMethod.scala | 5 +- .../james/jmap/method/MDNSendMethod.scala | 1 + .../data/jmap/EmailQueryViewPopulator.java | 1 + .../apache/james/webadmin/dto/MailDto.java | 8 +- 19 files changed, 188 insertions(+), 138 deletions(-) diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java index cae6c2554c8..5ffc93c194e 100644 --- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java +++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java @@ -518,12 +518,16 @@ public String toString() { public List getAttachments() { try { AtomicInteger counter = new AtomicInteger(0); - return new MessageParser().retrieveAttachments(getFullContent()) + MessageParser.ParsingResult parsingResult = new MessageParser().retrieveAttachments(getFullContent()); + ImmutableList result = parsingResult + .getAttachments() .stream() .map(Throwing.function( attachmentMetadata -> attachmentMetadata.asMessageAttachment(generateFixedAttachmentId(counter.incrementAndGet()), getMessageId())) .sneakyThrow()) .collect(ImmutableList.toImmutableList()); + parsingResult.dispose(); + return result; } catch (IOException e) { throw new RuntimeException(e); } diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java index d12c61c10a3..655cd9b54cc 100644 --- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java +++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java @@ -52,7 +52,7 @@ DeletedMessage convert(DeletedMessageVaultHook.DeletedMessageMailboxContext dele Optional mimeMessage = parseMessage(message); - return DeletedMessage.builder() + DeletedMessage deletedMessage = DeletedMessage.builder() .messageId(deletedMessageMailboxContext.getMessageId()) .originMailboxes(deletedMessageMailboxContext.getOwnerMailboxes()) .user(retrieveOwner(deletedMessageMailboxContext)) @@ -64,6 +64,8 @@ DeletedMessage convert(DeletedMessageVaultHook.DeletedMessageMailboxContext dele .size(message.getFullContentOctets()) .subject(mimeMessage.map(Message::getSubject)) .build(); + mimeMessage.ifPresent(Message::dispose); + return deletedMessage; } private Optional parseMessage(org.apache.james.mailbox.store.mail.model.Message message) throws IOException { diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java index 797648020c9..574fcb7da9e 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java @@ -45,8 +45,11 @@ import org.apache.james.mailbox.store.mail.model.impl.MessageParser; import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; import org.apache.james.mailbox.store.mail.utils.MimeMessageHeadersUtil; +import org.apache.james.mime4j.codec.DecodeMonitor; import org.apache.james.mime4j.dom.Message; +import org.apache.james.mime4j.message.DefaultMessageBuilder; import org.apache.james.mime4j.message.HeaderImpl; +import org.apache.james.mime4j.stream.MimeConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -115,25 +118,28 @@ public Mono>>> ap } private Mono> storeAttachments(MessageId messageId, Content messageContent, Optional maybeMessage, MailboxSession session) { - List attachments = extractAttachments(messageContent, maybeMessage); + MessageParser.ParsingResult attachments = extractAttachments(messageContent, maybeMessage); return attachmentMapperFactory.getAttachmentMapper(session) - .storeAttachmentsReactive(attachments, messageId); + .storeAttachmentsReactive(attachments.getAttachments(), messageId) + .doFinally(any -> attachments.dispose()); } - private List extractAttachments(Content contentIn, Optional maybeMessage) { + private MessageParser.ParsingResult extractAttachments(Content contentIn, Optional maybeMessage) { return maybeMessage.map(message -> { try { - return messageParser.retrieveAttachments(message); + return new MessageParser.ParsingResult(messageParser.retrieveAttachments(message), () -> { + + }); } catch (Exception e) { LOGGER.warn("Error while parsing mail's attachments: {}", e.getMessage(), e); - return ImmutableList.of(); + return MessageParser.ParsingResult.EMPTY; } }).orElseGet(() -> { try (InputStream inputStream = contentIn.getInputStream()) { return messageParser.retrieveAttachments(inputStream); } catch (Exception e) { LOGGER.warn("Error while parsing mail's attachments: {}", e.getMessage(), e); - return ImmutableList.of(); + return MessageParser.ParsingResult.EMPTY; } }); } 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 b1d2166904d..7297975c8cc 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 @@ -54,6 +54,27 @@ import com.google.common.io.ByteSource; public class MessageParser { + public static class ParsingResult { + public static final ParsingResult EMPTY = new ParsingResult(ImmutableList.of(), () -> { + + }); + + private final List attachments; + private final Runnable dispose; + + public ParsingResult(List attachments, Runnable dispose) { + this.attachments = attachments; + this.dispose = dispose; + } + + public List getAttachments() { + return attachments; + } + + public void dispose() { + + } + } private static final String TEXT_MEDIA_TYPE = "text"; private static final String CONTENT_TYPE = "Content-Type"; @@ -81,29 +102,25 @@ public MessageParser() { .unwrap(); } - public List retrieveAttachments(InputStream fullContent) throws IOException { + public ParsingResult retrieveAttachments(InputStream fullContent) throws IOException { DefaultMessageBuilder defaultMessageBuilder = new DefaultMessageBuilder(); defaultMessageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE); defaultMessageBuilder.setDecodeMonitor(DecodeMonitor.SILENT); Message message = defaultMessageBuilder.parseMessage(fullContent); - return retrieveAttachments(message); + return new ParsingResult(retrieveAttachments(message), message::dispose); } public List retrieveAttachments(Message message) throws IOException { Body body = message.getBody(); - try { - if (body instanceof Multipart) { - Multipart multipartBody = (Multipart) body; - return listAttachments(multipartBody, Context.fromSubType(multipartBody.getSubType())) - .collect(ImmutableList.toImmutableList()); - } else { - if (isAttachment(message, Context.BODY)) { - return ImmutableList.of(retrieveAttachment(message)); - } - return ImmutableList.of(); + if (body instanceof Multipart) { + Multipart multipartBody = (Multipart) body; + return listAttachments(multipartBody, Context.fromSubType(multipartBody.getSubType())) + .collect(ImmutableList.toImmutableList()); + } else { + if (isAttachment(message, Context.BODY)) { + return ImmutableList.of(retrieveAttachment(message)); } - } finally { - body.dispose(); + return ImmutableList.of(); } } diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearches.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearches.java index f840856f591..5cf798d63ba 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearches.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/MessageSearches.java @@ -54,8 +54,6 @@ import org.apache.james.mailbox.store.mail.model.MailboxMessage; import org.apache.james.mailbox.store.search.comparator.CombinedComparator; import org.apache.james.mime4j.MimeException; -import org.apache.james.mime4j.MimeIOException; -import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.dom.address.Address; import org.apache.james.mime4j.dom.address.AddressList; import org.apache.james.mime4j.dom.address.Group; @@ -67,9 +65,7 @@ import org.apache.james.mime4j.field.address.LenientAddressParser; import org.apache.james.mime4j.field.datetime.parser.DateTimeParser; import org.apache.james.mime4j.field.datetime.parser.ParseException; -import org.apache.james.mime4j.message.DefaultMessageBuilder; import org.apache.james.mime4j.message.HeaderImpl; -import org.apache.james.mime4j.stream.MimeConfig; import org.apache.james.mime4j.util.MimeUtil; import org.apache.james.mime4j.utils.search.MessageMatcher; import org.slf4j.Logger; @@ -270,20 +266,6 @@ private Stream toAttachmentContent(AttachmentMetadata attachment, Mailbo } } - private HeaderImpl buildTextHeaders(MailboxMessage message) throws IOException, MimeIOException { - DefaultMessageBuilder defaultMessageBuilder = new DefaultMessageBuilder(); - defaultMessageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE); - Message headersMessage = defaultMessageBuilder - .parseMessage(message.getHeaderContent()); - HeaderImpl headerImpl = new HeaderImpl(); - addFrom(headerImpl, headersMessage.getFrom()); - addAddressList(headerImpl, headersMessage.getTo()); - addAddressList(headerImpl, headersMessage.getCc()); - addAddressList(headerImpl, headersMessage.getBcc()); - headerImpl.addField(Fields.subject(headersMessage.getSubject())); - return headerImpl; - } - private void addFrom(HeaderImpl headerImpl, MailboxList from) { if (from != null) { headerImpl.addField(Fields.from(Lists.newArrayList(from.iterator()))); 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 e4af546c262..e75f073fb00 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 @@ -50,120 +50,123 @@ void setup() { @Test void getAttachmentsShouldBeEmptyWhenNoAttachment() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/noAttachment.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/noAttachment.eml")); - assertThat(attachments).isEmpty(); + assertThat(attachments.getAttachments()).isEmpty(); + attachments.dispose(); } @Test void getAttachmentsShouldIgnoreInlineWhenMixedMultipart() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/inlined-mixed.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/inlined-mixed.eml")); - assertThat(attachments).hasSize(2); + assertThat(attachments.getAttachments()).hasSize(2); } @Test void getAttachmentsShouldRetrieveAttachmentsWhenOneAttachment() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeTextInlined.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeTextInlined.eml")); - assertThat(attachments).hasSize(1); + assertThat(attachments.getAttachments()).hasSize(1); } @Test void getAttachmentsShouldRetrieveAttachmentNameWhenOne() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeTextInlined.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeTextInlined.eml")); - assertThat(attachments).hasSize(1); + assertThat(attachments.getAttachments()).hasSize(1); Optional expectedName = Optional.of("exploits_of_a_mom.png"); - assertThat(attachments.get(0).getName()).isEqualTo(expectedName); + assertThat(attachments.getAttachments().get(0).getName()).isEqualTo(expectedName); } @Test void getAttachmentsShouldRetrieveAttachmentNameWhenOneContainingNonASCIICharacters() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/messageWithNonASCIIFilenameAttachment.eml")); - assertThat(attachments).hasSize(1); - assertThat(attachments.get(0).getName()).contains("ديناصور.odt"); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/messageWithNonASCIIFilenameAttachment.eml")); + assertThat(attachments.getAttachments()).hasSize(1); + assertThat(attachments.getAttachments().get(0).getName()).contains("ديناصور.odt"); } @Test void getAttachmentsShouldRetrieveEmptyNameWhenNone() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentWithoutName.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentWithoutName.eml")); - assertThat(attachments).hasSize(1); - assertThat(attachments.get(0).getName()).isEqualTo(Optional.empty()); + assertThat(attachments.getAttachments()).hasSize(1); + assertThat(attachments.getAttachments().get(0).getName()).isEqualTo(Optional.empty()); } @Test void getAttachmentsShouldNotFailWhenContentTypeIsNotHere() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentWithoutContentType.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentWithoutContentType.eml")); - assertThat(attachments).hasSize(1); - assertThat(attachments.get(0).getContentType()) + assertThat(attachments.getAttachments()).hasSize(1); + assertThat(attachments.getAttachments().get(0).getContentType()) .isEqualTo(ContentType.of("application/octet-stream")); } @Test void getAttachmentsShouldNotFailWhenContentTypeIsEmpty() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentWithEmptyContentType.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentWithEmptyContentType.eml")); - assertThat(attachments).hasSize(1); - assertThat(attachments.get(0).getContentType()) + assertThat(attachments.getAttachments()).hasSize(1); + assertThat(attachments.getAttachments().get(0).getContentType()) .isEqualTo(ContentType.of("application/octet-stream")); } @Test void getAttachmentsShouldRetrieveTheAttachmentContentTypeWhenOneAttachment() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeTextInlined.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeTextInlined.eml")); - assertThat(attachments).hasSize(1); - assertThat(attachments.get(0).getContentType()) + assertThat(attachments.getAttachments()).hasSize(1); + assertThat(attachments.getAttachments().get(0).getContentType()) .isEqualTo(ContentType.of("application/octet-stream;\tname=\"exploits_of_a_mom.png\"")); } @Test void retrieveAttachmentsShouldNotFailOnMessagesWithManyHeaders() throws Exception { - List messageAttachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/mailWithManyHeaders.eml")); + List messageAttachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/mailWithManyHeaders.eml")) + .getAttachments(); assertThat(messageAttachments).hasSize(1); } @Test void retrieveAttachmentsShouldNotFailOnMessagesWithLongHeaders() throws Exception { - List messageAttachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/mailWithLongHeaders.eml")); + List messageAttachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/mailWithLongHeaders.eml")) + .getAttachments(); assertThat(messageAttachments).hasSize(1); } @Test void getAttachmentsShouldRetrieveTheAttachmentContentTypeWhenOneAttachmentWithSimpleContentType() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentWithSimpleContentType.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentWithSimpleContentType.eml")); - assertThat(attachments).hasSize(1); - assertThat(attachments.get(0).getContentType()) + assertThat(attachments.getAttachments()).hasSize(1); + assertThat(attachments.getAttachments().get(0).getContentType()) .isEqualTo(ContentType.of("application/octet-stream")); } @Test void getAttachmentsShouldReturnTheExpectedAttachment() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeTextInlined.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeTextInlined.eml")); - ParsedAttachment attachment = attachments.get(0); + ParsedAttachment attachment = attachments.getAttachments().get(0); assertThat(attachment.getContent().openStream()) .hasSameContentAs(ClassLoader.getSystemResourceAsStream("eml/gimp.png")); } @Test void getAttachmentsShouldRetrieveAttachmentsWhenTwo() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/twoAttachments.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/twoAttachments.eml")); - assertThat(attachments).hasSize(2); + assertThat(attachments.getAttachments()).hasSize(2); } @Test void retrieveAttachmentShouldUseFilenameAsNameWhenNoName() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/filenameOnly.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/filenameOnly.eml")); - assertThat(attachments).hasSize(1) + assertThat(attachments.getAttachments()).hasSize(1) .extracting(ParsedAttachment::getName) .allMatch(Optional::isPresent) .extracting(Optional::get) @@ -172,9 +175,9 @@ void retrieveAttachmentShouldUseFilenameAsNameWhenNoName() throws Exception { @Test void retrieveAttachmentShouldUseNameWhenBothNameAndFilename() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/filenameAndName.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/filenameAndName.eml")); - assertThat(attachments).hasSize(1) + assertThat(attachments.getAttachments()).hasSize(1) .extracting(ParsedAttachment::getName) .allMatch(Optional::isPresent) .extracting(Optional::get) @@ -183,104 +186,104 @@ void retrieveAttachmentShouldUseNameWhenBothNameAndFilename() throws Exception { @Test void getAttachmentsShouldRetrieveEmbeddedAttachmentsWhenSome() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/embeddedAttachmentWithInline.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/embeddedAttachmentWithInline.eml")); - assertThat(attachments).hasSize(1); + assertThat(attachments.getAttachments()).hasSize(1); } @Test void getAttachmentsShouldRetrieveInlineAttachmentsWhenSome() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/embeddedAttachmentWithAttachment.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/embeddedAttachmentWithAttachment.eml")); - assertThat(attachments).hasSize(1); + assertThat(attachments.getAttachments()).hasSize(1); } @Test void getAttachmentsShouldRetrieveTheAttachmentCIDWhenOne() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneInlinedAttachment.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneInlinedAttachment.eml")); - assertThat(attachments).hasSize(1); - assertThat(attachments.get(0).getCid()).isEqualTo(Optional.of(Cid.from("part1.37A15C92.A7C3488D@linagora.com"))); + assertThat(attachments.getAttachments()).hasSize(1); + assertThat(attachments.getAttachments().get(0).getCid()).isEqualTo(Optional.of(Cid.from("part1.37A15C92.A7C3488D@linagora.com"))); } @Test void getAttachmentsShouldSetInlineWhenOneInlinedAttachment() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneInlinedAttachment.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneInlinedAttachment.eml")); - assertThat(attachments).hasSize(1); - assertThat(attachments.get(0).isInline()).isTrue(); + assertThat(attachments.getAttachments()).hasSize(1); + assertThat(attachments.getAttachments().get(0).isInline()).isTrue(); } @Test void getAttachementsShouldRetrieveHtmlAttachementsWhenSome() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneHtmlAttachmentAndSomeTextInlined.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneHtmlAttachmentAndSomeTextInlined.eml")); - assertThat(attachments).hasSize(1); + assertThat(attachments.getAttachments()).hasSize(1); } @Test void getAttachementsShouldRetrieveAttachmentsWhenSomeAreInTheMultipartAlternative() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/invitationEmailFromOP.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/invitationEmailFromOP.eml")); - assertThat(attachments).hasSize(6); + assertThat(attachments.getAttachments()).hasSize(6); } @Test void getAttachmentsShouldNotConsiderUnknownContentDispositionAsAttachments() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/unknownDisposition.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/unknownDisposition.eml")); - assertThat(attachments).hasSize(0); + assertThat(attachments.getAttachments()).hasSize(0); } @Test void getAttachmentsShouldConsiderNoContentDispositionAsAttachmentsWhenCID() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/noContentDispositionWithCID.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/noContentDispositionWithCID.eml")); - assertThat(attachments).hasSize(1); + assertThat(attachments.getAttachments()).hasSize(1); } @Test void getAttachmentsShouldRetrieveAttachmentsWhenNoCidForInlined() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneInlinedAttachmentWithNoCid.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneInlinedAttachmentWithNoCid.eml")); - assertThat(attachments).hasSize(1); + assertThat(attachments.getAttachments()).hasSize(1); } @Test void getAttachmentsShouldRetrieveAttachmentsWhenEmptyCidForInlined() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneInlinedAttachmentWithEmptyCid.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneInlinedAttachmentWithEmptyCid.eml")); - assertThat(attachments).hasSize(1); + assertThat(attachments.getAttachments()).hasSize(1); } @Test void getAttachmentsShouldRetrieveAttachmentsWhenBlankCidForInlined() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneInlinedAttachmentWithBlankCid.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneInlinedAttachmentWithBlankCid.eml")); - assertThat(attachments).hasSize(1); + assertThat(attachments.getAttachments()).hasSize(1); } @Test void getAttachmentsShouldRetrieveAttachmentsWhenOneFailOnWrongContentDisposition() throws Exception { - List attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/multiAttachmentsWithOneWrongContentDisposition.eml")); + MessageParser.ParsingResult attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/multiAttachmentsWithOneWrongContentDisposition.eml")); - assertThat(attachments).hasSize(2); + assertThat(attachments.getAttachments()).hasSize(2); } @Test void getAttachmentsShouldRetrieveOneAttachmentWhenMessageWithAttachmentContentDisposition() throws Exception { - List attachments = testee.retrieveAttachments( + MessageParser.ParsingResult attachments = testee.retrieveAttachments( ClassLoader.getSystemResourceAsStream("eml/emailWithOnlyAttachment.eml")); - assertThat(attachments).hasSize(1); + assertThat(attachments.getAttachments()).hasSize(1); } @Test void getAttachmentsShouldRetrieveCharset() throws Exception { - List attachments = testee.retrieveAttachments( + MessageParser.ParsingResult attachments = testee.retrieveAttachments( ClassLoader.getSystemResourceAsStream("eml/charset.eml")); - assertThat(attachments).hasSize(1) + assertThat(attachments.getAttachments()).hasSize(1) .first() .satisfies(attachment -> assertThat(attachment.getContentType()) .isEqualTo(ContentType.of("text/calendar; charset=\"iso-8859-1\"; method=COUNTER"))); @@ -288,10 +291,10 @@ void getAttachmentsShouldRetrieveCharset() throws Exception { @Test void getAttachmentsShouldRetrieveAllPartsCharset() throws Exception { - List attachments = testee.retrieveAttachments( + MessageParser.ParsingResult attachments = testee.retrieveAttachments( ClassLoader.getSystemResourceAsStream("eml/charset2.eml")); - assertThat(attachments).hasSize(2) + assertThat(attachments.getAttachments()).hasSize(2) .extracting(ParsedAttachment::getContentType) .containsOnly(ContentType.of("text/calendar; charset=\"iso-8859-1\"; method=COUNTER"), ContentType.of("text/calendar; charset=\"iso-4444-5\"; method=COUNTER")); @@ -299,19 +302,19 @@ void getAttachmentsShouldRetrieveAllPartsCharset() throws Exception { @Test void getAttachmentsShouldNotConsiderTextCalendarAsAttachmentsByDefault() throws Exception { - List attachments = testee.retrieveAttachments( + MessageParser.ParsingResult attachments = testee.retrieveAttachments( ClassLoader.getSystemResourceAsStream("eml/calendar.eml")); - assertThat(attachments) + assertThat(attachments.getAttachments()) .isEmpty(); } @Test void getAttachmentsShouldConsiderTextCalendarAsAttachments() throws Exception { - List attachments = testee.retrieveAttachments( + MessageParser.ParsingResult attachments = testee.retrieveAttachments( ClassLoader.getSystemResourceAsStream("eml/calendar2.eml")); - assertThat(attachments) + assertThat(attachments.getAttachments()) .hasSize(1) .extracting(ParsedAttachment::getContentType) .containsExactly(ContentType.of("text/calendar; charset=\"utf-8\"; method=COUNTER")); @@ -319,10 +322,10 @@ void getAttachmentsShouldConsiderTextCalendarAsAttachments() throws Exception { @Test void gpgSignatureShouldBeConsideredAsAnAttachment() throws Exception { - List attachments = testee.retrieveAttachments( + MessageParser.ParsingResult attachments = testee.retrieveAttachments( ClassLoader.getSystemResourceAsStream("eml/signedMessage.eml")); - assertThat(attachments).hasSize(2) + assertThat(attachments.getAttachments()).hasSize(2) .extracting(ParsedAttachment::getName) .allMatch(Optional::isPresent) .extracting(Optional::get) @@ -348,8 +351,8 @@ void mdnReportShouldBeConsideredAsAttachmentWhenDispositionContentType() throws .asMime4JMessageBuilder() .build(); - List result = testee.retrieveAttachments(new ByteArrayInputStream(DefaultMessageWriter.asBytes(message))); - assertThat(result).hasSize(1) + MessageParser.ParsingResult result = testee.retrieveAttachments(new ByteArrayInputStream(DefaultMessageWriter.asBytes(message))); + assertThat(result.getAttachments()).hasSize(1) .allMatch(attachment -> attachment.getContentType().equals(ContentType.of("message/disposition-notification; charset=UTF-8"))); } } diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/Preview.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/Preview.java index 12e7ef34c9f..5474b6211c5 100644 --- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/Preview.java +++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/Preview.java @@ -63,7 +63,12 @@ public Preview fromMessageAsString(String messageAsString) throws IOException { } public Preview fromInputStream(InputStream inputStream) throws IOException { - return fromMime4JMessage(parse(inputStream)); + Message message = parse(inputStream); + try { + return fromMime4JMessage(message); + } finally { + message.dispose(); + } } public Preview fromMime4JMessage(Message mimeMessage) throws IOException { diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SendMDNProcessor.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SendMDNProcessor.java index 030a5775e8b..0ce526408b1 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SendMDNProcessor.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SendMDNProcessor.java @@ -145,6 +145,7 @@ private MessageId sendMdn(ValueWithId.MDNCreationEntry MDNCreationEntry, Mailbox Message originalMessage = retrieveOriginalMessage(mdn, mailboxSession); Message mdnAnswer = mdn.generateMDNMessage(originalMessage, mailboxSession); + originalMessage.dispose(); Flags seen = new Flags(Flags.Flag.SEEN); MetaDataWithContent metaDataWithContent = messageAppender.appendMessageInMailbox(mdnAnswer, diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastViewFactory.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastViewFactory.java index 00965af91e3..bacb694e527 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastViewFactory.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastViewFactory.java @@ -77,7 +77,9 @@ public Mono fromMessageResults(Collection messag Message mimeMessage = Helpers.parse(firstMessageResult.getFullContent().getInputStream()); - return instanciateFastView(messageResults, firstMessageResult, messageProjection, mailboxIds, mimeMessage); + MessageFastView result = instanciateFastView(messageResults, firstMessageResult, messageProjection, mailboxIds, mimeMessage); + mimeMessage.dispose(); + return result; }); } diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java index 7386434abff..f1e6e8e594e 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java @@ -107,7 +107,8 @@ private Mono fromMetaDataWithContent(MetaDataWithContent messag return retrieveProjection(messageContent, message.getMessageId(), () -> MessageFullView.hasAttachment(getAttachments(message.getAttachments()))) - .map(messageProjection -> instanciateMessageFullView(message, mimeMessage, htmlBody, textBody, messageProjection)); + .map(messageProjection -> instanciateMessageFullView(message, mimeMessage, htmlBody, textBody, messageProjection)) + .doFinally(any -> mimeMessage.dispose()); } private MessageFullView instanciateMessageFullView(MetaDataWithContent message, Message mimeMessage, Optional htmlBody, Optional textBody, MessageFastViewPrecomputedProperties messageProjection) { diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageHeaderViewFactory.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageHeaderViewFactory.java index 479b5113b1b..3a5b56e1d3f 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageHeaderViewFactory.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageHeaderViewFactory.java @@ -64,16 +64,18 @@ private Mono fromMessageResults(Collection mes return Mono.fromCallable(() -> messageResults.iterator().next()) - .flatMap(Throwing.function(firstMessageResult -> { + .map(Throwing.function(firstMessageResult -> { Collection mailboxIds = Helpers.getMailboxIds(messageResults); Message mimeMessage = Helpers.parse(firstMessageResult.getFullContent().getInputStream()); - return instanciateHeaderView(messageResults, firstMessageResult, mailboxIds, mimeMessage); + MessageHeaderView result = instanciateHeaderView(messageResults, firstMessageResult, mailboxIds, mimeMessage); + mimeMessage.dispose(); + return result; })); } - private Mono instanciateHeaderView(Collection messageResults, MessageResult firstMessageResult, + private MessageHeaderView instanciateHeaderView(Collection messageResults, MessageResult firstMessageResult, Collection mailboxIds, Message mimeMessage) { - return Mono.just(MessageHeaderView.messageHeaderBuilder() + return MessageHeaderView.messageHeaderBuilder() .id(firstMessageResult.getMessageId()) .mailboxIds(mailboxIds) .blobId(BlobId.of(firstMessageResult.getMessageId())) @@ -89,6 +91,6 @@ private Mono instanciateHeaderView(Collection .bcc(Emailer.fromAddressList(mimeMessage.getBcc())) .replyTo(Emailer.fromAddressList(mimeMessage.getReplyTo())) .date(Helpers.getDateFromHeaderOrInternalDateOtherwise(mimeMessage, firstMessageResult)) - .build()); + .build(); } } diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java index a8a6baf3992..a908a2ce1e2 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/ExtractMDNOriginalJMAPMessageId.java @@ -82,6 +82,7 @@ public void service(Mail mail) throws MessagingException { .map(OriginalMessageId::getOriginalMessageId) .flatMap(messageId -> findMessageIdForRFC822MessageId(messageId, recipient)) .ifPresent(messageId -> setJmapMessageIdAsHeader(mimeMessage, messageId)); + message.dispose(); } catch (Exception e) { throw new MessagingException("MDN can't be parse", e); } diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala index 6650dce10da..a10baa80548 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala @@ -450,6 +450,10 @@ private class EmailHeaderViewFactory @Inject()(zoneIdProvider: ZoneIdProvider) e blobId <- BlobId.of(messageId) keywords <- LENIENT_KEYWORDS_FACTORY.fromFlags(firstMessage.getFlags) } yield { + val headers = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage) + val specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage) + mime4JMessage.dispose() + EmailHeaderView( metadata = EmailMetadata( id = messageId, @@ -459,8 +463,8 @@ private class EmailHeaderViewFactory @Inject()(zoneIdProvider: ZoneIdProvider) e receivedAt = UTCDate.from(firstMessage.getInternalDate, zoneIdProvider.get()), size = sanitizeSize(firstMessage.getSize), keywords = keywords), - header = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage), - specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage)) + header = headers, + specificHeaders = specificHeaders) } } } @@ -485,6 +489,10 @@ private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, pre preview <- Try(previewFactory.fromMime4JMessage(mime4JMessage)) keywords <- LENIENT_KEYWORDS_FACTORY.fromFlags(firstMessage.getFlags) } yield { + val headers = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage) + val specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage) + mime4JMessage.dispose() + EmailFullView( metadata = EmailMetadata( id = messageId, @@ -494,7 +502,7 @@ private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, pre receivedAt = UTCDate.from(firstMessage.getInternalDate, zoneIdProvider.get()), keywords = keywords, size = sanitizeSize(firstMessage.getSize)), - header = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage), + header = headers, bodyMetadata = EmailBodyMetadata( hasAttachment = HasAttachment(!firstMessage.getLoadedAttachments.isEmpty), preview = preview), @@ -504,7 +512,7 @@ private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, pre htmlBody = bodyStructure.htmlBody, attachments = bodyStructure.attachments, bodyValues = bodyValues), - specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage)) + specificHeaders = specificHeaders) } } @@ -650,6 +658,9 @@ private class EmailFastViewReader @Inject()(messageIdManager: MessageIdManager, blobId <- BlobId.of(messageId) keywords <- LENIENT_KEYWORDS_FACTORY.fromFlags(firstMessage.getFlags) } yield { + val headers = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage) + val specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage) + mime4JMessage.dispose() EmailFastView( metadata = EmailMetadata( id = messageId, @@ -662,8 +673,8 @@ private class EmailFastViewReader @Inject()(messageIdManager: MessageIdManager, bodyMetadata = EmailBodyMetadata( hasAttachment = HasAttachment(fastView.hasAttachment), preview = fastView.getPreview), - header = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage), - specificHeaders = EmailHeaders.extractSpecificHeaders(request.properties)(zoneIdProvider.get(), mime4JMessage)) + header = headers, + specificHeaders = specificHeaders) } } } diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala index 78e0d98a295..26636d00668 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala @@ -78,7 +78,9 @@ object EmailBodyPart { defaultMessageBuilder.setDecodeMonitor(DecodeMonitor.SILENT) val mime4JMessage = Try(defaultMessageBuilder.parseMessage(message.getFullContent.getInputStream)) - mime4JMessage.flatMap(of(messageId, _)) + val result = mime4JMessage.flatMap(of(messageId, _)) + mime4JMessage.toOption.foreach(_.dispose()) + result } def of(messageId: MessageId, message: Message): Try[EmailBodyPart] = diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNParse.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNParse.scala index 98db1b84a26..db9a30b7653 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNParse.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MDNParse.scala @@ -73,7 +73,7 @@ case class MDNNotParsable(value: Set[UnparsedBlobId]) { object MDNParsed { def fromMDN(mdn: MDN, message: Message, originalMessageId: Option[MessageId]): MDNParsed = { val report = mdn.getReport - MDNParsed(forEmailId = originalMessageId.map(ForEmailIdField(_)), + val result = MDNParsed(forEmailId = originalMessageId.map(ForEmailIdField(_)), subject = Option(message.getSubject).map(SubjectField), textBody = Some(TextBodyField(mdn.getHumanReadableText)), reportingUA = report.getReportingUserAgentField @@ -89,12 +89,14 @@ object MDNParsed { includeOriginalMessage = IncludeOriginalMessageField(mdn.getOriginalMessage.isPresent), disposition = MDNDisposition.fromJava(report.getDispositionField), error = Option(report.getErrorFields.asScala - .map(error => ErrorField(error.getText.formatted())) - .toSeq) + .map(error => ErrorField(error.getText.formatted())) + .toSeq) .filter(error => error.nonEmpty), extensionFields = Option(report.getExtensionFields.asScala .map(extension => (extension.getFieldName, extension.getRawValue)) .toMap).filter(_.nonEmpty)) + message.dispose() + result } } diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailImportMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailImportMethod.scala index 41bfa69be16..a44b450cbba 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailImportMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailImportMethod.scala @@ -139,7 +139,10 @@ class EmailImportMethod @Inject() (val metricFactory: MetricFactory, validatedRequest <- emailImport.request.validate message <- asMessage(emailImport.blob) response <- append(validatedRequest, message, mailboxSession) - } yield response + } yield { + message.dispose() + response + } either.fold(e => ImportFailure(emailImport.id, e), response => ImportSuccess(emailImport.id, response)) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNSendMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNSendMethod.scala index 87b330091cb..75614b4035e 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNSendMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MDNSendMethod.scala @@ -150,6 +150,7 @@ class MDNSendMethod @Inject()(serializer: MDNSerializer, mailAndResponseAndId <- buildMailAndResponse(identity, session.getUser.asString(), requestEntry, messageRelated) _ <- Try(enqueue(mailAndResponseAndId._1)).toEither } yield { + messageRelated.dispose() MDNSendCreateSuccess( mdnCreationId = mdnSendCreationId, createResponse = mailAndResponseAndId._2, diff --git a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java index fc1a4f800da..5e309d4752f 100644 --- a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java +++ b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java @@ -154,6 +154,7 @@ private Mono correctProjection(MessageResult messageResult, Progress pro ZonedDateTime receivedAt = ZonedDateTime.ofInstant(messageResult.getInternalDate().toInstant(), ZoneOffset.UTC); Message mime4JMessage = parseMessage(messageResult); Date sentAtDate = Optional.ofNullable(mime4JMessage.getDate()).orElse(messageResult.getInternalDate()); + mime4JMessage.dispose(); ZonedDateTime sentAt = ZonedDateTime.ofInstant(sentAtDate.toInstant(), ZoneOffset.UTC); return new EmailQueryView.Entry(mailboxId, messageId, sentAt, receivedAt); diff --git a/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/dto/MailDto.java b/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/dto/MailDto.java index 5d48341c97b..d82fca4dbf0 100644 --- a/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/dto/MailDto.java +++ b/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/dto/MailDto.java @@ -103,8 +103,12 @@ private static Optional fetchMessage(Set additi try { MessageContentExtractor extractor = new MessageContentExtractor(); return Optional.ofNullable(mail.getMessage()) - .map(Throwing.function(MailDto::convertMessage).sneakyThrow()) - .map(Throwing.function(extractor::extract).sneakyThrow()); + .map(Throwing.function(message -> { + Message mimeMessage = MailDto.convertMessage(message); + MessageContent result = extractor.extract(mimeMessage); + mimeMessage.dispose(); + return result; + }).sneakyThrow()); } catch (MessagingException e) { if (additionalFields.contains(AdditionalField.TEXT_BODY)) { throw new InaccessibleFieldException(AdditionalField.TEXT_BODY, e); From 8087cffc70b73f670d99f613e422c946f2584191 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Sat, 12 Nov 2022 16:43:54 +0700 Subject: [PATCH 3/6] fixup! [MIME4J-0.8.8] Systematically dispose Mime4J messages --- .../james/jmap/draft/methods/MIMEMessageConverter.java | 5 ++++- .../org/apache/james/jmap/draft/methods/MessageAppender.java | 5 +---- .../apache/james/jmap/method/EmailSetCreatePerformer.scala | 3 ++- 3 files changed, 7 insertions(+), 6 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 5f499047ffc..be44fd574cc 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 @@ -111,7 +111,10 @@ public MIMEMessageConverter() { } public byte[] convert(ValueWithId.CreationMessageEntry creationMessageEntry, ImmutableList messageAttachments) { - return asBytes(convertToMime(creationMessageEntry, messageAttachments)); + Message message = convertToMime(creationMessageEntry, messageAttachments); + byte[] result = asBytes(message); + message.dispose(); + return result; } public byte[] asBytes(Message message) { diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MessageAppender.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MessageAppender.java index f114ae75254..b8e89035139 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MessageAppender.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MessageAppender.java @@ -50,8 +50,6 @@ import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mime4j.dom.Message; import org.apache.james.util.ReactorUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -62,8 +60,6 @@ import reactor.core.publisher.Mono; public class MessageAppender { - private static final Logger LOGGER = LoggerFactory.getLogger(MessageAppender.class); - private static class NewMessage { private final byte[] messageContent; private final Message message; @@ -116,6 +112,7 @@ public Mono appendMessageInMailboxes(CreationMessageEntry c .withParsedMessage(newMessage.message) .build(new ByteContent(newMessage.messageContent)), session)) + .doFinally(any -> newMessage.message.dispose()) .flatMap(appendResult -> { ComposedMessageId ids = appendResult.getId(); if (targetMailboxes.size() > 1) { diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala index a9f34ada3e9..76907f48242 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala @@ -104,7 +104,8 @@ class EmailSetCreatePerformer @Inject()(serializer: EmailSetSerializer, message => asAppendCommand(request, message) .fold(e => SMono.error(e), - appendCommand => append(clientId, appendCommand, mailboxSession, mailboxIds)))) + appendCommand => append(clientId, appendCommand, mailboxSession, mailboxIds)) + .doFinally(any => message.dispose()))) .onErrorResume(e => SMono.just[CreationResult](CreationFailure(clientId, e))) .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER) } From 7c3693f456aab55e0d7e52b2fae7b3652e0672e9 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Sat, 12 Nov 2022 16:45:17 +0700 Subject: [PATCH 4/6] fixup! fixup! [MIME4J-0.8.8] Systematically dispose Mime4J messages --- .../java/org/apache/james/mailbox/store/MessageStorer.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java index 574fcb7da9e..73ff95c7047 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java @@ -34,7 +34,6 @@ import org.apache.james.mailbox.model.MessageAttachmentMetadata; import org.apache.james.mailbox.model.MessageId; import org.apache.james.mailbox.model.MessageMetaData; -import org.apache.james.mailbox.model.ParsedAttachment; import org.apache.james.mailbox.model.ThreadId; import org.apache.james.mailbox.store.mail.AttachmentMapperFactory; import org.apache.james.mailbox.store.mail.MessageMapper; @@ -45,11 +44,8 @@ import org.apache.james.mailbox.store.mail.model.impl.MessageParser; import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; import org.apache.james.mailbox.store.mail.utils.MimeMessageHeadersUtil; -import org.apache.james.mime4j.codec.DecodeMonitor; import org.apache.james.mime4j.dom.Message; -import org.apache.james.mime4j.message.DefaultMessageBuilder; import org.apache.james.mime4j.message.HeaderImpl; -import org.apache.james.mime4j.stream.MimeConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 828b8ce9107143949675acdb757dc66998781b12 Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Mon, 14 Nov 2022 09:58:26 +0700 Subject: [PATCH 5/6] fixup! fixup! fixup! [MIME4J-0.8.8] Systematically dispose Mime4J messages --- .../draft/methods/MIMEMessageConverterTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 4c79709dbef..1329287e6df 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 @@ -46,6 +46,7 @@ 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; @@ -56,6 +57,7 @@ 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; @@ -708,8 +710,6 @@ void convertToMimeShouldAddAttachmentAndMultipartAlternativeWhenOneAttachementAn .textBody("Hello all!") .htmlBody("Hello all!") .build(); - TextBody expectedTextBody = new BasicBodyFactory().textBody("Hello all!".getBytes(), StandardCharsets.UTF_8); - TextBody expectedHtmlBody = new BasicBodyFactory().textBody("Hello all!".getBytes(), StandardCharsets.UTF_8); String expectedCID = "cid"; String expectedMimeType = "image/png"; @@ -741,16 +741,16 @@ void convertToMimeShouldAddAttachmentAndMultipartAlternativeWhenOneAttachementAn .hasSize(1) .extracting(entity -> (Multipart) entity.getBody()) .flatExtracting(Multipart::getBodyParts) - .satisfies(part -> { + .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); - 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)) + 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 all!".getBytes()); + }), Index.atIndex(0)) .satisfies(part -> { assertThat(part.getBody()).isEqualToComparingOnlyGivenFields(expectedAttachmentBody, "content"); assertThat(part.getDispositionType()).isEqualTo("inline"); From 376be2286fd08d79716452c3f96e936fb72db3ed Mon Sep 17 00:00:00 2001 From: Benoit Tellier Date: Fri, 2 Dec 2022 15:29:02 +0700 Subject: [PATCH 6/6] fixup! fixup! fixup! fixup! [MIME4J-0.8.8] Systematically dispose Mime4J messages --- .../james/mailbox/store/mail/model/impl/MessageParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7297975c8cc..9488adc5109 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 @@ -72,7 +72,7 @@ public List getAttachments() { } public void dispose() { - + dispose.run(); } }