This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push: new a63d257f2c JAMES-4012 Lenient MDN parsing for human readable part (#2057) a63d257f2c is described below commit a63d257f2c87fb20ed30bbdc55ce1e49564eecc0 Author: Benoit TELLIER <btell...@linagora.com> AuthorDate: Wed Feb 28 08:39:25 2024 +0100 JAMES-4012 Lenient MDN parsing for human readable part (#2057) --- mdn/src/main/java/org/apache/james/mdn/MDN.java | 65 +++++++++------- .../test/java/org/apache/james/mdn/MDNTest.java | 88 ++++++++++++++++++++-- 2 files changed, 117 insertions(+), 36 deletions(-) diff --git a/mdn/src/main/java/org/apache/james/mdn/MDN.java b/mdn/src/main/java/org/apache/james/mdn/MDN.java index 737ec1cb25..a884239628 100644 --- a/mdn/src/main/java/org/apache/james/mdn/MDN.java +++ b/mdn/src/main/java/org/apache/james/mdn/MDN.java @@ -87,7 +87,6 @@ public class MDN { public MDN build() { Preconditions.checkState(report != null); Preconditions.checkState(humanReadableText != null); - Preconditions.checkState(!humanReadableText.trim().isEmpty()); return new MDN(humanReadableText, report, message); } @@ -130,15 +129,17 @@ public class MDN { throw new MDNParseBodyPartInvalidException("MDN Message must contain at least two parts"); } try { - var humanReadableTextEntity = bodyParts.get(0); - return extractHumanReadableText(humanReadableTextEntity) - .flatMap(humanReadableText -> extractMDNReport(bodyParts.get(1)) - .map(report -> MDN.builder() + return extractMDNReport(bodyParts) + .map(Throwing.function(report -> { + String humanReadableText = extractHumanReadableText(bodyParts) + .orElse(""); + return MDN.builder() .humanReadableText(humanReadableText) .report(report) .message(extractOriginalMessage(bodyParts)) - .build())) - .orElseThrow(() -> new MDNParseException("MDN can not extract. Body part is invalid")); + .build(); + })) + .orElseThrow(() -> new MDNParseException("MDN can not extract. Report body part is invalid")); } catch (MDNParseException e) { throw e; } catch (Exception e) { @@ -156,29 +157,37 @@ public class MDN { .map(Message.class::cast); } - public static Optional<String> extractHumanReadableText(Entity humanReadableTextEntity) throws IOException { - if (humanReadableTextEntity.getMimeType().equals("text/plain")) { - try (InputStream inputStream = ((SingleBody) humanReadableTextEntity.getBody()).getInputStream()) { - return Optional.of(IOUtils.toString(inputStream, humanReadableTextEntity.getCharset())); - } - } - return Optional.empty(); + public static Optional<String> extractHumanReadableText(List<Entity> entities) throws IOException { + return entities.stream() + .filter(entity -> entity.getMimeType().equals("text/plain")) + .findAny() + .map(Throwing.<Entity, String>function(entity -> { + try (InputStream inputStream = ((SingleBody) entity.getBody()).getInputStream()) { + return IOUtils.toString(inputStream, entity.getCharset()); + } + }).sneakyThrow()); } - public static Optional<MDNReport> extractMDNReport(Entity reportEntity) { - if (!reportEntity.getMimeType().startsWith(DISPOSITION_CONTENT_TYPE)) { - return Optional.empty(); - } - try (InputStream inputStream = ((SingleBody) reportEntity.getBody()).getInputStream()) { - Try<MDNReport> result = MDNReportParser.parse(inputStream, reportEntity.getCharset()); - if (result.isSuccess()) { - return Optional.of(result.get()); - } else { - return Optional.empty(); - } - } catch (IOException e) { - return Optional.empty(); - } + public static Optional<MDNReport> extractMDNReport(List<Entity> entities) { + return entities.stream() + .filter(entity -> entity.getMimeType().startsWith(DISPOSITION_CONTENT_TYPE)) + .findAny() + .flatMap(entity -> { + try (InputStream inputStream = ((SingleBody) entity.getBody()).getInputStream()) { + Try<MDNReport> result = MDNReportParser.parse(inputStream, entity.getCharset()); + if (result.isSuccess()) { + return Optional.of(result.get()); + } else { + return Optional.empty(); + } + } catch (IOException e) { + return Optional.empty(); + } + }); + } + + public boolean isReport(Entity entity) { + return entity.getMimeType().startsWith(DISPOSITION_CONTENT_TYPE); } private final String humanReadableText; diff --git a/mdn/src/test/java/org/apache/james/mdn/MDNTest.java b/mdn/src/test/java/org/apache/james/mdn/MDNTest.java index d18887ecd5..ba58ab3c23 100644 --- a/mdn/src/test/java/org/apache/james/mdn/MDNTest.java +++ b/mdn/src/test/java/org/apache/james/mdn/MDNTest.java @@ -20,6 +20,7 @@ package org.apache.james.mdn; 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.ByteArrayOutputStream; @@ -149,21 +150,21 @@ class MDNTest { } @Test - void buildShouldThrowOnEmptyHumanReadableText() { - assertThatThrownBy(() -> MDN.builder() + void buildShouldNotThrowOnEmptyHumanReadableText() { + assertThatCode(() -> MDN.builder() .humanReadableText("") .report(MINIMAL_REPORT) .build()) - .isInstanceOf(IllegalStateException.class); + .doesNotThrowAnyException(); } @Test - void buildShouldThrowOnFoldingWhiteHumanReadableText() { - assertThatThrownBy(() -> MDN.builder() + void buildShouldNotThrowOnFoldingWhiteHumanReadableText() { + assertThatCode(() -> MDN.builder() .humanReadableText(" ") .report(MINIMAL_REPORT) .build()) - .isInstanceOf(IllegalStateException.class); + .doesNotThrowAnyException(); } @Test @@ -262,7 +263,7 @@ class MDNTest { .build(); assertThatThrownBy(() -> MDN.parse(message)) .isInstanceOf(MDN.MDNParseException.class) - .hasMessage("MDN can not extract. Body part is invalid"); + .hasMessage("MDN can not extract. Report body part is invalid"); } @Test @@ -281,7 +282,7 @@ class MDNTest { .build(); assertThatThrownBy(() -> MDN.parse(message)) .isInstanceOf(MDN.MDNParseException.class) - .hasMessage("MDN can not extract. Body part is invalid"); + .hasMessage("MDN can not extract. Report body part is invalid"); } @Test @@ -355,6 +356,77 @@ class MDNTest { assertThat(mdnActual).isEqualTo(mdnExpect); } + @Test + public void parseShouldSuccessWithValidMDNWithTextHTMLExplanation() throws Exception { + BodyPart mdnBodyPart = BodyPartBuilder + .create() + .setBody(SingleBodyBuilder.create() + .setText("Reporting-UA: UA_name; UA_product\r\n" + + "MDN-Gateway: rfc822; apache.org\r\n" + + "Original-Recipient: rfc822; originalRecipient\r\n" + + "Final-Recipient: rfc822; final_recipient\r\n" + + "Original-Message-ID: <origi...@message.id>\r\n" + + "Disposition: automatic-action/MDN-sent-automatically;processed/error,failed\r\n" + + "Error: Message1\r\n" + + "Error: Message2\r\n" + + "X-OPENPAAS-IP: 177.177.177.77\r\n" + + "X-OPENPAAS-PORT: 8000\r\n" + + "".replace(System.lineSeparator(), "\r\n").strip()) + .buildText()) + .setContentType("message/disposition-notification") + .build(); + + Message message = Message.Builder.of() + .setBody(MultipartBuilder.create("report") + .addBinaryPart("first".getBytes(StandardCharsets.UTF_8), "text/html") + .addBodyPart(mdnBodyPart) + .build()) + .build(); + MDN mdnActual = MDN.parse(message); + MDNReport mdnReportExpect = MDNReport.builder() + .reportingUserAgentField(ReportingUserAgent.builder() + .userAgentName("UA_name") + .userAgentProduct("UA_product") + .build()) + .gatewayField(Gateway.builder() + .nameType(AddressType.RFC_822) + .name(Text.fromRawText("apache.org")) + .build()) + .originalRecipientField(OriginalRecipient.builder() + .originalRecipient(Text.fromRawText("originalRecipient")) + .addressType(AddressType.RFC_822) + .build()) + .finalRecipientField(FinalRecipient.builder() + .finalRecipient(Text.fromRawText("final_recipient")) + .addressType(AddressType.RFC_822) + .build()) + .originalMessageIdField("<origi...@message.id>") + .dispositionField(Disposition.builder() + .actionMode(DispositionActionMode.Automatic) + .sendingMode(DispositionSendingMode.Automatic) + .type(DispositionType.Processed) + .addModifier(DispositionModifier.Error) + .addModifier(DispositionModifier.Failed) + .build()) + .addErrorField("Message1") + .addErrorField("Message2") + .withExtensionField(ExtensionField.builder() + .fieldName("X-OPENPAAS-IP") + .rawValue(" 177.177.177.77") + .build()) + .withExtensionField(ExtensionField.builder() + .fieldName("X-OPENPAAS-PORT") + .rawValue(" 8000") + .build()) + .build(); + + MDN mdnExpect = MDN.builder() + .report(mdnReportExpect) + .humanReadableText("") + .build(); + assertThat(mdnActual).isEqualTo(mdnExpect); + } + @Test public void parseShouldSuccessWithMDNHasMinimalProperties() throws Exception { Message message = Message.Builder.of() --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org