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 7c58485dc4 [FIX] Parsing for long content disposition filename (#2555)
7c58485dc4 is described below

commit 7c58485dc429484151100ad0d6e636a503c300f5
Author: Benoit TELLIER <btell...@linagora.com>
AuthorDate: Tue Dec 10 21:09:08 2024 +0100

    [FIX] Parsing for long content disposition filename (#2555)
---
 .../transport/matchers/AttachmentFileNameIs.java   | 96 +++++++++++++++++++---
 .../matchers/AttachmentFileNameIsTest.java         | 86 +++++++++++++++++++
 2 files changed, 169 insertions(+), 13 deletions(-)

diff --git 
a/mailet/standard/src/main/java/org/apache/james/transport/matchers/AttachmentFileNameIs.java
 
b/mailet/standard/src/main/java/org/apache/james/transport/matchers/AttachmentFileNameIs.java
index 7423a2135e..115b9fad84 100755
--- 
a/mailet/standard/src/main/java/org/apache/james/transport/matchers/AttachmentFileNameIs.java
+++ 
b/mailet/standard/src/main/java/org/apache/james/transport/matchers/AttachmentFileNameIs.java
@@ -21,7 +21,10 @@ package org.apache.james.transport.matchers;
 
 import java.io.IOException;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
@@ -31,12 +34,21 @@ import jakarta.mail.Part;
 import org.apache.james.core.MailAddress;
 import org.apache.james.mime4j.codec.DecodeMonitor;
 import org.apache.james.mime4j.codec.DecoderUtil;
+import org.apache.james.mime4j.dom.field.ContentDispositionField;
+import org.apache.james.mime4j.dom.field.ContentTypeField;
+import org.apache.james.mime4j.field.ContentDispositionFieldLenientImpl;
+import org.apache.james.mime4j.field.ContentTypeFieldLenientImpl;
+import org.apache.james.mime4j.stream.RawFieldParser;
+import org.apache.james.mime4j.util.ContentUtil;
+import org.apache.james.mime4j.util.MimeParameterMapping;
+import org.apache.james.mime4j.util.MimeUtil;
 import org.apache.james.transport.matchers.utils.MimeWalk;
 import org.apache.mailet.Mail;
 import org.apache.mailet.base.GenericMatcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.github.fge.lambdas.Throwing;
 import com.google.common.annotations.VisibleForTesting;
 
 /**
@@ -93,22 +105,80 @@ public class AttachmentFileNameIs extends GenericMatcher {
             .matchMail(mail);
     }
 
-    private boolean partMatch(Part part) throws MessagingException, 
IOException {
-        String fileName = part.getFileName();
-        if (fileName != null) {
-            fileName = cleanFileName(fileName);
-            // check the file name
-            if (matchFound(fileName)) {
-                if (configuration.isDebug()) {
-                    LOGGER.debug("matched {}", fileName);
+    private boolean partMatch(Part part) throws MessagingException {
+        Optional<ContentDispositionField> contentDispositionField = 
Optional.ofNullable(part.getHeader("Content-Disposition"))
+            .map(headers -> headers[0])
+            .map(value -> "Content-Disposition: " + value)
+            .map(ContentUtil::encode)
+            .map(Throwing.function(RawFieldParser.DEFAULT::parseField))
+            .map(raw -> ContentDispositionFieldLenientImpl.PARSER.parse(raw, 
DecodeMonitor.SILENT));
+        Optional<ContentTypeField> contentTypeField = 
Optional.ofNullable(part.getHeader("Content-Type"))
+            .map(headers -> headers[0])
+            .map(value -> "Content-Type: " + value)
+            .map(ContentUtil::encode)
+            .map(Throwing.function(RawFieldParser.DEFAULT::parseField))
+            .map(raw -> ContentTypeFieldLenientImpl.PARSER.parse(raw, 
DecodeMonitor.SILENT));
+
+        return extractFilename(contentTypeField, contentDispositionField)
+            .map(AttachmentFileNameIs::cleanFileName)
+            .map(Throwing.function(fileName -> {
+                if (matchFound(fileName)) {
+                    if (configuration.isDebug()) {
+                        LOGGER.debug("matched {}", fileName);
+                    }
+                    return true;
                 }
-                return true;
-            }
-            if (configuration.unzipIsRequested() && 
fileName.endsWith(ZIP_SUFFIX) && matchFoundInZip(part)) {
-                return true;
+                if (configuration.unzipIsRequested() && 
fileName.endsWith(ZIP_SUFFIX) && matchFoundInZip(part)) {
+                    return true;
+                }
+                return false;
+            }))
+            .orElse(false);
+    }
+
+    private Optional<String> extractFilename(Optional<ContentTypeField> 
contentTypeField, Optional<ContentDispositionField> contentDispositionField) {
+        Comparator<Map.Entry<String, String>> comparingName = (e1, e2) -> 
extractParameterName(e1.getKey()).compareTo(extractParameterName(e2.getKey()));
+        Comparator<Map.Entry<String, String>> comparingPartNumbers = (e1, e2) 
-> Integer.compare(extractPartNumber(e1.getKey()),
+            extractPartNumber(e2.getKey()));
+
+        return contentTypeField
+            .flatMap(field -> {
+                MimeParameterMapping mimeParameterMapping = new 
MimeParameterMapping();
+                field.getParameters().entrySet().stream()
+                    .sorted(comparingName.thenComparing(comparingPartNumbers))
+                    .forEach(e -> 
mimeParameterMapping.addParameter(e.getKey(), e.getValue()));
+                return Optional.ofNullable(mimeParameterMapping.get("name"));
+            })
+            .or(() -> 
contentDispositionField.map(ContentDispositionField::getFilename))
+            .map(MimeUtil::unscrambleHeaderValue);
+    }
+
+    String extractParameterName(String name) {
+        int separatorPosition = name.indexOf('*');
+        if (separatorPosition > 0) {
+            return name.substring(0, separatorPosition);
+        }
+        return name;
+    }
+
+    String removeTrailingSeparator(String name) {
+        int separatorPosition = name.indexOf('*');
+        if (separatorPosition > 0) {
+            return name.substring(0, separatorPosition);
+        }
+        return name;
+    }
+
+    int extractPartNumber(String name) {
+        int separatorPosition = name.indexOf('*');
+        if (separatorPosition > 0) {
+            try {
+                return 
Integer.parseInt(removeTrailingSeparator(name.substring(separatorPosition + 
1)));
+            } catch (NumberFormatException e) {
+                return 0;
             }
         }
-        return false;
+        return 0;
     }
 
     /**
diff --git 
a/mailet/standard/src/test/java/org/apache/james/transport/matchers/AttachmentFileNameIsTest.java
 
b/mailet/standard/src/test/java/org/apache/james/transport/matchers/AttachmentFileNameIsTest.java
index d0657c1dcf..3b50eeb7ac 100644
--- 
a/mailet/standard/src/test/java/org/apache/james/transport/matchers/AttachmentFileNameIsTest.java
+++ 
b/mailet/standard/src/test/java/org/apache/james/transport/matchers/AttachmentFileNameIsTest.java
@@ -22,8 +22,13 @@ package org.apache.james.transport.matchers;
 import static org.apache.mailet.base.MailAddressFixture.ANY_AT_JAMES;
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.nio.charset.StandardCharsets;
+
+import jakarta.mail.internet.MimeMessage;
+
 import org.apache.james.core.builder.MimeMessageBuilder;
 import org.apache.james.util.ClassLoaderUtils;
+import org.apache.james.util.MimeMessageUtil;
 import org.apache.mailet.Mail;
 import org.apache.mailet.base.test.FakeMail;
 import org.apache.mailet.base.test.FakeMatcherConfig;
@@ -151,6 +156,31 @@ class AttachmentFileNameIsTest {
             .containsOnly(ANY_AT_JAMES);
     }
 
+    @Test
+    void shouldMatchWhenLong() throws Exception {
+        Mail mail = FakeMail.builder()
+            .name("mail")
+            .recipient(ANY_AT_JAMES)
+            .mimeMessage(MimeMessageBuilder.mimeMessageBuilder()
+                .setText("abc", "text/plain")
+                .addHeader("Content-Disposition", "attachment;\n" +
+                    " 
filename*0=\"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooo\";\n" 
+
+                    " 
filename*1=\"oooooooooooooooooooooooooooooooooooooong_fiiiiiiiiiiiiiiiiii\";\n" 
+
+                    " 
filename*2=\"iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiileeeeeeeeeeeeeeeeeee\";\n" 
+
+                    " 
filename*3=\"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.txt\""))
+            .build();
+
+        AttachmentFileNameIs testee = new AttachmentFileNameIs();
+
+        testee.init(FakeMatcherConfig.builder()
+            .matcherName("AttachmentFileNameIs")
+            .condition("*.txt")
+            .build());
+
+        assertThat(testee.match(mail))
+            .containsOnly(ANY_AT_JAMES);
+    }
+
     @Test
     void shouldSupportWildcardPrefix() throws Exception {
         Mail mail = FakeMail.builder()
@@ -400,6 +430,62 @@ class AttachmentFileNameIsTest {
             .containsOnly(ANY_AT_JAMES);
     }
 
+    @Test
+    void shouldSupportMultilineFilenameWithTrailingStar() throws Exception {
+        /*Content-Type: text/plain;
+        name*0=fiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii;
+        name*1=iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiile;
+        name*2=.txt; charset=us-ascii
+        */
+
+        MimeMessage mimeMessage = 
MimeMessageUtil.mimeMessageFromBytes(("Content-Type: text/plain;\r\n" +
+            "        
name*0*=fiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii;\r\n" +
+            "        
name*1*=iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiile;\r\n" +
+            "        name*2*=.txt; 
charset=us-ascii\r\n\r\n").getBytes(StandardCharsets.US_ASCII));
+        Mail mail = FakeMail.builder()
+            .name("mail")
+            .recipient(ANY_AT_JAMES)
+            .mimeMessage(mimeMessage)
+            .build();
+
+        AttachmentFileNameIs testee = new AttachmentFileNameIs();
+
+        testee.init(FakeMatcherConfig.builder()
+            .matcherName("AttachmentFileNameIs")
+            
.condition("fiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiile.txt")
+            .build());
+
+        assertThat(testee.match(mail))
+            .containsOnly(ANY_AT_JAMES);
+    }
+
+    @Test
+    void shouldSupportMultilineFilename2() throws Exception {
+        /*Content-Type: text/plain;
+        name*0=fiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii;
+        name*1=iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiile;
+        name*2=.txt; charset=us-ascii
+        */
+
+        Mail mail = FakeMail.builder()
+            .name("mail")
+            .recipient(ANY_AT_JAMES)
+            .mimeMessage(MimeMessageBuilder.mimeMessageBuilder()
+                .setText("abc", "text/plain;\r\n name=\"" + 
"fiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiile".repeat(7)
 + ".txt\"")
+                .build())
+            .build();
+
+        AttachmentFileNameIs testee = new AttachmentFileNameIs();
+
+        testee.init(FakeMatcherConfig.builder()
+            .matcherName("AttachmentFileNameIs")
+            .condition("*.txt")
+            .build());
+
+        assertThat(testee.match(mail))
+            .containsOnly(ANY_AT_JAMES);
+    }
+
     @Test
     void shouldSupportTrimming() throws Exception {
         Mail mail = FakeMail.builder()


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org
For additional commands, e-mail: notifications-h...@james.apache.org

Reply via email to