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