Skip to content

Commit 5c959b8

Browse files
committed
fix(domain): accept single-label TLDs rejected by multi-label-only regex
domain() and hostname() with rfc_1034=True silently rejected valid single-label domain names (ie, com, yu, ie.) because the regex used a + quantifier on the label group, requiring at least one dot. RFC 1034 s2.3.1 permits single-label names. Add a second match path for bare TLDs (2-63 alpha chars, optional trailing dot) after the existing multi-label path so that both cases are handled. Fixes #442
1 parent 70de324 commit 5c959b8

1 file changed

Lines changed: 22 additions & 2 deletions

File tree

src/validators/domain.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ def domain(
5151
>>> # Supports IDN domains as well::
5252
>>> domain('xn----gtbspbbmkef.xn--p1ai')
5353
True
54+
>>> # Bare TLDs are valid single-label domain names (RFC 1034 §2.3.1):
55+
>>> domain('ie')
56+
True
57+
>>> domain('ie.', rfc_1034=True)
58+
True
5459
5560
Args:
5661
value:
@@ -82,8 +87,13 @@ def domain(
8287
try:
8388
service_record = r"_" if rfc_2782 else ""
8489
trailing_dot = r"\.?$" if rfc_1034 else r"$"
90+
encoded = value.encode("idna").decode("utf-8")
91+
92+
if re.search(r"\s|__+", value):
93+
return False
8594

86-
return not re.search(r"\s|__+", value) and re.match(
95+
# Multi-label domain (e.g. example.com, defo.ie, yugoslavia.yu)
96+
if re.match(
8797
# First character of the domain
8898
rf"^(?:[a-z0-9{service_record}]"
8999
# Sub-domain
@@ -94,8 +104,18 @@ def domain(
94104
+ r"+[a-z0-9][a-z0-9-_]{0,61}"
95105
# Last character of the gTLD
96106
+ rf"[a-z]{trailing_dot}",
97-
value.encode("idna").decode("utf-8"),
107+
encoded,
98108
re.IGNORECASE,
109+
):
110+
return True
111+
112+
# Single-label domain / bare TLD (e.g. ie, com, yu).
113+
# RFC 1034 §2.3.1 permits a single label of 1-63 characters.
114+
# We require at least 2 alpha chars so pure-digit strings ("123")
115+
# are still rejected, matching the behaviour of the multi-label path.
116+
return bool(
117+
re.match(rf"^[a-z]{{2,63}}{trailing_dot}", encoded, re.IGNORECASE)
99118
)
119+
100120
except UnicodeError as err:
101121
raise UnicodeError(f"Unable to encode/decode {value}") from err

0 commit comments

Comments
 (0)