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 9465de671e2710bef5ad2416835340f5158ce45b Author: Benoit TELLIER <[email protected]> AuthorDate: Thu Nov 28 16:11:02 2024 +0100 [ENHANCEMENT] Add a PartHasContentType Rejecting contentTypes is a common policy for content filtering. James should offer a tool out of the box to do this. --- .../servers/partials/PartHasContentType.adoc | 12 +++ .../servers/partials/configure/matchers.adoc | 4 +- .../transport/matchers/PartHasContentType.java | 68 +++++++++++++ .../james/transport/matchers/utils/MimeWalk.java | 4 - .../transport/matchers/PartHasContentTypeTest.java | 105 +++++++++++++++++++++ 5 files changed, 188 insertions(+), 5 deletions(-) diff --git a/docs/modules/servers/partials/PartHasContentType.adoc b/docs/modules/servers/partials/PartHasContentType.adoc new file mode 100644 index 0000000000..9d01ad3206 --- /dev/null +++ b/docs/modules/servers/partials/PartHasContentType.adoc @@ -0,0 +1,12 @@ +=== PartHasContentType + +Checks if at least one attachment has a content type header which matches any +element of a comma-separated or space-separated list of content type masks. + +Syntax: *match="PartHasContentType=[-d] masks"* + +The match is case-insensitive. + +Multiple content types name masks can be specified, e.g.: 'application/json,image/png'. + +If '*-d*' is coded, some debug info will be logged. diff --git a/docs/modules/servers/partials/configure/matchers.adoc b/docs/modules/servers/partials/configure/matchers.adoc index 3b787e2a40..cd7d6ff074 100644 --- a/docs/modules/servers/partials/configure/matchers.adoc +++ b/docs/modules/servers/partials/configure/matchers.adoc @@ -57,6 +57,8 @@ include::partial$IsSMIMESigned.adoc[] include::partial$IsX509CertificateSubject.adoc[] +include::partial$PartHasContentType.adoc[] + include::partial$RecipientCountExceeds.adoc[] include::partial$RecipientDomainIs.adoc[] @@ -163,4 +165,4 @@ Here is the syntax to adopt in *mailetcontainer.xml*: <processor>relay</processor> </mailet> </processor> -.... \ No newline at end of file +.... diff --git a/mailet/standard/src/main/java/org/apache/james/transport/matchers/PartHasContentType.java b/mailet/standard/src/main/java/org/apache/james/transport/matchers/PartHasContentType.java new file mode 100755 index 0000000000..14a15ddea6 --- /dev/null +++ b/mailet/standard/src/main/java/org/apache/james/transport/matchers/PartHasContentType.java @@ -0,0 +1,68 @@ +/**************************************************************** + * 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; + +import java.util.Collection; + +import jakarta.mail.MessagingException; +import jakarta.mail.Part; + +import org.apache.james.core.MailAddress; +import org.apache.james.transport.matchers.utils.MimeWalk; +import org.apache.mailet.Mail; +import org.apache.mailet.base.GenericMatcher; + +import com.google.common.annotations.VisibleForTesting; + +/** + * <P>Checks if at least one attachment has a content type header which matches any + * element of a comma-separated or space-separated list of content type masks.</P> + * + * <P>Syntax: <CODE>match="PartHasContentType=[-d]<I>masks</I>"</CODE></P> + * + * <P>The match is case insensitive.</P> + * <P>Multiple content types name masks can be specified, e.g.: 'application/json,image/png'.</P> + * <P>If '<CODE>-d</CODE>' is coded, some debug info will be logged.</P> + */ +public class PartHasContentType extends GenericMatcher { + @VisibleForTesting + MimeWalk.Configuration configuration = MimeWalk.Configuration.DEFAULT; + + @Override + public void init() throws MessagingException { + configuration = MimeWalk.Configuration.parse(getCondition()); + } + + @Override + public Collection<MailAddress> match(Mail mail) throws MessagingException { + return new MimeWalk(configuration, this::partMatch) + .matchMail(mail); + } + + private boolean partMatch(Part part) throws MessagingException { + for (MimeWalk.Mask mask : configuration.masks()) { + if (part.getContentType().startsWith(mask.getMatchString())) { + return true; // matching file found + } + } + return false; + } +} + 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 index 9b2a9a776a..f0336b73a0 100644 --- 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 @@ -67,10 +67,6 @@ public class MimeWalk { } } - public boolean isSuffixMatch() { - return suffixMatch; - } - public String getMatchString() { return matchString; } diff --git a/mailet/standard/src/test/java/org/apache/james/transport/matchers/PartHasContentTypeTest.java b/mailet/standard/src/test/java/org/apache/james/transport/matchers/PartHasContentTypeTest.java new file mode 100644 index 0000000000..ad17b38a74 --- /dev/null +++ b/mailet/standard/src/test/java/org/apache/james/transport/matchers/PartHasContentTypeTest.java @@ -0,0 +1,105 @@ +/**************************************************************** + * 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; + +import static org.apache.mailet.base.MailAddressFixture.ANY_AT_JAMES; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collection; + +import org.apache.james.core.MailAddress; +import org.apache.james.core.builder.MimeMessageBuilder; +import org.apache.mailet.Mail; +import org.apache.mailet.base.test.FakeMail; +import org.apache.mailet.base.test.FakeMatcherConfig; +import org.junit.jupiter.api.Test; + +class PartHasContentTypeTest { + @Test + void shouldNotMatchWhenNoContentType() throws Exception { + PartHasContentType testee = new PartHasContentType(); + testee.init(FakeMatcherConfig.builder() + .matcherName("PartHasContentType") + .condition("image/png") + .build()); + + Mail mail = FakeMail.builder() + .name("mail") + .recipient(ANY_AT_JAMES) + .mimeMessage(MimeMessageBuilder.mimeMessageBuilder() + .setMultipartWithBodyParts( + MimeMessageBuilder.bodyPartBuilder() + .disposition("attachment") + .filename("xxx.zip"))) + .build(); + Collection<MailAddress> matched = testee.match(mail); + + assertThat(matched) + .isNull(); + } + + @Test + void shouldNotMatchWhenWrongContentType() throws Exception { + PartHasContentType testee = new PartHasContentType(); + testee.init(FakeMatcherConfig.builder() + .matcherName("PartHasContentType") + .condition("image/png") + .build()); + + Mail mail = FakeMail.builder() + .name("mail") + .recipient(ANY_AT_JAMES) + .mimeMessage(MimeMessageBuilder.mimeMessageBuilder() + .setMultipartWithBodyParts( + MimeMessageBuilder.bodyPartBuilder() + .type("text/plain") + .disposition("attachment") + .filename("xxx.zip"))) + .build(); + Collection<MailAddress> matched = testee.match(mail); + + assertThat(matched) + .isNull(); + } + + @Test + void shouldMatchWhenGoodContentType() throws Exception { + PartHasContentType testee = new PartHasContentType(); + testee.init(FakeMatcherConfig.builder() + .matcherName("PartHasContentType") + .condition("image/png") + .build()); + + Mail mail = FakeMail.builder() + .name("mail") + .recipient(ANY_AT_JAMES) + .mimeMessage(MimeMessageBuilder.mimeMessageBuilder() + .setMultipartWithBodyParts( + MimeMessageBuilder.bodyPartBuilder() + .type("image/png") + .disposition("attachment") + .filename("xxx.zip"))) + .build(); + Collection<MailAddress> matched = testee.match(mail); + + assertThat(matched) + .isNotEmpty(); + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
