JAMES-2529 Falback on address parsing failure - (not) exactly-equals default to full header matching - (not) contains stays fully functional
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/97ba6503 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/97ba6503 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/97ba6503 Branch: refs/heads/master Commit: 97ba6503c9551d449e6ae3732a61d8b6706d5589 Parents: ee14044 Author: Benoit Tellier <[email protected]> Authored: Thu Aug 30 11:20:33 2018 +0700 Committer: Antoine Duprat <[email protected]> Committed: Thu Aug 30 15:07:02 2018 +0200 ---------------------------------------------------------------------- .../james/jmap/mailet/filter/MailMatcher.java | 46 +++-- .../jmap/mailet/filter/JMAPFilteringTest.java | 201 +++++++++++++------ 2 files changed, 167 insertions(+), 80 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/97ba6503/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 index ab7e05d..a7ac517 100644 --- 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 @@ -20,10 +20,9 @@ package org.apache.james.jmap.mailet.filter; import static org.apache.james.jmap.api.filtering.Rule.Condition; +import static org.apache.mailet.base.RFC2822Headers.FROM; -import java.util.Arrays; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; @@ -36,7 +35,9 @@ 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.james.mime4j.util.MimeUtil; import org.apache.james.util.OptionalUtils; +import org.apache.james.util.StreamUtils; import org.apache.mailet.Mail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,14 +49,14 @@ import com.google.common.collect.ImmutableMap; public interface MailMatcher { interface HeaderExtractor extends ThrowingFunction<Mail, Stream<String>> { + Logger LOGGER = LoggerFactory.getLogger(HeaderExtractor.class); + HeaderExtractor SUBJECT_EXTRACTOR = mail -> OptionalUtils.ofNullableToStream(mail.getMessage().getSubject()); - HeaderExtractor RECIPIENT_EXTRACTOR = mail -> addressExtractor( - 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); + HeaderExtractor RECIPIENT_EXTRACTOR = and(TO_EXTRACTOR, CC_EXTRACTOR); + HeaderExtractor FROM_EXTRACTOR = addressExtractor(mail -> mail.getMessage().getFrom(), FROM); Map<Field, HeaderExtractor> HEADER_EXTRACTOR_REGISTRY = ImmutableMap.<Field, HeaderExtractor>builder() .put(Field.SUBJECT, SUBJECT_EXTRACTOR) @@ -65,21 +66,38 @@ public interface MailMatcher { .put(Field.TO, TO_EXTRACTOR) .build(); + static HeaderExtractor and(HeaderExtractor headerExtractor1, HeaderExtractor headerExtractor2) { + return (Mail mail) -> StreamUtils.flatten(headerExtractor1.apply(mail), headerExtractor2.apply(mail)); + } + static HeaderExtractor recipientExtractor(Message.RecipientType type) { - return mail -> addressExtractor(mail.getMessage().getRecipients(type)); + ThrowingFunction<Mail, Address[]> addressGetter = mail -> mail.getMessage().getRecipients(type); + String fallbackHeaderName = type.toString(); + + return addressExtractor(addressGetter, fallbackHeaderName); } - static Stream<String> addressExtractor(Address[]... addresses) { + static HeaderExtractor addressExtractor(ThrowingFunction<Mail, Address[]> addressGetter, String fallbackHeaderName) { + return mail -> { + try { + return toContent(addressGetter.apply(mail)); + } catch (Exception e) { + LOGGER.info("Failed parsing header. Falling back to unparsed header value matching", e); + return Stream.of(mail.getMessage().getHeader(fallbackHeaderName)) + .map(MimeUtil::unscrambleHeaderValue); + } + }; + } + + static Stream<String> toContent(Address[] addresses) { return Optional.ofNullable(addresses) - .map(Arrays::stream) - .orElse(Stream.empty()) - .filter(Objects::nonNull) - .flatMap(AddressHelper::asStringStream); + .map(AddressHelper::asStringStream) + .orElse(Stream.empty()); } static Optional<HeaderExtractor> asHeaderExtractor(Field field) { - return Optional - .ofNullable(HeaderExtractor.HEADER_EXTRACTOR_REGISTRY.get(field)); + return Optional.ofNullable( + HeaderExtractor.HEADER_EXTRACTOR_REGISTRY.get(field)); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/97ba6503/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 index 57bec4a..15ba523 100644 --- 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 @@ -36,7 +36,6 @@ 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; @@ -250,45 +249,67 @@ class JMAPFilteringTest { argumentBuilder(headerField) .description("folded content (different case)") .headerForField(USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) - .valueToMatch(UNFOLDED_USERNAME.toUpperCase()) + .valueToMatch(UNFOLDED_USERNAME.toUpperCase()), + argumentBuilder(headerField) + .description("invalid address, personal match") + .headerForField("Benoit <invalid>") + .valueToMatch("Benoit"), + argumentBuilder(headerField) + .description("invalid address, address match") + .headerForField("Benoit <invalid>") + .valueToMatch("invalid"), + argumentBuilder(headerField) + .description("invalid address, full match") + .headerForField("Benoit <invalid>") + .valueToMatch("Benoit <invalid>"), + argumentBuilder(headerField) + .description("invalid header, full match") + .headerForField("Benoit <invalid") + .valueToMatch("Benoit <invalid") ).map(FilteringArgumentBuilder::build)), Stream.of(TO_HEADER, CC_HEADER) .flatMap(headerName -> Stream.of( - argumentBuilder() + argumentBuilder(RECIPIENT) .description("full address " + headerName + " header") - .field(RECIPIENT) .header(headerName, USER_3_FULL_ADDRESS) - .valueToMatch(USER_3_FULL_ADDRESS) - .build(), - argumentBuilder() + .valueToMatch(USER_3_FULL_ADDRESS), + argumentBuilder(RECIPIENT) .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() + .valueToMatch(USER_3_FULL_ADDRESS.toUpperCase(Locale.ENGLISH)), + argumentBuilder(RECIPIENT) .description("address only " + headerName + " header") - .field(RECIPIENT).header(headerName, USER_3_FULL_ADDRESS) - .valueToMatch(USER_3_ADDRESS) - .build(), - argumentBuilder() + .header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch(USER_3_ADDRESS), + argumentBuilder(RECIPIENT) .description("personal only " + headerName + " header") - .field(RECIPIENT) .header(headerName, USER_3_FULL_ADDRESS) - .valueToMatch(USER_3_USERNAME) - .build(), - argumentBuilder() + .valueToMatch(USER_3_USERNAME), + argumentBuilder(RECIPIENT) .description("scrambled content in " + headerName + " header") - .field(RECIPIENT) .header(headerName, FRED_MARTIN_FULL_SCRAMBLED_ADDRESS) - .valueToMatch(FRED_MARTIN_FULLNAME) - .build(), - argumentBuilder() + .valueToMatch(FRED_MARTIN_FULLNAME), + argumentBuilder(RECIPIENT) .description("folded content in " + headerName + " header") - .field(RECIPIENT) .header(headerName, USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) - .valueToMatch(UNFOLDED_USERNAME) - .build())), + .valueToMatch(UNFOLDED_USERNAME), + argumentBuilder(RECIPIENT) + .description("invalid " + headerName + " address, personal match") + .header(headerName, "Benoit <invalid>") + .valueToMatch("Benoit"), + argumentBuilder(RECIPIENT) + .description("invalid " + headerName + " address, address match") + .header(headerName, "Benoit <invalid>") + .valueToMatch("invalid"), + argumentBuilder(RECIPIENT) + .description("invalid " + headerName + " address, full match") + .header(headerName, "Benoit <invalid>") + .valueToMatch("Benoit <invalid>"), + argumentBuilder(RECIPIENT) + .description("invalid " + headerName + ", full match") + .header(headerName, "Benoit <invalid") + .valueToMatch("Benoit <invalid")) + .map(FilteringArgumentBuilder::build)), Stream.of( argumentBuilder().description("multiple to and cc headers").field(RECIPIENT) .ccRecipient(USER_1_FULL_ADDRESS) @@ -347,46 +368,83 @@ class JMAPFilteringTest { argumentBuilder(headerField) .description("folded content (partial matching)") .headerForField(USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) - .valueToMatch("ded_us")) + .valueToMatch("ded_us"), + argumentBuilder(headerField) + .description("invalid address, personal match (partial matching)") + .headerForField("Benoit <invalid>") + .valueToMatch("enoi"), + argumentBuilder(headerField) + .description("invalid address, address match (partial matching)") + .headerForField("Benoit <invalid>") + .valueToMatch("nvali"), + argumentBuilder(headerField) + .description("invalid address, full match (partial matching)") + .headerForField("Benoit <invalid>") + .valueToMatch("enoit <invali"), + argumentBuilder(headerField) + .description("invalid header, full match (partial matching)") + .headerForField("Benoit <invalid") + .valueToMatch("enoit <invali"), + argumentBuilder(headerField) + .description("invalid header, personal match (partial matching)") + .headerForField("Benoit <invalid") + .valueToMatch("enoi"), + argumentBuilder(headerField) + .description("invalid header, address match (partial matching)") + .headerForField("Benoit <invalid") + .valueToMatch("nvali")) .map(FilteringArgumentBuilder::build)), Stream.of(TO_HEADER, CC_HEADER) .flatMap(headerName -> Stream.of( - argumentBuilder() + argumentBuilder(RECIPIENT) .description("full address " + headerName + " header (partial matching)") - .field(RECIPIENT) .header(headerName, USER_3_FULL_ADDRESS) - .valueToMatch("ser3 <us") - .build(), - argumentBuilder() + .valueToMatch("ser3 <us"), + argumentBuilder(RECIPIENT) .description("full address " + headerName + " header (partial matching, different case)") - .field(RECIPIENT) .header(headerName, USER_3_FULL_ADDRESS) - .valueToMatch("SER3 <US") - .build(), - argumentBuilder() + .valueToMatch("SER3 <US"), + argumentBuilder(RECIPIENT) .description("address only " + headerName + " header (partial matching)") - .field(RECIPIENT) .header(headerName, USER_3_FULL_ADDRESS) - .valueToMatch("ser3@jam") - .build(), - argumentBuilder() + .valueToMatch("ser3@jam"), + argumentBuilder(RECIPIENT) .description("personal only " + headerName + " header (partial matching)") - .field(RECIPIENT) .header(headerName, USER_3_FULL_ADDRESS) - .valueToMatch("ser3") - .build(), - argumentBuilder() + .valueToMatch("ser3"), + argumentBuilder(RECIPIENT) .description("scrambled content in " + headerName + " header (partial matching)") - .field(RECIPIENT) .header(headerName, FRED_MARTIN_FULL_SCRAMBLED_ADDRESS) - .valueToMatch("déric MAR") - .build(), - argumentBuilder() + .valueToMatch("déric MAR"), + argumentBuilder(RECIPIENT) .description("folded content in " + headerName + " header (partial matching)") - .field(RECIPIENT) .header(headerName, USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) - .valueToMatch("folded_us") - .build())), + .valueToMatch("folded_us"), + argumentBuilder(RECIPIENT) + .description("invalid address, personal match (partial matching)") + .header(headerName, "Benoit <invalid>") + .valueToMatch("enoi"), + argumentBuilder(RECIPIENT) + .description("invalid address, address match (partial matching)") + .header(headerName, "Benoit <invalid>") + .valueToMatch("nvali"), + argumentBuilder(RECIPIENT) + .description("invalid address, full match (partial matching)") + .header(headerName, "Benoit <invalid>") + .valueToMatch("enoit <invali"), + argumentBuilder(RECIPIENT) + .description("invalid header, full match (partial matching)") + .header(headerName, "Benoit <invalid") + .valueToMatch("enoit <invali"), + argumentBuilder(RECIPIENT) + .description("invalid header, personal match (partial matching)") + .header(headerName, "Benoit <invalid") + .valueToMatch("enoi"), + argumentBuilder(RECIPIENT) + .description("invalid header, address match (partial matching)") + .header(headerName, "Benoit <invalid") + .valueToMatch("nvali")) + .map(FilteringArgumentBuilder::build)), Stream.of( argumentBuilder().description("multiple to and cc headers (partial matching)").field(RECIPIENT) .ccRecipient(USER_1_FULL_ADDRESS) @@ -428,32 +486,43 @@ class JMAPFilteringTest { argumentBuilder(headerField) .description("empty content") .headerForField(EMPTY) + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(headerField) + .description("invalid address, personal match") + .headerForField("Benoit <invalid>") + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(headerField) + .description("invalid header, full match") + .headerForField("Benoit <invalid") .valueToMatch(SHOULD_NOT_MATCH)) .map(FilteringArgumentBuilder::build)), Stream.of(TO_HEADER, CC_HEADER) .flatMap(headerName -> Stream.of( - argumentBuilder() + argumentBuilder(RECIPIENT) .description("normal content " + headerName + " header") - .field(RECIPIENT).header(headerName, USER_3_FULL_ADDRESS) - .valueToMatch(SHOULD_NOT_MATCH) - .build(), - argumentBuilder() + .header(headerName, USER_3_FULL_ADDRESS) + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(RECIPIENT) .description("scrambled content in " + headerName + " header") .field(RECIPIENT).header(headerName, FRED_MARTIN_FULL_SCRAMBLED_ADDRESS) - .valueToMatch(SHOULD_NOT_MATCH) - .build(), - argumentBuilder() + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(RECIPIENT) .description("folded content in " + headerName + " header") - .field(RECIPIENT) .header(headerName, USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) - .valueToMatch(SHOULD_NOT_MATCH) - .build(), - argumentBuilder() + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(RECIPIENT) .description("bcc header") - .field(RECIPIENT) .header(headerName, USER_1_AND_UNFOLDED_USER_FULL_ADDRESS) - .valueToMatch(SHOULD_NOT_MATCH) - .build())), + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(RECIPIENT) + .description("invalid address, personal match") + .header(headerName, "Benoit <invalid>") + .valueToMatch(SHOULD_NOT_MATCH), + argumentBuilder(RECIPIENT) + .description("invalid header, full match") + .header(headerName, "Benoit <invalid") + .valueToMatch(SHOULD_NOT_MATCH)) + .map(FilteringArgumentBuilder::build)), Stream.of( argumentBuilder().description("multiple to and cc headers").field(RECIPIENT) .ccRecipient(USER_1_FULL_ADDRESS) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
