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
commit e389ab7d4aff45be34f804dbc084e3f2360e0366 Author: Benoit TELLIER <[email protected]> AuthorDate: Thu Nov 28 15:53:56 2024 +0100 [ENHANCEMENT] Extract MimeWalk in a separate class --- .../transport/matchers/AttachmentFileNameIs.java | 164 +---------------- .../james/transport/matchers/utils/MimeWalk.java | 199 +++++++++++++++++++++ .../matchers/AttachmentFileNameIsTest.java | 24 --- .../transport/matchers/utils/MimeWalkTest.java | 51 ++++++ 4 files changed, 255 insertions(+), 183 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 d5703299fb..7423a2135e 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 @@ -16,29 +16,22 @@ * specific language governing permissions and limitations * * under the License. * ****************************************************************/ - - package org.apache.james.transport.matchers; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; import java.util.Collection; import java.util.Locale; -import java.util.StringTokenizer; -import java.util.function.Predicate; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import jakarta.mail.MessagingException; -import jakarta.mail.Multipart; import jakarta.mail.Part; -import jakarta.mail.internet.MimeMessage; import org.apache.james.core.MailAddress; import org.apache.james.mime4j.codec.DecodeMonitor; import org.apache.james.mime4j.codec.DecoderUtil; +import org.apache.james.transport.matchers.utils.MimeWalk; import org.apache.mailet.Mail; import org.apache.mailet.base.GenericMatcher; import org.slf4j.Logger; @@ -63,131 +56,6 @@ import com.google.common.annotations.VisibleForTesting; public class AttachmentFileNameIs extends GenericMatcher { private static final Logger LOGGER = LoggerFactory.getLogger(AttachmentFileNameIs.class); - record MimeWalkConfiguration(Mask[] masks, boolean isDebug, boolean unzipIsRequested) { - public static MimeWalkConfiguration DEFAULT = new MimeWalkConfiguration(null, false, false); - - public static MimeWalkConfiguration parse(String condition) { - StringTokenizer st = new StringTokenizer(condition, ", ", false); - ArrayList<Mask> theMasks = new ArrayList<>(20); - boolean unzipIsRequested = false; - boolean isDebug = false; - while (st.hasMoreTokens()) { - String fileName = st.nextToken(); - - // check possible parameters at the beginning of the condition - if (theMasks.isEmpty() && fileName.equalsIgnoreCase(UNZIP_REQUEST_PARAMETER)) { - unzipIsRequested = true; - LOGGER.info("zip file analysis requested"); - continue; - } - if (theMasks.isEmpty() && fileName.equalsIgnoreCase(DEBUG_REQUEST_PARAMETER)) { - isDebug = true; - LOGGER.info("debug requested"); - continue; - } - Mask mask = new Mask(); - if (fileName.startsWith("*")) { - mask.suffixMatch = true; - mask.matchString = fileName.substring(1); - } else { - mask.suffixMatch = false; - mask.matchString = fileName; - } - mask.matchString = cleanFileName(mask.matchString); - theMasks.add(mask); - } - return new MimeWalkConfiguration(theMasks.toArray(Mask[]::new), isDebug, unzipIsRequested); - } - } - - public static class MimeWalk { - @FunctionalInterface - interface PartMatch { - boolean partMatch(Part part) throws MessagingException, IOException; - } - - private final MimeWalkConfiguration configuration; - private final PartMatch partMatch; - - public MimeWalk(MimeWalkConfiguration configuration, PartMatch partMatch) { - this.configuration = configuration; - this.partMatch = partMatch; - } - - private Collection<MailAddress> matchMail(Mail mail) throws MessagingException { - try { - MimeMessage message = mail.getMessage(); - - if (matchFound(message)) { - return mail.getRecipients(); - } else { - return null; - } - - } catch (Exception e) { - if (configuration.isDebug()) { - LOGGER.debug("Malformed message", e); - } - throw new MessagingException("Malformed message", e); - } - } - - /** - * Checks if <I>part</I> matches with at least one of the <CODE>masks</CODE>. - * - * @param part - */ - protected boolean matchFound(Part part) throws Exception { - - /* - * if there is an attachment and no inline text, - * the content type can be anything - */ - - if (part.getContentType() == null || - part.getContentType().startsWith("multipart/alternative")) { - return false; - } - - Object content; - - try { - content = part.getContent(); - } catch (UnsupportedEncodingException uee) { - // in this case it is not an attachment, so ignore it - return false; - } - - Exception anException = null; - - if (content instanceof Multipart) { - Multipart multipart = (Multipart) content; - for (int i = 0; i < multipart.getCount(); i++) { - try { - Part bodyPart = multipart.getBodyPart(i); - if (matchFound(bodyPart)) { - return true; // matching file found - } - } catch (MessagingException e) { - anException = e; - } // remember any messaging exception and process next bodypart - } - } else { - if (partMatch.partMatch(part)) { - return true; - } - } - - // if no matching attachment was found and at least one exception was catched rethrow it up - if (anException != null) { - throw anException; - } - - return false; - } - } - - /** * Transforms <I>fileName<I> in a trimmed lowercase string usable for matching agains the masks. * Also decode encoded words. @@ -196,45 +64,23 @@ public class AttachmentFileNameIs extends GenericMatcher { return DecoderUtil.decodeEncodedWords(fileName.toLowerCase(Locale.US).trim(), DecodeMonitor.SILENT); } - /** Unzip request parameter. */ - protected static final String UNZIP_REQUEST_PARAMETER = "-z"; - - /** Debug request parameter. */ - protected static final String DEBUG_REQUEST_PARAMETER = "-d"; - /** Match string for zip files. */ protected static final String ZIP_SUFFIX = ".zip"; /** * represents a single parsed file name mask. */ - private static class Mask { - /** true if the mask starts with a wildcard asterisk */ - public boolean suffixMatch; - - /** file name mask not including the wildcard asterisk */ - public String matchString; - - public boolean match(String fileName) { - //XXX: file names in mail may contain directory - theoretically - if (this.suffixMatch) { - return fileName.endsWith(this.matchString); - } else { - return fileName.equals(this.matchString); - } - } - } /** * Controls certain log messages. */ @VisibleForTesting - MimeWalkConfiguration configuration = MimeWalkConfiguration.DEFAULT; + MimeWalk.Configuration configuration = MimeWalk.Configuration.DEFAULT; @Override public void init() throws MessagingException { - configuration = MimeWalkConfiguration.parse(getCondition()); + configuration = MimeWalk.Configuration.parse(getCondition()); } /** @@ -271,7 +117,7 @@ public class AttachmentFileNameIs extends GenericMatcher { * @param fileName */ protected boolean matchFound(String fileName) { - for (Mask mask1 : configuration.masks) { + for (MimeWalk.Mask mask1 : configuration.masks()) { if (mask1.match(fileName)) { return true; // matching file found } @@ -293,7 +139,7 @@ public class AttachmentFileNameIs extends GenericMatcher { } String fileName = zipEntry.getName(); if (matchFound(fileName)) { - if (configuration.unzipIsRequested) { + if (configuration.unzipIsRequested()) { LOGGER.debug("matched {}({})", part.getFileName(), fileName); } return true; diff --git a/mailet/standard/src/main/java/org/apache/james/transport/matchers/utils/MimeWalk.java b/mailet/standard/src/main/java/org/apache/james/transport/matchers/utils/MimeWalk.java new file mode 100644 index 0000000000..9b2a9a776a --- /dev/null +++ b/mailet/standard/src/main/java/org/apache/james/transport/matchers/utils/MimeWalk.java @@ -0,0 +1,199 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.transport.matchers.utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Locale; +import java.util.StringTokenizer; + +import jakarta.mail.MessagingException; +import jakarta.mail.Multipart; +import jakarta.mail.Part; +import jakarta.mail.internet.MimeMessage; + +import org.apache.james.core.MailAddress; +import org.apache.james.mime4j.codec.DecodeMonitor; +import org.apache.james.mime4j.codec.DecoderUtil; +import org.apache.mailet.Mail; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MimeWalk { + private static final Logger LOGGER = LoggerFactory.getLogger(MimeWalk.class); + /** Unzip request parameter. */ + protected static final String UNZIP_REQUEST_PARAMETER = "-z"; + + /** Debug request parameter. */ + protected static final String DEBUG_REQUEST_PARAMETER = "-d"; + + @FunctionalInterface + public interface PartMatch { + boolean partMatch(Part part) throws MessagingException, IOException; + } + + public static class Mask { + /** true if the mask starts with a wildcard asterisk */ + private boolean suffixMatch; + + /** file name mask not including the wildcard asterisk */ + private String matchString; + + public boolean match(String fileName) { + //XXX: file names in mail may contain directory - theoretically + if (this.suffixMatch) { + return fileName.endsWith(this.matchString); + } else { + return fileName.equals(this.matchString); + } + } + + public boolean isSuffixMatch() { + return suffixMatch; + } + + public String getMatchString() { + return matchString; + } + } + + public record Configuration(Mask[] masks, boolean isDebug, boolean unzipIsRequested) { + public static Configuration DEFAULT = new Configuration(null, false, false); + + public static Configuration parse(String condition) { + StringTokenizer st = new StringTokenizer(condition, ", ", false); + ArrayList<Mask> theMasks = new ArrayList<>(20); + boolean unzipIsRequested = false; + boolean isDebug = false; + while (st.hasMoreTokens()) { + String fileName = st.nextToken(); + + // check possible parameters at the beginning of the condition + if (theMasks.isEmpty() && fileName.equalsIgnoreCase(UNZIP_REQUEST_PARAMETER)) { + unzipIsRequested = true; + LOGGER.info("zip file analysis requested"); + continue; + } + if (theMasks.isEmpty() && fileName.equalsIgnoreCase(DEBUG_REQUEST_PARAMETER)) { + isDebug = true; + LOGGER.info("debug requested"); + continue; + } + Mask mask = new Mask(); + if (fileName.startsWith("*")) { + mask.suffixMatch = true; + mask.matchString = fileName.substring(1); + } else { + mask.suffixMatch = false; + mask.matchString = fileName; + } + mask.matchString = cleanString(mask.matchString); + theMasks.add(mask); + } + return new Configuration(theMasks.toArray(Mask[]::new), isDebug, unzipIsRequested); + } + } + + public static String cleanString(String fileName) { + return DecoderUtil.decodeEncodedWords(fileName.toLowerCase(Locale.US).trim(), DecodeMonitor.SILENT); + } + + private final Configuration configuration; + private final PartMatch partMatch; + + public MimeWalk(Configuration configuration, PartMatch partMatch) { + this.configuration = configuration; + this.partMatch = partMatch; + } + + public Collection<MailAddress> matchMail(Mail mail) throws MessagingException { + try { + MimeMessage message = mail.getMessage(); + + if (matchFound(message)) { + return mail.getRecipients(); + } else { + return null; + } + + } catch (Exception e) { + if (configuration.isDebug()) { + LOGGER.debug("Malformed message", e); + } + throw new MessagingException("Malformed message", e); + } + } + + /** + * Checks if <I>part</I> matches with at least one of the <CODE>masks</CODE>. + * + * @param part + */ + protected boolean matchFound(Part part) throws Exception { + + /* + * if there is an attachment and no inline text, + * the content type can be anything + */ + + if (part.getContentType() == null || + part.getContentType().startsWith("multipart/alternative")) { + return false; + } + + Object content; + + try { + content = part.getContent(); + } catch (UnsupportedEncodingException uee) { + // in this case it is not an attachment, so ignore it + return false; + } + + Exception anException = null; + + if (content instanceof Multipart) { + Multipart multipart = (Multipart) content; + for (int i = 0; i < multipart.getCount(); i++) { + try { + Part bodyPart = multipart.getBodyPart(i); + if (matchFound(bodyPart)) { + return true; // matching file found + } + } catch (MessagingException e) { + anException = e; + } // remember any messaging exception and process next bodypart + } + } else { + if (partMatch.partMatch(part)) { + return true; + } + } + + // if no matching attachment was found and at least one exception was catched rethrow it up + if (anException != null) { + throw anException; + } + + return false; + } +} 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 5642dbadcd..d0657c1dcf 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 @@ -27,7 +27,6 @@ import org.apache.james.util.ClassLoaderUtils; import org.apache.mailet.Mail; import org.apache.mailet.base.test.FakeMail; import org.apache.mailet.base.test.FakeMatcherConfig; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class AttachmentFileNameIsTest { @@ -535,27 +534,4 @@ class AttachmentFileNameIsTest { assertThat(testee.match(mail)) .isNull(); } - - @Nested - class MimeWalkConfigurationTest { - @Test - void shouldSupportDebugMode() { - assertThat(AttachmentFileNameIs.MimeWalkConfiguration.parse("-d test.txt").isDebug()).isTrue(); - } - - @Test - void debugModeShouldBeFalseByDefault() { - assertThat(AttachmentFileNameIs.MimeWalkConfiguration.parse("test.txt").isDebug()).isFalse(); - } - - @Test - void shouldSupportUnzipMode() { - assertThat(AttachmentFileNameIs.MimeWalkConfiguration.parse("-z test.txt").unzipIsRequested()).isTrue(); - } - - @Test - void unzipModeShouldBeFalseByDefault() { - assertThat(AttachmentFileNameIs.MimeWalkConfiguration.parse("test.txt").unzipIsRequested()).isFalse(); - } - } } \ No newline at end of file diff --git a/mailet/standard/src/test/java/org/apache/james/transport/matchers/utils/MimeWalkTest.java b/mailet/standard/src/test/java/org/apache/james/transport/matchers/utils/MimeWalkTest.java new file mode 100644 index 0000000000..4860783692 --- /dev/null +++ b/mailet/standard/src/test/java/org/apache/james/transport/matchers/utils/MimeWalkTest.java @@ -0,0 +1,51 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.transport.matchers.utils; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class MimeWalkTest { + + @Nested + class ConfigurationTest { + @Test + void shouldSupportDebugMode() { + assertThat(MimeWalk.Configuration.parse("-d test.txt").isDebug()).isTrue(); + } + + @Test + void debugModeShouldBeFalseByDefault() { + assertThat(MimeWalk.Configuration.parse("test.txt").isDebug()).isFalse(); + } + + @Test + void shouldSupportUnzipMode() { + assertThat(MimeWalk.Configuration.parse("-z test.txt").unzipIsRequested()).isTrue(); + } + + @Test + void unzipModeShouldBeFalseByDefault() { + assertThat(MimeWalk.Configuration.parse("test.txt").unzipIsRequested()).isFalse(); + } + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
