Allow multiple wildcards in providers.xml; add hotmail domains

* Change handling of the providers.xml file to allow asterisk
  as a placeholder for an individual domain name part
  (the previous behavior was a very greedy wildcard)
* Add hotmail aliases using the new scheme
* Update unit tests

Bug: 5318329
Change-Id: I73a0dfcb956830b18c5460a1b3ddfc58459d08c9
This commit is contained in:
Marc Blank 2011-09-14 16:24:32 -07:00
parent 3d0f0d74b0
commit 1b65e834c3
3 changed files with 102 additions and 178 deletions

View File

@ -91,9 +91,12 @@
contain pattern matching characters that can be used to match user entered
domains without knowing the exact domain.
The domain attribute may specify a most one global character - a '*'. The
global character matches zero or more characters. This is a very greedy wild
card and may lead to unexpected matches.
An asterisk (*) is used to match that part of a domain name that is demarcated
by a period (dot); no other characters may appear on either side of an asterisk.
Therefore, foo.*.com and *.mail.com are valid, whereas a*.com and foo.c* are not.
An asterisk is also not greedy; it only matches a single part of a domain name;
therefore, foo.bar.bletch is NOT matched by foo.*; it does, however, match
foo.*.* or foo.bar.*.
The alternate is the wild card character - a '?'. The wild card character
matches any single character. This is very useful when the number of characters
@ -168,15 +171,39 @@
<!-- Hotmail and variants. NOTE: These are handled by exchange if available, else POP3. -->
<!-- EXCHANGE-REMOVE-SECTION-START -->
<provider id="live" label="Windows Live Hotmail Plus" domain="live.com">
<provider id="live1" label="Windows Live Hotmail Plus" domain="live.*">
<incoming uri="eas+ssl+://m.hotmail.com" username="$email" />
<outgoing uri="eas+ssl+://m.hotmail.com" username="$email" />
</provider>
<provider id="hotmail" label="Windows Live Hotmail Plus" domain="hotmail.com">
<provider id="live2" label="Windows Live Hotmail Plus" domain="live.*.*">
<incoming uri="eas+ssl+://m.hotmail.com" username="$email" />
<outgoing uri="eas+ssl+://m.hotmail.com" username="$email" />
</provider>
<provider id="msn" label="Windows Live Hotmail Plus" domain="msn.com">
<provider id="live3" label="Windows Live Hotmail Plus" domain="*.live.*">
<incoming uri="eas+ssl+://m.hotmail.com" username="$email" />
<outgoing uri="eas+ssl+://m.hotmail.com" username="$email" />
</provider>
<provider id="hotmail1" label="Windows Live Hotmail Plus" domain="hotmail.*">
<incoming uri="eas+ssl+://m.hotmail.com" username="$email" />
<outgoing uri="eas+ssl+://m.hotmail.com" username="$email" />
</provider>
<provider id="hotmail2" label="Windows Live Hotmail Plus" domain="hotmail.*.*">
<incoming uri="eas+ssl+://m.hotmail.com" username="$email" />
<outgoing uri="eas+ssl+://m.hotmail.com" username="$email" />
</provider>
<provider id="hotmail3" label="Windows Live Hotmail Plus" domain="livemail.*">
<incoming uri="eas+ssl+://m.hotmail.com" username="$email" />
<outgoing uri="eas+ssl+://m.hotmail.com" username="$email" />
</provider>
<provider id="hotmail4" label="Windows Live Hotmail Plus" domain="livemail.*.*">
<incoming uri="eas+ssl+://m.hotmail.com" username="$email" />
<outgoing uri="eas+ssl+://m.hotmail.com" username="$email" />
</provider>
<provider id="msn" label="Windows Live Hotmail Plus" domain="msn.*">
<incoming uri="eas+ssl+://m.hotmail.com" username="$email" />
<outgoing uri="eas+ssl+://m.hotmail.com" username="$email" />
</provider>
<provider id="msnhotmail" label="Windows Live Hotmail Plus" domain="msnhotmail.com">
<incoming uri="eas+ssl+://m.hotmail.com" username="$email" />
<outgoing uri="eas+ssl+://m.hotmail.com" username="$email" />
</provider>

View File

@ -16,13 +16,6 @@
package com.android.email.activity.setup;
import com.android.email.R;
import com.android.email.VendorPolicyLoader;
import com.android.email.provider.AccountBackupRestore;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.XmlResourceParser;
@ -30,15 +23,23 @@ import android.text.Editable;
import android.util.Log;
import android.widget.EditText;
import com.android.email.R;
import com.android.email.VendorPolicyLoader;
import com.android.email.provider.AccountBackupRestore;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.google.common.annotations.VisibleForTesting;
import java.io.Serializable;
import java.util.regex.Pattern;
public class AccountSettingsUtils {
/** Pattern to match globals in the domain */
private final static Pattern DOMAIN_GLOB_PATTERN = Pattern.compile("\\*");
/** Pattern to match any part of a domain */
private final static String WILD_STRING = "*";
/** Will match any, single character */
private final static char WILD_CHARACTER = '?';
private final static String DOMAIN_SEPARATOR = "\\.";
/**
* Commits the UI-related settings of an account to the provider. This is static so that it
@ -116,7 +117,7 @@ public class AccountSettingsUtils {
&& "provider".equals(xml.getName())) {
String providerDomain = getXmlAttribute(context, xml, "domain");
try {
if (globMatchIgnoreCase(domain, providerDomain)) {
if (matchProvider(domain, providerDomain)) {
provider = new Provider();
provider.id = getXmlAttribute(context, xml, "id");
provider.label = getXmlAttribute(context, xml, "label");
@ -154,99 +155,44 @@ public class AccountSettingsUtils {
}
/**
* Returns if the string <code>s1</code> matches the string <code>s2</code>. The string
* <code>s2</code> may contain any number of wildcards -- a '?' character -- and/or a
* single global character -- a '*' character. Wildcards match any, single character
* while a global character matches zero or more characters.
* @throws IllegalArgumentException if either string is null or <code>s2</code> has
* multiple globals.
* Returns true if the string <code>s1</code> matches the string <code>s2</code>. The string
* <code>s2</code> may contain any number of wildcards -- a '?' character -- and/or asterisk
* characters -- '*'. Wildcards match any single character, while the asterisk matches a domain
* part (i.e. substring demarcated by a period, '.')
*/
/*package*/ static boolean globMatchIgnoreCase(String s1, String s2)
throws IllegalArgumentException {
if (s1 == null || s2 == null) {
throw new IllegalArgumentException("one or both strings are null");
@VisibleForTesting
static boolean matchProvider(String testDomain, String providerDomain) {
String[] testParts = testDomain.split(DOMAIN_SEPARATOR);
String[] providerParts = providerDomain.split(DOMAIN_SEPARATOR);
if (testParts.length != providerParts.length) {
return false;
}
// Handle the possible global in the domain name
String[] globParts = DOMAIN_GLOB_PATTERN.split(s2);
switch (globParts.length) {
case 1:
// No globals; test for simple equality
if (!wildEqualsIgnoreCase(s1, globParts[0])) {
return false;
}
break;
case 2:
// Global; test the front & end parts of the domain
String d1 = globParts[0];
String d2 = globParts[1];
if (!wildStartsWithIgnoreCase(s1, d1) ||
!wildEndsWithIgnoreCase(s1.substring(d1.length()), d2)) {
return false;
}
break;
default:
throw new IllegalArgumentException("Multiple globals");
for (int i = 0; i < testParts.length; i++) {
String testPart = testParts[i].toLowerCase();
String providerPart = providerParts[i].toLowerCase();
if (!providerPart.equals(WILD_STRING) &&
!matchWithWildcards(testPart, providerPart)) {
return false;
}
}
return true;
}
/**
* Returns if the string <code>s1</code> equals the string <code>s2</code>. The string
* <code>s2</code> may contain zero or more wildcards -- a '?' character.
* @throws IllegalArgumentException if the strings are null.
*/
/*package*/ static boolean wildEqualsIgnoreCase(String s1, String s2)
throws IllegalArgumentException {
if (s1 == null || s2 == null) {
throw new IllegalArgumentException("one or both strings are null");
}
if (s1.length() != s2.length()) {
private static boolean matchWithWildcards(String testPart, String providerPart) {
int providerLength = providerPart.length();
if (testPart.length() != providerLength){
return false;
}
char[] charArray1 = s1.toLowerCase().toCharArray();
char[] charArray2 = s2.toLowerCase().toCharArray();
for (int i = 0; i < charArray2.length; i++) {
if (charArray2[i] == WILD_CHARACTER || charArray1[i] == charArray2[i]) continue;
return false;
for (int i = 0; i < providerLength; i++) {
char testChar = testPart.charAt(i);
char providerChar = providerPart.charAt(i);
if (testChar != providerChar && providerChar != WILD_CHARACTER) {
return false;
}
}
return true;
}
/**
* Returns if the string <code>s1</code> starts with the string <code>s2</code>. The string
* <code>s2</code> may contain zero or more wildcards -- a '?' character.
* @throws IllegalArgumentException if the strings are null.
*/
/*package*/ static boolean wildStartsWithIgnoreCase(String s1, String s2)
throws IllegalArgumentException {
if (s1 == null || s2 == null) {
throw new IllegalArgumentException("one or both strings are null");
}
if (s1.length() < s2.length()) {
return false;
}
s1 = s1.substring(0, s2.length());
return wildEqualsIgnoreCase(s1, s2);
}
/**
* Returns if the string <code>s1</code> ends with the string <code>s2</code>. The string
* <code>s2</code> may contain zero or more wildcards -- a '?' character.
* @throws IllegalArgumentException if the strings are null.
*/
/*package*/ static boolean wildEndsWithIgnoreCase(String s1, String s2)
throws IllegalArgumentException {
if (s1 == null || s2 == null) {
throw new IllegalArgumentException("one or both strings are null");
}
if (s1.length() < s2.length()) {
return false;
}
s1 = s1.substring(s1.length() - s2.length(), s1.length());
return wildEqualsIgnoreCase(s1, s2);
}
/**
* Attempts to get the given attribute as a String resource first, and if it fails
* returns the attribute as a simple String value.

View File

@ -16,14 +16,13 @@
package com.android.email.activity.setup;
import com.android.email.tests.R;
import com.android.email.activity.setup.AccountSettingsUtils.Provider;
import android.content.Context;
import android.test.AndroidTestCase;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.email.activity.setup.AccountSettingsUtils.Provider;
import com.android.email.tests.R;
/**
* This is a series of unit tests for the AccountSettingsUtils class.
*
@ -100,86 +99,38 @@ public class AccountSettingsUtilsTests extends InstrumentationTestCase {
assertNull(testProvider);
}
public void testGlobEndsWithIgnoreCase() {
assertTrue(AccountSettingsUtils.wildEndsWithIgnoreCase(
"yahoo.com.tw", ".??"));
assertTrue(AccountSettingsUtils.wildEndsWithIgnoreCase(
"abcd", "a??d"));
assertFalse(AccountSettingsUtils.wildEndsWithIgnoreCase(
"yahoo.com.tw.foo.com", ".??"));
assertFalse(AccountSettingsUtils.wildEndsWithIgnoreCase(
"abc", "a??d"));
}
public void testMatchProvider() {
assertTrue(AccountSettingsUtils.matchProvider("foo.com", "foo.com"));
assertFalse(AccountSettingsUtils.matchProvider("foo.co", "foo.com"));
assertFalse(AccountSettingsUtils.matchProvider("", "foo.com"));
public void testGlobStartsWithIgnoreCase() {
assertTrue(AccountSettingsUtils.wildStartsWithIgnoreCase(
"tw.yahoo.com", "??."));
assertTrue(AccountSettingsUtils.wildStartsWithIgnoreCase(
"abcdxyz", "a??d"));
assertFalse(AccountSettingsUtils.wildStartsWithIgnoreCase(
"abc", "a??d"));
}
assertTrue(AccountSettingsUtils.matchProvider("foo.com", "fo?.com"));
assertTrue(AccountSettingsUtils.matchProvider("foo.com", "f??.com"));
assertTrue(AccountSettingsUtils.matchProvider("fzz.com", "f??.com"));
assertTrue(AccountSettingsUtils.matchProvider("foo.com", "???.???"));
assertFalse(AccountSettingsUtils.matchProvider("foo.com", "???.????"));
public void testGlobEqualsIgnoreCase() {
assertTrue(AccountSettingsUtils.wildEqualsIgnoreCase(
"tw.yahoo.com", "??.yahoo.com"));
assertTrue(AccountSettingsUtils.wildEqualsIgnoreCase(
"yahoo.com.tw", "yahoo.com.??"));
assertTrue(AccountSettingsUtils.wildEqualsIgnoreCase(
"abcdxyz", "a??dxyz"));
assertFalse(AccountSettingsUtils.wildEqualsIgnoreCase(
"abc", "a??d"));
assertFalse(AccountSettingsUtils.wildEqualsIgnoreCase(
"abccxyz", "a??d"));
}
assertTrue(AccountSettingsUtils.matchProvider("foo.com", "*.com"));
assertTrue(AccountSettingsUtils.matchProvider("foo.com", "foo.*"));
assertTrue(AccountSettingsUtils.matchProvider("foo.com", "*.*"));
assertFalse(AccountSettingsUtils.matchProvider("foo.com", "fox.*"));
assertTrue(AccountSettingsUtils.matchProvider("foo.com", "*.???"));
assertFalse(AccountSettingsUtils.matchProvider("foo.com", "*.?"));
public void testGlobMatchIgnoreCase() {
assertTrue(AccountSettingsUtils.globMatchIgnoreCase(
"mail.yahoo.com", "mail*yahoo.com"));
assertTrue(AccountSettingsUtils.globMatchIgnoreCase(
"mail.foo.bar.yahoo.com", "mail*yahoo.com"));
assertTrue(AccountSettingsUtils.globMatchIgnoreCase(
"mail.notwhatyouwant.myyahoo.com", "mail*yahoo.com"));
assertFalse(AccountSettingsUtils.matchProvider("foo.bar.com", "food.barge.comb"));
assertTrue(AccountSettingsUtils.matchProvider("foo.bar.com", "foo.bar.com"));
assertFalse(AccountSettingsUtils.matchProvider("foo.bar.com", "foo.bar.gag.com"));
assertTrue(AccountSettingsUtils.matchProvider("foo.bar.com", "foo.*.com"));
assertTrue(AccountSettingsUtils.matchProvider("foo.bar.com", "foo.*.*"));
assertFalse(AccountSettingsUtils.matchProvider("foo.bar.com", "foo.bar.*.*"));
assertFalse(AccountSettingsUtils.matchProvider("foo.bar.com", "foo.bar.*com"));
assertTrue(AccountSettingsUtils.matchProvider("foo.bar.com", "*.bar.com"));
assertTrue(AccountSettingsUtils.matchProvider("foo.bar.com", "*.*.com"));
assertTrue(AccountSettingsUtils.matchProvider("foo.bar.com", "*.*.*"));
assertTrue(AccountSettingsUtils.matchProvider("foo.bar.com", "foo.bar.*"));
// Test other combinations
assertTrue(AccountSettingsUtils.globMatchIgnoreCase(
"yahoo.com", "yahoo.com"));
assertFalse(AccountSettingsUtils.globMatchIgnoreCase(
"yahoo.com.au", "yahoo.com"));
assertFalse(AccountSettingsUtils.globMatchIgnoreCase(
"yahoo.com", "yahoo.com.au"));
// Try mixed case in the domain name
assertTrue(AccountSettingsUtils.globMatchIgnoreCase(
"GmAiL.cOm", "gMaIl.CoM"));
assertFalse(AccountSettingsUtils.globMatchIgnoreCase(
"nonexist.frr.com", "*.rr.com"));
assertFalse(AccountSettingsUtils.globMatchIgnoreCase(
"rr.com", "*.rr.com"));
assertTrue(AccountSettingsUtils.globMatchIgnoreCase(
"abbc.com", "ab*bc.com"));
assertFalse(AccountSettingsUtils.globMatchIgnoreCase(
"abc.com", "ab*bc.com"));
try {
AccountSettingsUtils.globMatchIgnoreCase(
"abc.com", "ab*bc*.com");
fail("Should have thrown an IllegalArgumentException");
} catch (IllegalArgumentException e) {
}
try {
AccountSettingsUtils.globMatchIgnoreCase(
null, "ab*bc*.com");
fail("Should have thrown an IllegalArgumentException");
} catch (IllegalArgumentException e) {
}
try {
AccountSettingsUtils.globMatchIgnoreCase(
"abc.com", null);
fail("Should have thrown an IllegalArgumentException");
} catch (IllegalArgumentException e) {
}
assertTrue(AccountSettingsUtils.matchProvider("foo.bar.com", "foo.???.*"));
assertFalse(AccountSettingsUtils.matchProvider("foo.bar.com", "foo.*??.*"));
}
public void testExpandTemplates() {