You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ad...@apache.org on 2018/08/30 13:15:51 UTC

[09/26] james-project git commit: JAMES-2529 Falback on address parsing failure

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 <bt...@linagora.com>
Authored: Thu Aug 30 11:20:33 2018 +0700
Committer: Antoine Duprat <ad...@linagora.com>
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: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org