1,856 bytes · the Python implementation · view raw
import re
from .net_hostname_validate_types import HostnameValidation
ALLOWED = re.compile(r"[A-Za-z0-9.-]*")
DIGITS = re.compile(r"[0-9]+")
def validate_hostname(text: str) -> HostnameValidation:
"""Check a host name against the RFC 1123 / RFC 952 rules and normalise it: lower-case, one trailing dot (a fully qualified name) removed. Checks run in a fixed order and the first failure is the reason: empty, characters, total length, then each label in turn, then the top-level label. """ifnot isinstance(text, str):
return HostnameValidation(valid=False, hostname=None, reason="not text", labels=0)
name = text[:-1] if text.endswith(".") else text
labels = [] if len(name) == 0else name.split(".")
def fail(reason: str) -> HostnameValidation:
return HostnameValidation(valid=False, hostname=None, reason=reason, labels=len(labels))
if len(name) == 0:
return fail("empty")
# Characters first: once the text is known to be ASCII, every language# counts its length the same way.ifnot ALLOWED.fullmatch(name):
return fail("character other than a letter, digit, hyphen or dot")
if len(name) > 253:
return fail("longer than 253 characters")
for label in labels:
if len(label) == 0:
return fail("empty label")
if len(label) > 63:
return fail("label longer than 63 characters")
if label.startswith("-") or label.endswith("-"):
return fail("label starts or ends with a hyphen")
# RFC 3696 section 2: a top-level domain is never all-numeric, which keeps# 1.2.3.4 an address rather than a name.if DIGITS.fullmatch(labels[-1]):
return fail("top-level label is all numeric")
return HostnameValidation(valid=True, hostname=name.lower(), reason=None, labels=len(labels))