Repository: james-project Updated Branches: refs/heads/master 1968a528f -> 072feaca6
JAMES-2529 Mailet Filter implementation Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/897cb08c Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/897cb08c Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/897cb08c Branch: refs/heads/master Commit: 897cb08c54341fd8c3021f0c366197494f39c718 Parents: 1968a52 Author: duc <[email protected]> Authored: Thu Aug 23 23:09:09 2018 +0700 Committer: Antoine Duprat <[email protected]> Committed: Thu Aug 30 15:07:00 2018 +0200 ---------------------------------------------------------------------- javax-mail-extension/pom.xml | 4 + .../org/apache/james/javax/AddressHelper.java | 37 + pom.xml | 5 + .../transport/matchers/dlp/DlpDomainRules.java | 17 +- server/protocols/jmap/pom.xml | 30 +- .../james/jmap/mailet/filter/ActionApplier.java | 101 +++ .../james/jmap/mailet/filter/JMAPFiltering.java | 85 +++ .../james/jmap/mailet/filter/MailMatcher.java | 225 ++++++ .../james/jmap/mailet/filter/RuleMatcher.java | 44 ++ .../mailet/filter/JMAPFilteringExtension.java | 151 ++++ .../mailet/filter/JMAPFilteringFixture.java | 59 ++ .../jmap/mailet/filter/JMAPFilteringTest.java | 731 +++++++++++++++++++ 12 files changed, 1471 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/javax-mail-extension/pom.xml ---------------------------------------------------------------------- diff --git a/javax-mail-extension/pom.xml b/javax-mail-extension/pom.xml index 2e2fa74..d23866d 100644 --- a/javax-mail-extension/pom.xml +++ b/javax-mail-extension/pom.xml @@ -40,5 +40,9 @@ <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>apache-mime4j-core</artifactId> + </dependency> </dependencies> </project> http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/javax-mail-extension/src/main/java/org/apache/james/javax/AddressHelper.java ---------------------------------------------------------------------- diff --git a/javax-mail-extension/src/main/java/org/apache/james/javax/AddressHelper.java b/javax-mail-extension/src/main/java/org/apache/james/javax/AddressHelper.java new file mode 100644 index 0000000..fb0084c --- /dev/null +++ b/javax-mail-extension/src/main/java/org/apache/james/javax/AddressHelper.java @@ -0,0 +1,37 @@ +/**************************************************************** + * 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.javax; + +import java.util.Arrays; +import java.util.stream.Stream; + +import javax.mail.Address; + +import org.apache.james.mime4j.util.MimeUtil; + +public class AddressHelper { + public static Stream<String> asStringStream(Address[] addresses) { + return Arrays.stream(addresses).map(AddressHelper::asString); + } + + private static String asString(Address address) { + return MimeUtil.unscrambleHeaderValue(address.toString()); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 4368273..9c9028a 100644 --- a/pom.xml +++ b/pom.xml @@ -2401,6 +2401,11 @@ </dependency> <dependency> <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-engine</artifactId> + <version>${junit.plateform.version}</version> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> <artifactId>junit-platform-launcher</artifactId> <version>${junit.plateform.version}</version> </dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/dlp/DlpDomainRules.java ---------------------------------------------------------------------- diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/dlp/DlpDomainRules.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/dlp/DlpDomainRules.java index f44a60e..d3f0eef 100644 --- a/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/dlp/DlpDomainRules.java +++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/dlp/DlpDomainRules.java @@ -19,14 +19,14 @@ package org.apache.james.transport.matchers.dlp; +import static org.apache.james.javax.AddressHelper.asStringStream; + import java.io.IOException; -import java.util.Arrays; import java.util.Objects; import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Stream; -import javax.mail.Address; import javax.mail.BodyPart; import javax.mail.Message; import javax.mail.MessagingException; @@ -36,8 +36,8 @@ import javax.mail.internet.MimeMessage; import org.apache.james.core.MailAddress; import org.apache.james.dlp.api.DLPConfigurationItem; import org.apache.james.dlp.api.DLPConfigurationItem.Targets; +import org.apache.james.javax.AddressHelper; import org.apache.james.javax.MultipartUtil; -import org.apache.james.mime4j.util.MimeUtil; import org.apache.james.util.OptionalUtils; import org.apache.mailet.Mail; @@ -71,15 +71,6 @@ public class DlpDomainRules { interface MatcherFunction extends ThrowingPredicate<Mail> { } - - private static Stream<String> asStringStream(Address[] addresses) { - return Arrays.stream(addresses).map(Rule::asString); - } - - private static String asString(Address address) { - return MimeUtil.unscrambleHeaderValue(address.toString()); - } - private static class ContentMatcher implements Rule.MatcherFunction { private final Pattern pattern; @@ -153,7 +144,7 @@ public class DlpDomainRules { private Stream<String> listHeaderRecipients(Mail mail) throws MessagingException { return Optional.ofNullable(mail.getMessage()) .flatMap(Throwing.function(m -> Optional.ofNullable(m.getAllRecipients()))) - .map(Rule::asStringStream) + .map(AddressHelper::asStringStream) .orElse(Stream.of()); } http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/server/protocols/jmap/pom.xml ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/pom.xml b/server/protocols/jmap/pom.xml index 8818166..93495fb 100644 --- a/server/protocols/jmap/pom.xml +++ b/server/protocols/jmap/pom.xml @@ -78,6 +78,11 @@ </dependency> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>event-sourcing-event-store-memory</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>james-mdn</artifactId> </dependency> <dependency> @@ -212,11 +217,6 @@ <scope>provided</scope> </dependency> <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> - </dependency> - <dependency> <groupId>net.javacrumbs.json-unit</groupId> <artifactId>json-unit-assertj</artifactId> <scope>test</scope> @@ -264,6 +264,26 @@ <version>1.2</version> </dependency> <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.vintage</groupId> + <artifactId>junit-vintage-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.jgrapht</groupId> <artifactId>jgrapht-core</artifactId> <version>1.2.0</version> http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java new file mode 100644 index 0000000..fdb55ac --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java @@ -0,0 +1,101 @@ +/**************************************************************** + * 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.jmap.mailet.filter; + +import javax.inject.Inject; + +import org.apache.james.core.User; +import org.apache.james.jmap.api.filtering.Rule; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.mailet.Mail; + +import com.github.fge.lambdas.Throwing; +import com.google.common.annotations.VisibleForTesting; + +public class ActionApplier { + static final String DELIVERY_PATH_PREFIX = "DeliveryPath_"; + + @VisibleForTesting + static class Factory { + private final MailboxManager mailboxManager; + private final MailboxId.Factory mailboxIdFactory; + + @Inject + Factory(MailboxManager mailboxManager, MailboxId.Factory mailboxIdFactory) { + this.mailboxManager = mailboxManager; + this.mailboxIdFactory = mailboxIdFactory; + } + + public RequireUser forMail(Mail mail) { + return new RequireUser(mail); + } + + public class RequireUser { + private final Mail mail; + + RequireUser(Mail mail) { + this.mail = mail; + } + + public ActionApplier forUser(User user) { + return new ActionApplier(mailboxManager, mailboxIdFactory, mail, user); + } + } + } + + private final MailboxManager mailboxManager; + private final MailboxId.Factory mailboxIdFactory; + private final Mail mail; + private final User user; + + @VisibleForTesting + public static Factory factory(MailboxManager mailboxManager, MailboxId.Factory mailboxIdFactory) { + return new Factory(mailboxManager, mailboxIdFactory); + } + + private ActionApplier(MailboxManager mailboxManager, MailboxId.Factory mailboxIdFactory, Mail mail, User user) { + this.mailboxManager = mailboxManager; + this.mailboxIdFactory = mailboxIdFactory; + this.mail = mail; + this.user = user; + } + + public void apply(Rule.Action action) { + action.getAppendInMailboxes() + .getMailboxIds() + .stream() + .findFirst() + .map(mailboxIdFactory::fromString) + .ifPresent(Throwing.consumer(this::addStorageDirective)); + } + + private void addStorageDirective(MailboxId mailboxId) throws MailboxException { + MailboxSession mailboxSession = mailboxManager.createSystemSession(user.asString()); + MessageManager messageManager = mailboxManager.getMailbox(mailboxId, mailboxSession); + + String mailboxName = messageManager.getMailboxPath().getName(); + String attributeNameForUser = DELIVERY_PATH_PREFIX + user.asString(); + mail.setAttribute(attributeNameForUser, mailboxName); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/JMAPFiltering.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/JMAPFiltering.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/JMAPFiltering.java new file mode 100644 index 0000000..388fabe --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/JMAPFiltering.java @@ -0,0 +1,85 @@ +/**************************************************************** + * 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.jmap.mailet.filter; + +import java.util.List; +import java.util.Optional; + +import javax.inject.Inject; + +import org.apache.james.core.MailAddress; +import org.apache.james.core.User; +import org.apache.james.jmap.api.filtering.FilteringManagement; +import org.apache.james.jmap.api.filtering.Rule; +import org.apache.james.user.api.UsersRepository; +import org.apache.james.user.api.UsersRepositoryException; +import org.apache.mailet.Mail; +import org.apache.mailet.base.GenericMailet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JMAPFiltering extends GenericMailet { + + private final Logger logger = LoggerFactory.getLogger(JMAPFiltering.class); + + private final FilteringManagement filteringManagement; + private final UsersRepository usersRepository; + private final ActionApplier.Factory actionApplierFactory; + + @Inject + public JMAPFiltering(FilteringManagement filteringManagement, + UsersRepository usersRepository, ActionApplier.Factory actionApplierFactory) { + + this.filteringManagement = filteringManagement; + this.usersRepository = usersRepository; + this.actionApplierFactory = actionApplierFactory; + } + + @Override + public void service(Mail mail) { + mail.getRecipients() + .forEach(recipient -> filteringForRecipient(mail, recipient)); + } + + private void filteringForRecipient(Mail mail, MailAddress recipient) { + Optional<User> maybeUser = retrieveUser(recipient); + maybeUser + .ifPresent(user -> findFirstApplicableRule(user, mail)); + } + + private void findFirstApplicableRule(User user, Mail mail) { + List<Rule> filteringRules = filteringManagement.listRulesForUser(user); + RuleMatcher ruleMatcher = new RuleMatcher(filteringRules); + Optional<Rule> maybeMatchingRule = ruleMatcher.findApplicableRule(mail); + + maybeMatchingRule.ifPresent(rule -> actionApplierFactory.forMail(mail) + .forUser(user) + .apply(rule.getAction())); + } + + private Optional<User> retrieveUser(MailAddress recipient) { + try { + return Optional.ofNullable(User.fromUsername(usersRepository.getUser(recipient))); + } catch (UsersRepositoryException e) { + logger.error("cannot retrieve user " + recipient.asString(), e); + return Optional.empty(); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java new file mode 100644 index 0000000..c9ea72b --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/MailMatcher.java @@ -0,0 +1,225 @@ +/**************************************************************** + * 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.jmap.mailet.filter; + +import static org.apache.james.jmap.api.filtering.Rule.Condition; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import javax.mail.Address; +import javax.mail.Message; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.james.javax.AddressHelper; +import org.apache.james.jmap.api.filtering.Rule; +import org.apache.james.jmap.api.filtering.Rule.Condition.Field; +import org.apache.mailet.Mail; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.fge.lambdas.functions.ThrowingFunction; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; + +public interface MailMatcher { + + interface HeaderExtractor extends ThrowingFunction<Mail, Stream<String>> {} + + class HeaderMatcher implements MailMatcher { + + private static final Logger LOGGER = LoggerFactory.getLogger(HeaderMatcher.class); + + private final ContentMatcher contentMatcher; + private final String ruleValue; + private final HeaderExtractor headerExtractor; + + private HeaderMatcher(ContentMatcher contentMatcher, String ruleValue, + HeaderExtractor headerExtractor) { + Preconditions.checkNotNull(contentMatcher); + Preconditions.checkNotNull(headerExtractor); + + this.contentMatcher = contentMatcher; + this.ruleValue = ruleValue; + this.headerExtractor = headerExtractor; + } + + @Override + public boolean match(Mail mail) { + try { + final Stream<String> headerLines = headerExtractor.apply(mail); + return contentMatcher.match(headerLines, ruleValue); + } catch (Exception e) { + LOGGER.error("error while extracting mail header", e); + return false; + } + } + } + + interface ContentMatcher { + + class AddressHeader { + private static final Logger LOGGER = LoggerFactory.getLogger(AddressHeader.class); + + private final Optional<String> personal; + private final Optional<String> address; + private final String fullAddress; + + private AddressHeader(String fullAddress) { + this.fullAddress = fullAddress; + Optional<InternetAddress> internetAddress = parseFullAddress(); + this.personal = internetAddress.map(InternetAddress::getPersonal); + this.address = internetAddress.map(InternetAddress::getAddress); + } + + private Optional<InternetAddress> parseFullAddress() { + try { + return Optional.of(new InternetAddress(fullAddress)); + } catch (AddressException e) { + LOGGER.error("error while parsing full address {}", fullAddress, e); + return Optional.empty(); + } + } + + public Optional<String> getPersonal() { + return personal; + } + + public Optional<String> getAddress() { + return address; + } + + public String getFullAddress() { + return fullAddress; + } + } + + ContentMatcher STRING_CONTAINS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.contains(content, valueToMatch)); + ContentMatcher STRING_NOT_CONTAINS_MATCHER = negate(STRING_CONTAINS_MATCHER); + ContentMatcher STRING_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents.anyMatch(content -> StringUtils.equals(content, valueToMatch)); + ContentMatcher STRING_NOT_EXACTLY_EQUALS_MATCHER = negate(STRING_EXACTLY_EQUALS_MATCHER); + + Map<Rule.Condition.Comparator, ContentMatcher> CONTENT_STRING_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder() + .put(Condition.Comparator.CONTAINS, STRING_CONTAINS_MATCHER) + .put(Condition.Comparator.NOT_CONTAINS, STRING_NOT_CONTAINS_MATCHER) + .put(Condition.Comparator.EXACTLY_EQUALS, STRING_EXACTLY_EQUALS_MATCHER) + .put(Condition.Comparator.NOT_EXACTLY_EQUALS, STRING_NOT_EXACTLY_EQUALS_MATCHER) + .build(); + + ContentMatcher ADDRESS_CONTAINS_MATCHER = (contents, valueToMatch) -> contents + .map(ContentMatcher::asAddressHeader) + .anyMatch(addressHeader -> StringUtils.containsIgnoreCase(addressHeader.getFullAddress(), valueToMatch)); + + ContentMatcher ADDRESS_NOT_CONTAINS_MATCHER = negate(ADDRESS_CONTAINS_MATCHER); + ContentMatcher ADDRESS_EXACTLY_EQUALS_MATCHER = (contents, valueToMatch) -> contents + .map(ContentMatcher::asAddressHeader) + .anyMatch(addressHeader -> + StringUtils.equalsIgnoreCase(addressHeader.getFullAddress(), valueToMatch) + || StringUtils.equalsIgnoreCase(addressHeader.getAddress().orElse(null), valueToMatch) + || StringUtils.equalsIgnoreCase(addressHeader.getPersonal().orElse(null), valueToMatch)); + + ContentMatcher ADDRESS_NOT_EXACTLY_EQUALS_MATCHER = negate(ADDRESS_EXACTLY_EQUALS_MATCHER); + + Map<Rule.Condition.Comparator, ContentMatcher> HEADER_ADDRESS_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Comparator, ContentMatcher>builder() + .put(Condition.Comparator.CONTAINS, ADDRESS_CONTAINS_MATCHER) + .put(Condition.Comparator.NOT_CONTAINS, ADDRESS_NOT_CONTAINS_MATCHER) + .put(Condition.Comparator.EXACTLY_EQUALS, ADDRESS_EXACTLY_EQUALS_MATCHER) + .put(Condition.Comparator.NOT_EXACTLY_EQUALS, ADDRESS_NOT_EXACTLY_EQUALS_MATCHER) + .build(); + + Map<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>> CONTENT_MATCHER_REGISTRY = ImmutableMap.<Rule.Condition.Field, Map<Rule.Condition.Comparator, ContentMatcher>>builder() + .put(Condition.Field.SUBJECT, CONTENT_STRING_MATCHER_REGISTRY) + .put(Condition.Field.TO, HEADER_ADDRESS_MATCHER_REGISTRY) + .put(Condition.Field.CC, HEADER_ADDRESS_MATCHER_REGISTRY) + .put(Condition.Field.RECIPIENT, HEADER_ADDRESS_MATCHER_REGISTRY) + .put(Condition.Field.FROM, HEADER_ADDRESS_MATCHER_REGISTRY) + .build(); + + static ContentMatcher negate(ContentMatcher contentMatcher) { + return (Stream<String> contents, String valueToMatch) -> + !contentMatcher.match(contents, valueToMatch); + } + + static Optional<ContentMatcher> asContentMatcher(Condition.Field field, Condition.Comparator comparator) { + return Optional + .ofNullable(CONTENT_MATCHER_REGISTRY.get(field)) + .map(matcherRegistry -> matcherRegistry.get(comparator)); + } + + static AddressHeader asAddressHeader(String addressAsString) { + return new AddressHeader(addressAsString); + } + + boolean match(Stream<String> contents, String valueToMatch); + } + + HeaderExtractor SUBJECT_EXTRACTOR = mail -> + Optional.ofNullable(mail.getMessage().getSubject()) + .map(Stream::of) + .orElse(Stream.empty()); + HeaderExtractor RECIPIENT_EXTRACTOR = mail -> addressExtractor( + ArrayUtils.addAll( + mail.getMessage().getRecipients(Message.RecipientType.TO), + mail.getMessage().getRecipients(Message.RecipientType.CC))); + + HeaderExtractor FROM_EXTRACTOR = mail -> addressExtractor(mail.getMessage().getFrom()); + HeaderExtractor CC_EXTRACTOR = recipientExtractor(Message.RecipientType.CC); + HeaderExtractor TO_EXTRACTOR = recipientExtractor(Message.RecipientType.TO); + + Map<Field, HeaderExtractor> HEADER_EXTRACTOR_REGISTRY = ImmutableMap.<Field, HeaderExtractor>builder() + .put(Field.SUBJECT, SUBJECT_EXTRACTOR) + .put(Field.RECIPIENT, RECIPIENT_EXTRACTOR) + .put(Field.FROM, FROM_EXTRACTOR) + .put(Field.CC, CC_EXTRACTOR) + .put(Field.TO, TO_EXTRACTOR) + .build(); + + static MailMatcher from(Rule rule) { + Condition ruleCondition = rule.getCondition(); + Optional<ContentMatcher> maybeContentMatcher = ContentMatcher.asContentMatcher(ruleCondition.getField(), ruleCondition.getComparator()); + Optional<HeaderExtractor> maybeHeaderExtractor = getHeaderExtractor(ruleCondition.getField()); + + return new HeaderMatcher( + maybeContentMatcher.orElseThrow(() -> new RuntimeException("No content matcher associated with field " + ruleCondition.getField())), + rule.getCondition().getValue(), + maybeHeaderExtractor.orElseThrow(() -> new RuntimeException("No content matcher associated with comparator " + ruleCondition.getComparator()))); + } + + static HeaderExtractor recipientExtractor(Message.RecipientType type) { + return mail -> addressExtractor(mail.getMessage().getRecipients(type)); + } + + static Stream<String> addressExtractor(Address[] addresses) { + return Optional.ofNullable(addresses) + .map(AddressHelper::asStringStream) + .orElse(Stream.empty()); + } + + static Optional<HeaderExtractor> getHeaderExtractor(Field field) { + return Optional + .ofNullable(HEADER_EXTRACTOR_REGISTRY.get(field)); + } + + boolean match(Mail mail); +} http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java new file mode 100644 index 0000000..abc1da1 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/filter/RuleMatcher.java @@ -0,0 +1,44 @@ +/**************************************************************** + * 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.jmap.mailet.filter; + +import java.util.List; +import java.util.Optional; + +import org.apache.james.jmap.api.filtering.Rule; +import org.apache.mailet.Mail; + +import com.google.common.base.Preconditions; + +class RuleMatcher { + private final List<Rule> filteringRules; + + RuleMatcher(List<Rule> filteringRules) { + Preconditions.checkNotNull(filteringRules); + + this.filteringRules = filteringRules; + } + + Optional<Rule> findApplicableRule(Mail mail) { + return filteringRules.stream() + .filter(rule -> MailMatcher.from(rule).match(mail)) + .findFirst(); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringExtension.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringExtension.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringExtension.java new file mode 100644 index 0000000..05dc317 --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringExtension.java @@ -0,0 +1,151 @@ +/**************************************************************** + * 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.jmap.mailet.filter; + +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.RECIPIENT_1; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.RECIPIENT_1_MAILBOX_1; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.RECIPIENT_1_USERNAME; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_1_ADDRESS; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.mail.MessagingException; + +import org.apache.james.core.User; +import org.apache.james.core.builder.MimeMessageBuilder; +import org.apache.james.eventsourcing.eventstore.memory.InMemoryEventStore; +import org.apache.james.jmap.api.filtering.FilteringManagement; +import org.apache.james.jmap.api.filtering.Rule; +import org.apache.james.jmap.api.filtering.impl.EventSourcingFilteringManagement; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; +import org.apache.james.mailbox.inmemory.InMemoryId; +import org.apache.james.mailbox.inmemory.InMemoryMailboxManager; +import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.user.memory.MemoryUsersRepository; +import org.apache.mailet.base.test.FakeMail; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +import com.google.common.collect.ImmutableList; + +public class JMAPFilteringExtension implements BeforeEachCallback, ParameterResolver { + + class JMAPFilteringTestSystem { + + private final JMAPFiltering jmapFiltering; + private final FilteringManagement filteringManagement; + private final InMemoryMailboxManager mailboxManager; + private final MailboxId recipient1Mailbox; + + JMAPFilteringTestSystem(JMAPFiltering jmapFiltering, FilteringManagement filteringManagement, + InMemoryMailboxManager mailboxManager) { + this.jmapFiltering = jmapFiltering; + this.filteringManagement = filteringManagement; + this.mailboxManager = mailboxManager; + try { + this.recipient1Mailbox = createMailbox(mailboxManager, RECIPIENT_1_USERNAME, RECIPIENT_1_MAILBOX_1); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public JMAPFiltering getJmapFiltering() { + return jmapFiltering; + } + + public FilteringManagement getFilteringManagement() { + return filteringManagement; + } + + public InMemoryMailboxManager getMailboxManager() { + return mailboxManager; + } + + public MailboxId getRecipient1MailboxId() { + return recipient1Mailbox; + } + + public MailboxId createMailbox(InMemoryMailboxManager mailboxManager, String username, String mailboxName) throws Exception { + MailboxSession mailboxSession = mailboxManager.createSystemSession(username); + return mailboxManager + .createMailbox(MailboxPath.forUser(username, mailboxName), mailboxSession) + .orElseThrow(() -> new RuntimeException("Missing mailboxId when creating mailbox")); + } + + public void defineRulesForRecipient1(Rule.Condition... conditions) { + defineRulesForRecipient1(Arrays.asList(conditions)); + } + + public void defineRulesForRecipient1(List<Rule.Condition> conditions) { + AtomicInteger counter = new AtomicInteger(); + ImmutableList<Rule> rules = conditions + .stream() + .map(condition -> Rule.builder() + .id(Rule.Id.of(String.valueOf(counter.incrementAndGet()))) + .name(String.valueOf(counter.incrementAndGet())) + .condition(condition) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(testSystem.getRecipient1MailboxId().serialize()))) + .build()) + .collect(ImmutableList.toImmutableList()); + + testSystem.getFilteringManagement().defineRulesForUser(User.fromUsername(RECIPIENT_1_USERNAME), rules); + } + + public FakeMail asMail(MimeMessageBuilder mimeMessageBuilder) throws MessagingException { + return FakeMail.builder() + .sender(USER_1_ADDRESS) + .recipients(RECIPIENT_1) + .mimeMessage(mimeMessageBuilder) + .build(); + } + } + + private JMAPFilteringTestSystem testSystem; + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + FilteringManagement filteringManagement = new EventSourcingFilteringManagement(new InMemoryEventStore()); + MemoryUsersRepository usersRepository = MemoryUsersRepository.withoutVirtualHosting(); + InMemoryMailboxManager mailboxManager = new InMemoryIntegrationResources().createMailboxManager(new SimpleGroupMembershipResolver()); + ActionApplier.Factory actionApplierFactory = ActionApplier.factory(mailboxManager, new InMemoryId.Factory()); + + JMAPFiltering jmapFiltering = new JMAPFiltering(filteringManagement, usersRepository, actionApplierFactory); + + testSystem = new JMAPFilteringTestSystem(jmapFiltering, filteringManagement, mailboxManager); + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return (parameterContext.getParameter().getType() == JMAPFilteringTestSystem.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return testSystem; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringFixture.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringFixture.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringFixture.java new file mode 100644 index 0000000..1b4792b --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringFixture.java @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.jmap.mailet.filter; + +public interface JMAPFilteringFixture { + String GA_BOU_ZO_MEU_FULL_ADDRESS = "GA BOU ZO MEU <[email protected]>"; + String BOU = "BOU"; + + String USER_1_FULL_ADDRESS = "user1 <[email protected]>"; + String USER_1_ADDRESS = "[email protected]"; + String USER_1_USERNAME = "user1"; + + String USER_2_FULL_ADDRESS = "user2 <[email protected]>"; + String USER_2_ADDRESS = "[email protected]"; + String USER_2_USERNAME = "user2"; + + String USER_1_AND_UNFOLDED_USER_FULL_ADDRESS = "user2 <[email protected]>, \r\nunfolded\r\n_user\r\n <[email protected]>"; + + String USER_3_FULL_ADDRESS = "user3 <[email protected]>"; + String USER_3_ADDRESS = "[email protected]"; + String USER_3_USERNAME = "user3"; + + String USER_4_FULL_ADDRESS = "user4 <[email protected]>"; + + String SCRAMBLED_SUBJECT = "this is the subject =?UTF-8?B?RnLDqWTDqXJpYyBNQVJUSU4=?= of the mail"; + String UNSCRAMBLED_SUBJECT = "this is the subject Frédéric MARTIN of the mail"; + String SHOULD_NOT_MATCH = "should not match"; + + String RECIPIENT_1 = "[email protected]"; + String RECIPIENT_1_USERNAME = "recipient1"; + String RECIPIENT_1_MAILBOX_1 = "recipient1_maibox1"; + + String FRED_MARTIN_FULLNAME = "Frédéric MARTIN"; + String FRED_MARTIN_FULL_SCRAMBLED_ADDRESS = "=?UTF-8?B?RnLDqWTDqXJpYyBNQVJUSU4=?= <[email protected]>"; + + String UNFOLDED_USERNAME = "unfolded_user"; + + String EMPTY = ""; + + String TO_HEADER = "to"; + String CC_HEADER = "cc"; +} http://git-wip-us.apache.org/repos/asf/james-project/blob/897cb08c/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java new file mode 100644 index 0000000..0d697f8 --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/filter/JMAPFilteringTest.java @@ -0,0 +1,731 @@ +/**************************************************************** + * 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.jmap.mailet.filter; + +import static org.apache.james.core.builder.MimeMessageBuilder.mimeMessageBuilder; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.CONTAINS; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.EXACTLY_EQUALS; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.NOT_CONTAINS; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.NOT_EXACTLY_EQUALS; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.CC; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.FROM; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.RECIPIENT; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.SUBJECT; +import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.TO; +import static org.apache.james.jmap.mailet.filter.ActionApplier.DELIVERY_PATH_PREFIX; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.BOU; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.CC_HEADER; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.EMPTY; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.FRED_MARTIN_FULLNAME; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.FRED_MARTIN_FULL_SCRAMBLED_ADDRESS; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.GA_BOU_ZO_MEU_FULL_ADDRESS; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.RECIPIENT_1; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.RECIPIENT_1_MAILBOX_1; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.RECIPIENT_1_USERNAME; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.SCRAMBLED_SUBJECT; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.SHOULD_NOT_MATCH; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.TO_HEADER; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.UNFOLDED_USERNAME; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.UNSCRAMBLED_SUBJECT; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_1_ADDRESS; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_1_AND_UNFOLDED_USER_FULL_ADDRESS; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_1_FULL_ADDRESS; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_1_USERNAME; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_2_ADDRESS; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_2_FULL_ADDRESS; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_3_ADDRESS; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_3_FULL_ADDRESS; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_3_USERNAME; +import static org.apache.james.jmap.mailet.filter.JMAPFilteringFixture.USER_4_FULL_ADDRESS; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Locale; +import java.util.Optional; +import java.util.stream.Stream; + +import org.apache.james.core.User; +import org.apache.james.core.builder.MimeMessageBuilder; +import org.apache.james.jmap.api.filtering.Rule; +import org.apache.james.jmap.mailet.filter.JMAPFilteringExtension.JMAPFilteringTestSystem; +import org.apache.james.mailbox.inmemory.InMemoryMailboxManager; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.util.StreamUtils; +import org.apache.mailet.base.test.FakeMail; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import com.github.fge.lambdas.Throwing; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +@ExtendWith(JMAPFilteringExtension.class) +class JMAPFilteringTest { + + static class FilteringArgumentBuilder { + private Optional<String> description; + private Optional<Rule.Condition.Field> field; + private MimeMessageBuilder mimeMessageBuilder; + private Optional<String> valueToMatch; + + private FilteringArgumentBuilder() { + this.description = Optional.empty(); + this.field = Optional.empty(); + mimeMessageBuilder = MimeMessageBuilder.mimeMessageBuilder(); + this.valueToMatch = Optional.empty(); + } + + public FilteringArgumentBuilder description(String description) { + this.description = Optional.ofNullable(description); + return this; + } + + public FilteringArgumentBuilder field(Rule.Condition.Field field) { + this.field = Optional.ofNullable(field); + return this; + } + + public FilteringArgumentBuilder from(String from) { + Optional.ofNullable(from).ifPresent(Throwing.consumer(mimeMessageBuilder::addFrom)); + return this; + } + + public FilteringArgumentBuilder noHeader() { + return this; + } + + public FilteringArgumentBuilder toRecipient(String toRecipient) { + Optional.ofNullable(toRecipient).ifPresent(Throwing.consumer(mimeMessageBuilder::addToRecipient)); + return this; + } + + public FilteringArgumentBuilder ccRecipient(String ccRecipient) { + Optional.ofNullable(ccRecipient).ifPresent(Throwing.consumer(mimeMessageBuilder::addCcRecipient)); + return this; + } + + public FilteringArgumentBuilder bccRecipient(String bccRecipient) { + Optional.ofNullable(bccRecipient).ifPresent(Throwing.consumer(mimeMessageBuilder::addBccRecipient)); + return this; + } + + public FilteringArgumentBuilder header(String headerName, String headerValue) { + mimeMessageBuilder.addHeader(headerName, headerValue); + return this; + } + + public FilteringArgumentBuilder headerForField(String headerValue) { + Preconditions.checkState(field.isPresent(), "field should be set first"); + + mimeMessageBuilder.addHeader(field.get().asString(), headerValue); + return this; + } + + public FilteringArgumentBuilder subject(String subject) { + mimeMessageBuilder.setSubject(subject); + return this; + } + + public FilteringArgumentBuilder valueToMatch(String valueToMatch) { + this.valueToMatch = Optional.ofNullable(valueToMatch); + return this; + } + + public FilteringArgumentBuilder scrambledSubjectToMatch(String valueToMatch) { + return description("normal content") + .field(SUBJECT) + .subject(SCRAMBLED_SUBJECT) + .valueToMatch(valueToMatch); + } + + public FilteringArgumentBuilder scrambledSubjectShouldNotMatchCaseSensitive() { + return description("normal content (case sensitive)") + .field(SUBJECT) + .subject(SCRAMBLED_SUBJECT) + .valueToMatch(SCRAMBLED_SUBJECT.toUpperCase(Locale.FRENCH)); + } + + public FilteringArgumentBuilder unscrambledSubjectToMatch(String valueToMatch) { + return description("unscrambled content") + .field(SUBJECT) + .subject(UNSCRAMBLED_SUBJECT) + .valueToMatch(valueToMatch); + } + + public FilteringArgumentBuilder unscrambledSubjectShouldNotMatchCaseSensitive() { + return description("unscrambled content (case sensitive)") + .field(SUBJECT) + .subject(UNSCRAMBLED_SUBJECT) + .valueToMatch(UNSCRAMBLED_SUBJECT.toUpperCase(Locale.FRENCH)); + } + + public Arguments build() { + Preconditions.checkState(description.isPresent()); + Preconditions.checkState(field.isPresent()); + Preconditions.checkState(valueToMatch.isPresent()); + + return Arguments.of(description.get(), field.get(), mimeMessageBuilder, valueToMatch.get()); + } + + } + + static FilteringArgumentBuilder argumentBuilder() { + return new FilteringArgumentBuilder(); + } + + static FilteringArgumentBuilder argumentBuilder(Rule.Condition.Field field) { + return new FilteringArgumentBuilder() + .field(field); + } + + static Stream<Arguments> exactlyEqualsTestSuite() { + return StreamUtils.flatten( + Stream.of(FROM, TO, CC) + .flatMap(headerField -> Stream.of( + argumentBuilder(headerField) + .description("full address value") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch(USER_1_USERNAME), + argumentBuilder(headerField) + .description("full address value (different case)") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch(USER_1_USERNAME.toUpperCase(Locale.ENGLISH)), + argumentBuilder(headerField) + .description("address only value") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch(USER_1_ADDRESS), + argumentBuilder(headerField) + .description("address only value (different case)") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch(USER_1_ADDRESS.toUpperCase(Locale.ENGLISH)), + argumentBuilder(headerField) + .description("personal only value") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch(USER_1_FULL_ADDRESS), + argumentBuilder(headerField) + .description("personal only value (different case)") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch(USER_1_FULL_ADDRESS.toUpperCase()), + argumentBuilder(headerField) + .description("personal header should match personal") + .headerForField(USER_1_USERNAME) + .valueToMatch(USER_1_USERNAME), + argumentBuilder(headerField) + .description("address header should match address") + .headerForField(USER_1_ADDRESS) + .valueToMatch(USER_1_ADDRESS), + argumentBuilder(headerField) + .description("multiple headers") + .headerForField(USER_1_FULL_ADDRESS) + .headerForField(USER_2_FULL_ADDRESS) + .valueToMatch(USER_1_USERNAME), + argumentBuilder(headerField) + .description("scrambled content") + .headerForField(FRED_MARTIN_FULL_SCRAMBLED_ADDRESS) + .valueToMatch(FRED_MARTIN_FULLNAME), + argumentBuilder(headerField) + .description("folded content") + .headerForField(USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) + .valueToMatch(UNFOLDED_USERNAME), + argumentBuilder(headerField) + .description("folded content (different case)") + .headerForField(USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) + .valueToMatch(UNFOLDED_USERNAME.toUpperCase()) + ).map(FilteringArgumentBuilder::build)), + Stream.of(TO_HEADER, CC_HEADER) + .flatMap(headerName -> Stream.of( + argumentBuilder() + .description("full address " + headerName + " header") + .field(RECIPIENT) + .header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch(USER_3_FULL_ADDRESS) + .build(), + argumentBuilder() + .description("full address " + headerName + " header (different case)") + .field(RECIPIENT) + .header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch(USER_3_FULL_ADDRESS.toUpperCase(Locale.ENGLISH)) + .build(), + argumentBuilder() + .description("address only " + headerName + " header") + .field(RECIPIENT).header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch(USER_3_ADDRESS) + .build(), + argumentBuilder() + .description("personal only " + headerName + " header") + .field(RECIPIENT) + .header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch(USER_3_USERNAME) + .build(), + argumentBuilder() + .description("scrambled content in " + headerName + " header") + .field(RECIPIENT) + .header(headerName, FRED_MARTIN_FULL_SCRAMBLED_ADDRESS) + .valueToMatch(FRED_MARTIN_FULLNAME) + .build(), + argumentBuilder() + .description("folded content in " + headerName + " header") + .field(RECIPIENT) + .header(headerName, USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) + .valueToMatch(UNFOLDED_USERNAME) + .build())), + Stream.of( + argumentBuilder().description("multiple to and cc headers").field(RECIPIENT) + .ccRecipient(USER_1_FULL_ADDRESS) + .ccRecipient(USER_2_FULL_ADDRESS) + .toRecipient(USER_3_FULL_ADDRESS) + .toRecipient(USER_4_FULL_ADDRESS) + .valueToMatch(USER_4_FULL_ADDRESS) + .build(), + argumentBuilder().scrambledSubjectToMatch(UNSCRAMBLED_SUBJECT).build(), + argumentBuilder().unscrambledSubjectToMatch(UNSCRAMBLED_SUBJECT).build())); + } + + static Stream<Arguments> containsTestSuite() { + return Stream.concat( + exactlyEqualsTestSuite(), + containsArguments()); + } + + private static Stream<Arguments> containsArguments() { + return StreamUtils.flatten( + Stream.of(FROM, TO, CC) + .flatMap(headerField -> Stream.of( + argumentBuilder(headerField) + .description("full address value (partial matching)") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch("ser1 <"), + argumentBuilder(headerField) + .description("full address value (partial matching, different case)") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch("SER1 <"), + argumentBuilder(headerField) + .description("address only value (partial matching)") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch("ser1@jam"), + argumentBuilder(headerField) + .description("personal only value (partial matching)") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch("ser1"), + argumentBuilder(headerField) + .description("address header & match in the address (partial matching)") + .headerForField(USER_1_ADDRESS) + .valueToMatch("ser1@jam"), + argumentBuilder(headerField) + .description("raw value matching (partial matching)") + .headerForField(GA_BOU_ZO_MEU_FULL_ADDRESS) + .valueToMatch(BOU), + argumentBuilder(headerField) + .description("multiple headers (partial matching)") + .headerForField(USER_1_FULL_ADDRESS) + .headerForField(USER_2_FULL_ADDRESS) + .valueToMatch("ser1@jam"), + argumentBuilder(headerField) + .description("scrambled content (partial matching)") + .headerForField(FRED_MARTIN_FULL_SCRAMBLED_ADDRESS) + .valueToMatch("déric MAR"), + argumentBuilder(headerField) + .description("folded content (partial matching)") + .headerForField(USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) + .valueToMatch("ded_us")) + .map(FilteringArgumentBuilder::build)), + Stream.of(TO_HEADER, CC_HEADER) + .flatMap(headerName -> Stream.of( + argumentBuilder() + .description("full address " + headerName + " header (partial matching)") + .field(RECIPIENT) + .header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch("ser3 <us") + .build(), + argumentBuilder() + .description("full address " + headerName + " header (partial matching, different case)") + .field(RECIPIENT) + .header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch("SER3 <US") + .build(), + argumentBuilder() + .description("address only " + headerName + " header (partial matching)") + .field(RECIPIENT) + .header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch("ser3@jam") + .build(), + argumentBuilder() + .description("personal only " + headerName + " header (partial matching)") + .field(RECIPIENT) + .header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch("ser3") + .build(), + argumentBuilder() + .description("scrambled content in " + headerName + " header (partial matching)") + .field(RECIPIENT) + .header(headerName, FRED_MARTIN_FULL_SCRAMBLED_ADDRESS) + .valueToMatch("déric MAR") + .build(), + argumentBuilder() + .description("folded content in " + headerName + " header (partial matching)") + .field(RECIPIENT) + .header(headerName, USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) + .valueToMatch("folded_us") + .build())), + Stream.of( + argumentBuilder().description("multiple to and cc headers (partial matching)").field(RECIPIENT) + .ccRecipient(USER_1_FULL_ADDRESS) + .ccRecipient(USER_2_FULL_ADDRESS) + .toRecipient(USER_3_FULL_ADDRESS) + .toRecipient(USER_4_FULL_ADDRESS) + .valueToMatch("user4@jam").build(), + argumentBuilder().scrambledSubjectToMatch("is the subject").build(), + argumentBuilder().unscrambledSubjectToMatch("rédéric MART").build())); + } + + static Stream<Arguments> notEqualsTestSuite() { + return Stream.concat( + notContainsTestSuite(), + containsArguments()); + } + + static Stream<Arguments> notContainsTestSuite() { + return StreamUtils.flatten( + Stream.of(FROM, TO, CC) + .flatMap(headerField -> Stream.of( + argumentBuilder(headerField) + .description("normal content") + .headerForField(USER_1_FULL_ADDRESS) + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(headerField) + .description("multiple headers") + .headerForField(USER_1_FULL_ADDRESS) + .from(USER_2_FULL_ADDRESS) + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(headerField) + .description("scrambled content") + .headerForField(FRED_MARTIN_FULL_SCRAMBLED_ADDRESS) + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(headerField) + .description("folded content") + .headerForField(USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(headerField) + .description("empty content") + .headerForField(EMPTY) + .valueToMatch(SHOULD_NOT_MATCH)) + .map(FilteringArgumentBuilder::build)), + Stream.of(TO_HEADER, CC_HEADER) + .flatMap(headerName -> Stream.of( + argumentBuilder() + .description("normal content " + headerName + " header") + .field(RECIPIENT).header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch(SHOULD_NOT_MATCH) + .build(), + argumentBuilder() + .description("scrambled content in " + headerName + " header") + .field(RECIPIENT).header(headerName, FRED_MARTIN_FULL_SCRAMBLED_ADDRESS) + .valueToMatch(SHOULD_NOT_MATCH) + .build(), + argumentBuilder() + .description("folded content in " + headerName + " header") + .field(RECIPIENT) + .header(headerName, USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) + .valueToMatch(SHOULD_NOT_MATCH) + .build(), + argumentBuilder() + .description("bcc header") + .field(RECIPIENT) + .header(headerName, USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) + .valueToMatch(SHOULD_NOT_MATCH) + .build())), + Stream.of( + argumentBuilder().description("multiple to and cc headers").field(RECIPIENT) + .ccRecipient(USER_1_FULL_ADDRESS) + .ccRecipient(USER_2_FULL_ADDRESS) + .toRecipient(USER_3_FULL_ADDRESS) + .toRecipient(USER_4_FULL_ADDRESS) + .valueToMatch(SHOULD_NOT_MATCH) + .build(), + argumentBuilder().description("matching bcc headers").field(RECIPIENT) + .bccRecipient(USER_1_FULL_ADDRESS) + .valueToMatch(USER_1_FULL_ADDRESS) + .build(), + argumentBuilder().scrambledSubjectToMatch(SHOULD_NOT_MATCH).build(), + argumentBuilder().scrambledSubjectShouldNotMatchCaseSensitive().build(), + argumentBuilder().unscrambledSubjectToMatch(SHOULD_NOT_MATCH).build(), + argumentBuilder().unscrambledSubjectShouldNotMatchCaseSensitive().build()), + Stream.of(Rule.Condition.Field.values()) + .map(field -> argumentBuilder() + .description("no header") + .field(field) + .noHeader() + .valueToMatch(USER_1_USERNAME) + .build())); + } + + @ParameterizedTest(name = "CONTAINS should match for header field {1}, with {0}") + @MethodSource("containsTestSuite") + void matchingContainsTest(String testDescription, + Rule.Condition.Field fieldToMatch, + MimeMessageBuilder mimeMessageBuilder, + String valueToMatch, + JMAPFilteringTestSystem testSystem) throws Exception { + + testSystem.defineRulesForRecipient1(Rule.Condition.of(fieldToMatch, CONTAINS, valueToMatch)); + FakeMail mail = testSystem.asMail(mimeMessageBuilder); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isEqualTo(RECIPIENT_1_MAILBOX_1); + } + + @ParameterizedTest(name = "CONTAINS should not match for header field {1}, with {0}") + @MethodSource("notContainsTestSuite") + void notMatchingContainsTest(String testDescription, + Rule.Condition.Field fieldToMatch, + MimeMessageBuilder mimeMessageBuilder, + String valueToMatch, + JMAPFilteringTestSystem testSystem) throws Exception { + + testSystem.defineRulesForRecipient1(Rule.Condition.of(fieldToMatch, CONTAINS, valueToMatch)); + FakeMail mail = testSystem.asMail(mimeMessageBuilder); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isNull(); + } + + @ParameterizedTest(name = "NOT-CONTAINS should be matching for field {1}, with {0}") + @MethodSource("notContainsTestSuite") + void matchingNotContainsTest(String testDescription, + Rule.Condition.Field fieldToMatch, + MimeMessageBuilder mimeMessageBuilder, + String valueToMatch, + JMAPFilteringTestSystem testSystem) throws Exception { + testSystem.defineRulesForRecipient1(Rule.Condition.of(fieldToMatch, NOT_CONTAINS, valueToMatch)); + FakeMail mail = testSystem.asMail(mimeMessageBuilder); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isEqualTo(RECIPIENT_1_MAILBOX_1); + } + + + @ParameterizedTest(name = "NOT-CONTAINS should not be matching for field {1}, with {0}") + @MethodSource("containsTestSuite") + void notContainsNotMatchingTest(String testDescription, + Rule.Condition.Field fieldToMatch, + MimeMessageBuilder mimeMessageBuilder, + String valueToMatch, + JMAPFilteringTestSystem testSystem) throws Exception { + + testSystem.defineRulesForRecipient1(Rule.Condition.of(fieldToMatch, NOT_CONTAINS, valueToMatch)); + FakeMail mail = testSystem.asMail(mimeMessageBuilder); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isNull(); + } + + @ParameterizedTest(name = "EXACTLY-EQUALS should match for header field {1}, with {0}") + @MethodSource("exactlyEqualsTestSuite") + void equalsMatchingTest(String testDescription, + Rule.Condition.Field fieldToMatch, + MimeMessageBuilder mimeMessageBuilder, + String valueToMatch, + JMAPFilteringTestSystem testSystem) throws Exception { + + testSystem.defineRulesForRecipient1(Rule.Condition.of(fieldToMatch, EXACTLY_EQUALS, valueToMatch)); + FakeMail mail = testSystem.asMail(mimeMessageBuilder); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isEqualTo(RECIPIENT_1_MAILBOX_1); + } + + @ParameterizedTest(name = "EXACTLY-EQUALS should not match for header field {1}, with {0}") + @MethodSource("notEqualsTestSuite") + void equalsNotMatchingTest(String testDescription, + Rule.Condition.Field fieldToMatch, + MimeMessageBuilder mimeMessageBuilder, + String valueToMatch, + JMAPFilteringTestSystem testSystem) throws Exception { + testSystem.defineRulesForRecipient1(Rule.Condition.of(fieldToMatch, EXACTLY_EQUALS, valueToMatch)); + FakeMail mail = testSystem.asMail(mimeMessageBuilder); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isNull(); + } + + @ParameterizedTest(name = "NOT_EXACTLY_EQUALS should match for header field {1}, with {0}") + @MethodSource("notEqualsTestSuite") + void notEqualsMatchingTest(String testDescription, + Rule.Condition.Field fieldToMatch, + MimeMessageBuilder mimeMessageBuilder, + String valueToMatch, + JMAPFilteringTestSystem testSystem) throws Exception { + + testSystem.defineRulesForRecipient1(Rule.Condition.of(fieldToMatch, NOT_EXACTLY_EQUALS, valueToMatch)); + FakeMail mail = testSystem.asMail(mimeMessageBuilder); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isEqualTo(RECIPIENT_1_MAILBOX_1); + } + + @ParameterizedTest(name = "NOT_EXACTLY_EQUALS should not match for header field {1}, with {0}") + @MethodSource("exactlyEqualsTestSuite") + void notMatchingNotEqualsTests(String testDescription, + Rule.Condition.Field fieldToMatch, + MimeMessageBuilder mimeMessageBuilder, + String valueToMatch, + JMAPFilteringTestSystem testSystem) throws Exception { + testSystem.defineRulesForRecipient1(Rule.Condition.of(fieldToMatch, NOT_EXACTLY_EQUALS, valueToMatch)); + FakeMail mail = testSystem.asMail(mimeMessageBuilder); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isNull(); + } + + @Nested + class MultiRuleBehaviourTest { + @Test + void mailDirectiveShouldSetFirstMatchedRuleWhenMultipleRules(JMAPFilteringTestSystem testSystem) throws Exception { + InMemoryMailboxManager mailboxManager = testSystem.getMailboxManager(); + MailboxId mailbox1Id = testSystem.createMailbox(mailboxManager, RECIPIENT_1_USERNAME, "RECIPIENT_1_MAILBOX_1"); + MailboxId mailbox2Id = testSystem.createMailbox(mailboxManager, RECIPIENT_1_USERNAME, "RECIPIENT_1_MAILBOX_2"); + MailboxId mailbox3Id = testSystem.createMailbox(mailboxManager, RECIPIENT_1_USERNAME, "RECIPIENT_1_MAILBOX_3"); + + testSystem.defineRulesForRecipient1(); + testSystem.getFilteringManagement().defineRulesForUser(User.fromUsername(RECIPIENT_1_USERNAME), + ImmutableList.of( + Rule.builder() + .id(Rule.Id.of("1")) + .name("rule 1") + .condition(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(mailbox1Id.serialize()))) + .build(), + Rule.builder() + .id(Rule.Id.of("2")) + .name("rule 2") + .condition(Rule.Condition.of(FROM, NOT_CONTAINS, USER_1_USERNAME)) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(mailbox2Id.serialize()))) + .build(), + Rule.builder() + .id(Rule.Id.of("3")) + .name("rule 3") + .condition(Rule.Condition.of(TO, EXACTLY_EQUALS, USER_3_ADDRESS)) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(mailbox3Id.serialize()))) + .build())); + + FakeMail mail = FakeMail.builder() + .sender(USER_1_ADDRESS) + .recipients(RECIPIENT_1) + .mimeMessage(mimeMessageBuilder() + .addFrom(USER_2_ADDRESS) + .addToRecipient(USER_3_ADDRESS) + .setSubject(UNSCRAMBLED_SUBJECT)) + .build(); + + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isEqualTo("RECIPIENT_1_MAILBOX_1"); + } + + @Test + void mailDirectiveShouldSetFirstMatchedMailboxWhenMultipleMailboxes(JMAPFilteringTestSystem testSystem) throws Exception { + InMemoryMailboxManager mailboxManager = testSystem.getMailboxManager(); + MailboxId mailbox1Id = testSystem.createMailbox(mailboxManager, RECIPIENT_1_USERNAME, "RECIPIENT_1_MAILBOX_1"); + MailboxId mailbox2Id = testSystem.createMailbox(mailboxManager, RECIPIENT_1_USERNAME, "RECIPIENT_1_MAILBOX_2"); + MailboxId mailbox3Id = testSystem.createMailbox(mailboxManager, RECIPIENT_1_USERNAME, "RECIPIENT_1_MAILBOX_3"); + + testSystem.getFilteringManagement().defineRulesForUser(User.fromUsername(RECIPIENT_1_USERNAME), + ImmutableList.of( + Rule.builder() + .id(Rule.Id.of("1")) + .name("rule 1") + .condition(Rule.Condition.of(SUBJECT, CONTAINS, UNSCRAMBLED_SUBJECT)) + .action(Rule.Action.of(Rule.Action.AppendInMailboxes.withMailboxIds(ImmutableList.of( + mailbox3Id.serialize(), + mailbox2Id.serialize(), + mailbox1Id.serialize())))) + .build())); + + FakeMail mail = FakeMail.builder() + .sender(USER_1_ADDRESS) + .recipients(RECIPIENT_1) + .mimeMessage(mimeMessageBuilder() + .setSubject(UNSCRAMBLED_SUBJECT)) + .build(); + + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isEqualTo("RECIPIENT_1_MAILBOX_3"); + } + + @Test + void mailDirectiveShouldNotBeSetWhenAllDoNotExactlyEqualsRuleValue(JMAPFilteringTestSystem testSystem) throws Exception { + testSystem.defineRulesForRecipient1( + Rule.Condition.of(FROM, CONTAINS, USER_1_FULL_ADDRESS), + Rule.Condition.of(FROM, EXACTLY_EQUALS, USER_1_FULL_ADDRESS), + Rule.Condition.of(TO, CONTAINS, USER_1_FULL_ADDRESS), + Rule.Condition.of(TO, EXACTLY_EQUALS, USER_1_FULL_ADDRESS), + Rule.Condition.of(CC, CONTAINS, USER_1_FULL_ADDRESS), + Rule.Condition.of(CC, EXACTLY_EQUALS, USER_1_FULL_ADDRESS), + Rule.Condition.of(RECIPIENT, EXACTLY_EQUALS, USER_1_FULL_ADDRESS), + Rule.Condition.of(RECIPIENT, EXACTLY_EQUALS, USER_1_FULL_ADDRESS), + Rule.Condition.of(SUBJECT, CONTAINS, USER_1_FULL_ADDRESS), + Rule.Condition.of(SUBJECT, EXACTLY_EQUALS, USER_1_FULL_ADDRESS) + ); + + FakeMail mail = testSystem.asMail(mimeMessageBuilder()); + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isNull(); + } + + @Test + void mailDirectiveShouldNotBeSetWhenNoneRulesValueIsContained(JMAPFilteringTestSystem testSystem) throws Exception { + + testSystem.defineRulesForRecipient1( + Rule.Condition.of(FROM, CONTAINS, SHOULD_NOT_MATCH), + Rule.Condition.of(TO, CONTAINS, SHOULD_NOT_MATCH), + Rule.Condition.of(CC, CONTAINS, SHOULD_NOT_MATCH)); + + FakeMail mail = FakeMail.builder() + .sender(USER_1_ADDRESS) + .recipients(RECIPIENT_1) + .mimeMessage(mimeMessageBuilder() + .addFrom(USER_1_FULL_ADDRESS) + .addToRecipient(USER_2_FULL_ADDRESS) + .addCcRecipient(USER_3_FULL_ADDRESS)) + .build(); + + testSystem.getJmapFiltering().service(mail); + + assertThat(mail.getAttribute(DELIVERY_PATH_PREFIX + RECIPIENT_1_USERNAME)) + .isNull(); + } + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
