diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/psl/PublicSuffixMatcher.java b/httpclient5/src/main/java/org/apache/hc/client5/http/psl/PublicSuffixMatcher.java index 5a5f27812f..6007a08ba4 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/psl/PublicSuffixMatcher.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/psl/PublicSuffixMatcher.java @@ -249,7 +249,12 @@ public boolean verify(final String domain) { if (domain == null) { return false; } - return verifyInternal(domain.startsWith(".") ? domain.substring(1) : domain); + // Normalise here so that verifyInternal can assume its input is already lowercase and in + // Unicode form. The rules are held that way, so an ACE-encoded (xn--) or mixed-case public + // suffix has to be decoded first; otherwise it fails to match a rule and is mistaken for a + // registrable domain. + final String normalized = DnsUtils.normalizeUnicode(domain.startsWith(".") ? domain.substring(1) : domain); + return verifyInternal(normalized); } @Internal diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/psl/TestPublicSuffixMatcher.java b/httpclient5/src/test/java/org/apache/hc/client5/http/psl/TestPublicSuffixMatcher.java index 2dce2cbdd7..b0dd783881 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/psl/TestPublicSuffixMatcher.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/psl/TestPublicSuffixMatcher.java @@ -191,6 +191,18 @@ void testMatchUnicode() { Assertions.assertTrue(matcher.matches(".xn--h-2fa.no")); } + @Test + void testVerifyUnicode() { + // A public suffix must be rejected by verify() whether it is given in Unicode or in its + // ACE (xn--) form; matches() already recognises both, so verify() must agree. + Assertions.assertTrue(matcher.matches(".xn--h-2fa.no")); + Assertions.assertFalse(matcher.verify("hå.no")); // å is + Assertions.assertFalse(matcher.verify("xn--h-2fa.no")); + // A genuine registrable domain under the IDN suffix is still allowed, in either form. + Assertions.assertTrue(matcher.verify("foo.hå.no")); + Assertions.assertTrue(matcher.verify("foo.xn--h-2fa.no")); + } + private void checkPublicSuffix(final String input, final String expected) { Assertions.assertEquals(expected, pslMatcher.getDomainRoot(input)); }