From 5c05f43928bb2963b75b5d47af4e1de7eb58dfb0 Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Wed, 19 Aug 2026 23:24:02 +0530 Subject: [PATCH] decode punycode public suffixes in PublicSuffixMatcher.verify --- .../hc/client5/http/psl/PublicSuffixMatcher.java | 9 ++++++++- .../hc/client5/http/psl/TestPublicSuffixMatcher.java | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) 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..a5dc014818 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 @@ -254,7 +254,14 @@ public boolean verify(final String domain) { @Internal public boolean verifyInternal(final String domain) { - final DomainRootInfo domainRootInfo = resolveDomainRoot(domain, null); + // Match the normalisation performed by getDomainRoot: the rules are held lowercase and in + // Unicode form, so an ACE-encoded (xn--) or mixed-case public suffix has to be decoded here + // as well, otherwise it fails to match a rule and is mistaken for a registrable domain. + String normalized = DnsUtils.normalize(domain); + if (normalized != null && normalized.contains("xn-")) { + normalized = IDN.toUnicode(normalized); + } + final DomainRootInfo domainRootInfo = resolveDomainRoot(normalized, null); if (domainRootInfo == null) { return false; } 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)); }