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

Reply via email to