Re-designed Public Suffix matching algorhithm based on recommendations published at https://publicsuffix.org/list/

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1622478 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2014-09-04 14:00:16 +00:00
parent 4ee8c3e011
commit 2466424d5d
6 changed files with 85 additions and 60 deletions

View File

@ -104,6 +104,6 @@ public class PublicSuffixFilter implements CookieAttributeHandler {
if (matcher == null) { if (matcher == null) {
matcher = new PublicSuffixMatcher(this.suffixes, this.exceptions); matcher = new PublicSuffixMatcher(this.suffixes, this.exceptions);
} }
return matcher.match(cookie.getDomain()); return matcher.matches(cookie.getDomain());
} }
} }

View File

@ -28,6 +28,7 @@ package org.apache.http.conn.util;
import java.net.IDN; import java.net.IDN;
import java.util.Collection; import java.util.Collection;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -66,36 +67,55 @@ public final class PublicSuffixMatcher {
} }
} }
public boolean match(final String domain) { /**
String s = domain; * Returns registrable part of the domain for the given domain name of {@code null}
if (s == null) { * if given domain represents a public suffix.
return false; *
* @param domain
* @return
*/
public String getDomainRoot(final String domain) {
if (domain == null) {
return null;
} }
if (s.startsWith(".")) { if (domain.startsWith(".")) {
s = s.substring(1); return null;
} }
s = IDN.toUnicode(s); String domainName = null;
String segment = domain.toLowerCase(Locale.ROOT);
while (segment != null) {
// An exception rule takes priority over any other matching rule. // An exception rule takes priority over any other matching rule.
if (this.exceptions != null && this.exceptions.containsKey(s)) { if (this.exceptions != null && this.exceptions.containsKey(IDN.toUnicode(segment))) {
return false; return segment;
} }
do { if (this.rules.containsKey(IDN.toUnicode(segment))) {
if (this.rules.containsKey(s)) {
return true;
}
// patterns
if (s.startsWith("*.")) {
s = s.substring(2);
}
final int nextdot = s.indexOf('.');
if (nextdot == -1) {
break; break;
} }
s = "*" + s.substring(nextdot);
} while (!s.isEmpty());
final int nextdot = segment.indexOf('.');
final String nextSegment = nextdot != -1 ? segment.substring(nextdot + 1) : null;
if (nextSegment != null) {
if (this.rules.containsKey("*." + IDN.toUnicode(nextSegment))) {
break;
}
}
if (nextdot != -1) {
domainName = segment;
}
segment = nextSegment;
}
return domainName;
}
public boolean matches(final String domain) {
if (domain == null) {
return false; return false;
} }
final String domainRoot = getDomainRoot(domain.startsWith(".") ? domain.substring(1) : domain);
return domainRoot == null;
}
} }

View File

@ -70,7 +70,8 @@ public class PublicSuffixDomainFilter implements CommonCookieAttributeHandler {
*/ */
@Override @Override
public boolean match(final Cookie cookie, final CookieOrigin origin) { public boolean match(final Cookie cookie, final CookieOrigin origin) {
if (matcher.match(cookie.getDomain())) { final String domain = cookie.getDomain();
if (matcher.matches(domain) && !domain.equalsIgnoreCase("localhost")) {
return false; return false;
} else { } else {
return handler.match(cookie, origin); return handler.match(cookie, origin);

View File

@ -57,29 +57,36 @@ public class TestPublicSuffixMatcher {
} }
@Test @Test
public void testParse() throws Exception { public void testGetDomainRoot() throws Exception {
Assert.assertTrue(matcher.match(".jp")); Assert.assertEquals("example.xx", matcher.getDomainRoot("example.XX"));
Assert.assertTrue(matcher.match(".ac.jp")); Assert.assertEquals("example.xx", matcher.getDomainRoot("www.example.XX"));
Assert.assertTrue(matcher.match(".any.tokyo.jp")); Assert.assertEquals("example.xx", matcher.getDomainRoot("www.blah.blah.example.XX"));
Assert.assertEquals(null, matcher.getDomainRoot("xx"));
Assert.assertEquals(null, matcher.getDomainRoot("jp"));
Assert.assertEquals(null, matcher.getDomainRoot("example"));
Assert.assertEquals("example.example", matcher.getDomainRoot("example.example"));
Assert.assertEquals(null, matcher.getDomainRoot("ac.jp"));
Assert.assertEquals(null, matcher.getDomainRoot("any.tokyo.jp"));
Assert.assertEquals("metro.tokyo.jp", matcher.getDomainRoot("metro.tokyo.jp"));
Assert.assertEquals("blah.blah.tokyo.jp", matcher.getDomainRoot("blah.blah.tokyo.jp"));
Assert.assertEquals("blah.ac.jp", matcher.getDomainRoot("blah.blah.ac.jp"));
}
@Test
public void testMatch() throws Exception {
Assert.assertTrue(matcher.matches(".jp"));
Assert.assertTrue(matcher.matches(".ac.jp"));
Assert.assertTrue(matcher.matches(".any.tokyo.jp"));
// exception // exception
Assert.assertFalse(matcher.match(".metro.tokyo.jp")); Assert.assertFalse(matcher.matches(".metro.tokyo.jp"));
} }
@Test @Test
public void testUnicode() throws Exception { public void testMatchUnicode() throws Exception {
Assert.assertTrue(matcher.match(".h\u00E5.no")); // \u00E5 is <aring> Assert.assertTrue(matcher.matches(".h\u00E5.no")); // \u00E5 is <aring>
Assert.assertTrue(matcher.match(".xn--h-2fa.no")); Assert.assertTrue(matcher.matches(".xn--h-2fa.no"));
Assert.assertTrue(matcher.match(".h\u00E5.no")); Assert.assertTrue(matcher.matches(".h\u00E5.no"));
Assert.assertTrue(matcher.match(".xn--h-2fa.no")); Assert.assertTrue(matcher.matches(".xn--h-2fa.no"));
}
@Test
public void testWhitespace() throws Exception {
Assert.assertTrue(matcher.match(".xx"));
// yy appears after whitespace
Assert.assertFalse(matcher.match(".yy"));
// zz is commented
Assert.assertFalse(matcher.match(".zz"));
} }
} }

View File

@ -482,12 +482,24 @@ public class TestBasicCookieAttribHandlers {
cookie.setDomain("co.uk"); cookie.setDomain("co.uk");
Assert.assertFalse(h.match(cookie, new CookieOrigin("apache.co.uk", 80, "/stuff", false))); Assert.assertFalse(h.match(cookie, new CookieOrigin("apache.co.uk", 80, "/stuff", false)));
cookie.setDomain(".co.com");
Assert.assertTrue(h.match(cookie, new CookieOrigin("apache.co.com", 80, "/stuff", false)));
cookie.setDomain("co.com");
Assert.assertFalse(h.match(cookie, new CookieOrigin("apache.co.com", 80, "/stuff", false)));
cookie.setDomain(".com"); cookie.setDomain(".com");
Assert.assertFalse(h.match(cookie, new CookieOrigin("apache.com", 80, "/stuff", false))); Assert.assertFalse(h.match(cookie, new CookieOrigin("apache.com", 80, "/stuff", false)));
cookie.setDomain("com"); cookie.setDomain("com");
Assert.assertFalse(h.match(cookie, new CookieOrigin("apache.com", 80, "/stuff", false))); Assert.assertFalse(h.match(cookie, new CookieOrigin("apache.com", 80, "/stuff", false)));
cookie.setDomain("apache.com");
Assert.assertTrue(h.match(cookie, new CookieOrigin("apache.com", 80, "/stuff", false)));
cookie.setDomain(".apache.com");
Assert.assertTrue(h.match(cookie, new CookieOrigin("www.apache.com", 80, "/stuff", false)));
cookie.setDomain("localhost"); cookie.setDomain("localhost");
Assert.assertTrue(h.match(cookie, new CookieOrigin("localhost", 80, "/stuff", false))); Assert.assertTrue(h.match(cookie, new CookieOrigin("localhost", 80, "/stuff", false)));
} }

View File

@ -95,19 +95,4 @@ public class TestPublicSuffixListParser {
Assert.assertFalse(filter.match(cookie, new CookieOrigin("apache.h\u00E5.no", 80, "/stuff", false))); Assert.assertFalse(filter.match(cookie, new CookieOrigin("apache.h\u00E5.no", 80, "/stuff", false)));
} }
@Test
public void testWhitespace() throws Exception {
final BasicClientCookie cookie = new BasicClientCookie("name", "value");
cookie.setDomain(".xx");
Assert.assertFalse(filter.match(cookie, new CookieOrigin("apache.xx", 80, "/stuff", false)));
// yy appears after whitespace
cookie.setDomain(".yy");
Assert.assertTrue(filter.match(cookie, new CookieOrigin("apache.yy", 80, "/stuff", false)));
// zz is commented
cookie.setDomain(".zz");
Assert.assertTrue(filter.match(cookie, new CookieOrigin("apache.zz", 80, "/stuff", false)));
}
} }