From 6534e5b2e5db7b4f001ecdb4fd186823ceb4d16f Mon Sep 17 00:00:00 2001 From: Rahul Ranjan Date: Tue, 20 Oct 2026 18:07:20 +0530 Subject: [PATCH] fix(domain): match IDN TLDs against IANA list in IDNA-encoded form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With consider_tld=True, the TLD was looked up in the IANA list as written. The list stores internationalized TLDs in their ASCII (IDNA-encoded) form, so domains like пример.рф were rejected even though their punycode form xn--e1afmkfd.xn--p1ai was accepted. Encode the TLD before the lookup (falling back to the raw value when IDNA encoding fails, so unencodable TLDs are still rejected exactly like before). hostname() and url() pass consider_tld through to domain(), so they pick up the fix as well. Fixes #522 Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com> --- src/validators/domain.py | 11 ++++++++++- tests/test_domain.py | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/validators/domain.py b/src/validators/domain.py index 1ce4a262..5a5481a2 100644 --- a/src/validators/domain.py +++ b/src/validators/domain.py @@ -76,7 +76,16 @@ def domain( if not value: return False - if consider_tld and not _IanaTLD.check(value.rstrip(".").rsplit(".", 1)[-1].upper()): + tld = value.rstrip(".").rsplit(".", 1)[-1].upper() + try: + # IANA stores internationalized TLDs in their IDNA-encoded form + # (e.g. "XN--P1AI"), so the lookup must use the encoded TLD too + tld = tld.encode("idna").decode("utf-8").upper() + except UnicodeError: + # An unencodable TLD cannot be in the IANA list; keep the raw + # value and let the lookup below reject it + pass + if consider_tld and not _IanaTLD.check(tld): return False try: diff --git a/tests/test_domain.py b/tests/test_domain.py index d606ad79..b727a39e 100644 --- a/tests/test_domain.py +++ b/tests/test_domain.py @@ -47,6 +47,10 @@ def test_returns_true_on_valid_domain(value: str, rfc_1034: bool, rfc_2782: bool ("example_.com", True, False, True), ("somerandomexample.xn--fiqs8s", True, False, False), ("somerandomexample.onion", True, False, False), + # internationalized TLDs are matched via their IDNA-encoded form + # ref: https://github.com/python-validators/validators/issues/522 + ("пример.рф", True, False, False), + ("пример.рф.", True, True, False), ], ) def test_returns_true_on_valid_top_level_domain( @@ -100,6 +104,8 @@ def test_returns_failed_validation_on_invalid_domain(value: str, rfc_1034: bool, ("_example.#13", True, False, True), ("example_.fo-ul", True, False, True), ("somerandomexample.xn-n-fiqs8s", True, False, False), + # non-IANA IDN TLDs must still be rejected after encoding + ("пример.испытание", True, False, False), ], ) def test_returns_failed_validation_invalid_top_level_domain(