Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0716d5f4 authored by Siyamed Sinir's avatar Siyamed Sinir
Browse files

Email address autolink regex updates.

This CL updates the email address pattern with:
* Local-part should be max 64 characters, and domain should be max 255
  characters (it was the opposite before).
* Labels in the domain name should be at most 63 characters.
* Support non-ascii unicode characters.
* Add tests for email address pattern.

Bug:9585450
Change-Id: I983f269904ef014ef625417dd08b6509084e6879
parent f0febdcf
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -224,7 +224,7 @@ public class Linkify {
        }

        if ((mask & EMAIL_ADDRESSES) != 0) {
            gatherLinks(links, text, Patterns.EMAIL_ADDRESS,
            gatherLinks(links, text, Patterns.AUTOLINK_EMAIL_ADDRESS,
                new String[] { "mailto:" },
                null, null);
        }
+30 −0
Original line number Diff line number Diff line
@@ -394,6 +394,36 @@ public class Patterns {
    public static final Pattern AUTOLINK_WEB_URL = Pattern.compile(
            "(" + WEB_URL_WITH_PROTOCOL + "|" + WEB_URL_WITHOUT_PROTOCOL + ")");

    /**
     * Regular expression for valid email characters. Does not include some of the valid characters
     * defined in RFC5321: #&~!^`{}/=$*?|
     */
    private static final String EMAIL_CHAR = LABEL_CHAR + "\\+\\-_%'";

    /**
     * Regular expression for local part of an email address. RFC5321 section 4.5.3.1.1 limits
     * the local part to be at most 64 octets.
     */
    private static final String EMAIL_ADDRESS_LOCAL_PART =
            "[" + EMAIL_CHAR + "]" + "(?:[" + EMAIL_CHAR + "\\.]{1,62}[" + EMAIL_CHAR + "])?";

    /**
     * Regular expression for the domain part of an email address. RFC5321 section 4.5.3.1.2 limits
     * the domain to be at most 255 octets.
     */
    private static final String EMAIL_ADDRESS_DOMAIN =
            "(?=.{1,255}(?:\\s|$|^))" + HOST_NAME;

    /**
     * Regular expression pattern to match email addresses. It excludes double quoted local parts
     * and the special characters #&~!^`{}/=$*?| that are included in RFC5321.
     * @hide
     */
    public static final Pattern AUTOLINK_EMAIL_ADDRESS = Pattern.compile("(" + WORD_BOUNDARY +
            "(?:" + EMAIL_ADDRESS_LOCAL_PART + "@" + EMAIL_ADDRESS_DOMAIN + ")" +
            WORD_BOUNDARY + ")"
    );

    public static final Pattern EMAIL_ADDRESS
        = Pattern.compile(
            "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
+227 −7
Original line number Diff line number Diff line
@@ -508,6 +508,226 @@ public class PatternsTest extends TestCase {
                Patterns.DOMAIN_NAME.matcher(domain).matches());
    }

    // Tests for Patterns.AUTOLINK_EMAIL_ADDRESS

    public void testAutoLinkEmailAddress_matchesShortValidEmail() throws Exception {
        String email = "a@a.co";
        assertTrue("Should match short valid email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesRegularEmail() throws Exception {
        String email = "email@android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesEmailWithMultipleSubdomains() throws Exception {
        String email = "email@e.somelongdomainnameforandroid.abc.uk";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesLocalPartWithDot() throws Exception {
        String email = "e.mail@android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesLocalPartWithPlus() throws Exception {
        String email = "e+mail@android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesLocalPartWithUnderscore() throws Exception {
        String email = "e_mail@android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesLocalPartWithDash() throws Exception {
        String email = "e-mail@android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesLocalPartWithApostrophe() throws Exception {
        String email = "e'mail@android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesLocalPartWithDigits() throws Exception {
        String email = "123@android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesUnicodeLocalPart() throws Exception {
        String email = "\uD604\uAE08\uC601\uC218\uC99D@android.kr";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesLocalPartWithEmoji() throws Exception {
        String email = "smiley\u263A@android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesLocalPartWithSurrogatePairs() throws Exception {
        String email = "\uD83C\uDF38@android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesDomainWithDash() throws Exception {
        String email = "email@an-droid.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesUnicodeDomain() throws Exception {
        String email = "email@\uD604\uAE08\uC601\uC218\uC99D.kr";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesUnicodeLocalPartAndDomain() throws Exception {
        String email = "\uD604\uAE08\uC601\uC218\uC99D@\uD604\uAE08\uC601\uC218\uC99D.kr";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesDomainWithEmoji() throws Exception {
        String email = "smiley@\u263Aandroid.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesDomainWithSurrogatePairs() throws Exception {
        String email = "email@\uD83C\uDF38android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesLocalPartAndDomainWithSurrogatePairs()
            throws Exception {
        String email = "\uD83C\uDF38@\uD83C\uDF38android.com";
        assertTrue("Should match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_doesNotMatchStringWithoutAtSign() throws Exception {
        String email = "android.com";
        assertFalse("Should not match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_doesNotMatchPlainString() throws Exception {
        String email = "email";
        assertFalse("Should not match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_doesNotMatchStringWithMultipleAtSigns() throws Exception {
        String email = "email@android@android.com";
        assertFalse("Should not match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_doesNotMatchEmailWithoutTld() throws Exception {
        String email = "email@android";
        assertFalse("Should not match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_doesNotMatchLocalPartEndingWithDot() throws Exception {
        String email = "email.@android.com";
        assertFalse("Should not match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_doesNotMatchLocalPartStartingWithDot() throws Exception {
        String email = ".email@android.com";
        assertFalse("Should not match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_doesNotMatchDomainStartingWithDash() throws Exception {
        String email = "email@-android.com";
        assertFalse("Should not match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_doesNotMatchDomainWithConsecutiveDots() throws Exception {
        String email = "email@android..com";
        assertFalse("Should not match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_doesNotMatchEmailWithIpAsDomain() throws Exception {
        String email = "email@127.0.0.1";
        assertFalse("Should not match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_doesNotMatchEmailWithInvalidTld() throws Exception {
        String email = "email@android.c";
        assertFalse("Should not match email: " + email,
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesLocalPartUpTo64Chars() throws Exception {
        String localPart = "";
        for (int i = 0; i < 64; i++) {
            localPart += "a";
        }
        String email = localPart + "@android.com";

        assertTrue("Should match local part of length: " + localPart.length(),
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());

        email = localPart + "a@android.com";
        assertFalse("Should not match local part of length: " + localPart.length(),
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesSubdomainUpTo63Chars() throws Exception {
        String subdomain = "";
        for (int i = 0; i < 63; i++) {
            subdomain += "a";
        }
        String email = "email@" + subdomain + ".com";

        assertTrue("Should match subdomain of length: " + subdomain.length(),
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());

        subdomain += "a";
        email = "email@" + subdomain + ".com";
        assertFalse("Should not match local part of length: " + subdomain.length(),
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    public void testAutoLinkEmailAddress_matchesDomainUpTo255Chars() throws Exception {
        String longDomain = "";
        while (longDomain.length() <= 250) {
            longDomain += "d.";
        }
        longDomain += "com";
        assertEquals(255, longDomain.length());
        String email = "a@" + longDomain;

        assertTrue("Should match domain of length: " + longDomain.length(),
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());

        email = email + "m";
        assertEquals(258, email.length());
        assertFalse("Should not match domain of length: " + longDomain.length(),
                Patterns.AUTOLINK_EMAIL_ADDRESS.matcher(email).matches());
    }

    // Tests for Patterns.PHONE

    @SmallTest