pypa/pip · error · IDNAError
Domain too long
Error message
Domain too long
What it means
Raised by uts46_remap() (core.py:475) as a defensive guard when the input domain is longer than _max_input_length (1024 characters) before the per-codepoint UTS #46 mapping loop. This bounds work on oversized input; the real 253/254-octet DNS limit is enforced separately in encode()/decode() via valid_string_length().
Source
Thrown at src/pip/_vendor/idna/core.py:475
Implements the mapping table from `UTS #46 §4
<https://www.unicode.org/reports/tr46/>`_: each character is kept,
replaced, or rejected based on its status (``V``, ``M``, ``D``, ``3``,
``I``). The result is returned in Normalisation Form C.
:param domain: The full domain name to remap.
:param std3_rules: If ``True``, apply the stricter STD3 ASCII rules
(status ``3`` codepoints raise instead of being kept or mapped).
:param transitional: If ``True``, use transitional processing (status
``D`` codepoints are mapped instead of kept). Transitional
processing has been removed from UTS #46 and this option is
retained only for backwards compatibility.
:returns: The remapped domain, in Normalisation Form C.
:raises InvalidCodepoint: If the domain contains a disallowed
codepoint under the chosen rules.
:raises IDNAError: If ``domain`` exceeds the defensive input length limit.
"""
if len(domain) > _max_input_length:
raise IDNAError("Domain too long")
from .uts46data import uts46_replacements, uts46_starts, uts46_statuses
output = ""
for pos, char in enumerate(domain):
code_point = ord(char)
i = code_point if code_point < 256 else bisect.bisect_right(uts46_starts, code_point) - 1
status = chr(uts46_statuses[i])
replacement: Optional[str] = uts46_replacements[i]
# UTS #46 §4: V is always valid, D is deviation (kept unless transitional),
# 3 is disallowed-STD3 (kept unmapped if std3_rules is off and no mapping).
keep_as_is = (
status == "V" or (status == "D" and not transitional) or (status == "3" and not std3_rules and replacement is None)
)
# M is mapped, 3-with-replacement and transitional D fall through to the
# same replacement output path.
use_replacement = replacement is not None and (View on GitHub (pinned to d7d0d0a394)
Solutions
- Extract the hostname component (e.g. via urllib.parse.urlsplit(s).hostname) before passing it to idna.
- Reject inputs longer than 254 characters at the trust boundary — no valid domain is longer.
- Cap input length to 1024 (or better, 254) and validate before calling uts46_remap/encode/decode.
Example fix
// before
idna.encode(long_url, uts46=True) # full URL > 1024 chars
// after
from urllib.parse import urlsplit
host = urlsplit(long_url).hostname or long_url
if len(host) > 253:
raise ValueError('hostname too long')
idna.encode(host, uts46=True) Defensive patterns
Strategy: validation
Validate before calling
def is_acceptable_domain_length(domain: str, limit=254) -> bool:
return isinstance(domain, str) and len(domain) <= limit
if not is_acceptable_domain_length(domain):
raise ValueError('domain too long for UTS #46 processing')
remapped = idna.uts46_remap(domain) Type guard
def is_bounded_str(s, limit=254) -> bool:
return isinstance(s, str) and len(s) <= limit Try / catch
import idna
try:
encoded = idna.encode(domain, uts46=True)
except idna.IDNAError as err:
if 'too long' in str(err):
raise ValueError('input exceeds domain length limits') from err
raise Prevention
- Extract the bare hostname (urlsplit(s).hostname) before passing to idna.
- Enforce a 254-char cap on domain inputs at the trust boundary.
- Never pass full URLs, tokens, or request lines into the UTS #46 / encode path.
When it happens
Trigger: Calling uts46_remap() directly, or calling encode()/decode() with uts46=True, on a domain string longer than 1024 characters — typically a non-domain string (URL, token, log line) routed into the mapping function.
Common situations: Passing a full URL instead of a bare hostname to idna, a proxy feeding the whole request line into the IDNA path, fuzz tests with unbounded lengths, or concatenated domains missing separators.
Related errors
- Codepoint {_unot(cp_value)} at position {pos + 1} of {label!
- Empty domain
- Empty label
- Codepoint {_unot(cp_value)} not allowed at position {pos + 1
- Malformed A-label, no Punycode eligible content found
AI-assisted analysis of pypa/pip@d7d0d0a394 (2026-08-04).
Data as JSON: /data/errors/3cd678ae356bc3b9.json.
Report an issue: GitHub.