encode/httpx · error · InvalidURL
Invalid non-printable ASCII character in URL {key} component
Error message
Invalid non-printable ASCII character in URL {key} component, {char!r} at position {idx}. What it means
Component-level analogue of error 66: raised during the kwargs validation loop when any individual URL component value (e.g. host, path, query) contains a non-printable ASCII character. Protects against CRLF/control-char injection through individual components the same way the whole-URL check does.
Source
Thrown at httpx/_urlparse.py:282
# -------------------------------------------------------------
for key, value in kwargs.items():
if value is not None:
if len(value) > MAX_URL_LENGTH:
raise InvalidURL(f"URL component '{key}' too long")
# If a component includes any ASCII control characters including \t, \r, \n,
# then treat it as invalid.
if any(char.isascii() and not char.isprintable() for char in value):
char = next(
char for char in value if char.isascii() and not char.isprintable()
)
idx = value.find(char)
error = (
f"Invalid non-printable ASCII character in URL {key} component, "
f"{char!r} at position {idx}."
)
raise InvalidURL(error)
# Ensure that keyword arguments match as a valid regex.
if not COMPONENT_REGEX[key].fullmatch(value):
raise InvalidURL(f"Invalid URL component '{key}'")
# The URL_REGEX will always match, but may have empty components.
url_match = URL_REGEX.match(url)
assert url_match is not None
url_dict = url_match.groupdict()
# * 'scheme', 'authority', and 'path' may be empty strings.
# * 'query' may be 'None', indicating no trailing "?" portion.
# Any string including the empty string, indicates a trailing "?".
# * 'fragment' may be 'None', indicating no trailing "#" portion.
# Any string including the empty string, indicates a trailing "#".
scheme = kwargs.get("scheme", url_dict["scheme"]) or ""
authority = kwargs.get("authority", url_dict["authority"]) or ""
path = kwargs.get("path", url_dict["path"]) or ""View on GitHub (pinned to b5addb64f0)
Solutions
- Percent-encode the component value with urllib.parse.quote before passing it.
- Strip control characters: value = ''.join(c for c in value if c.isprintable()).
- Reject inputs with control chars at the validation boundary.
- Prefer building the full URL string with safe concatenation rather than passing untrusted kwargs.
Example fix
// before url = httpx.URL(scheme="https", host=user_host) # user_host = "evil\r\n" // after from urllib.parse import quote url = httpx.URL(scheme="https", host=quote(user_host, safe=""))
Defensive patterns
Strategy: validation
Validate before calling
def clean_component(value: str) -> str:
return "".join(c for c in value if not (c.isascii() and not c.isprintable()))
url = httpx.URL(host=clean_component(user_host)) Type guard
def component_is_clean(value: str) -> bool:
return not any(c.isascii() and not c.isprintable() for c in value) Try / catch
from httpx import InvalidURL
try:
url = httpx.URL(host=raw_host)
except InvalidURL as e:
if "non-printable" in str(e):
from urllib.parse import quote
url = httpx.URL(host=quote(raw_host, safe=""))
else:
raise Prevention
- Percent-encode user-supplied components before passing them as kwargs.
- Reject any host/path with CR/LF at the input boundary.
- Prefer passing a full URL string and letting httpx parse, rather than risky kwargs.
When it happens
Trigger: httpx.URL(host=user_input) where user_input contains '\n'; passing a path kwarg with a tab character; query kwarg containing a raw carriage return.
Common situations: User-supplied hostname/path that contains a newline; reading components from a malformed source file; building host from a multi-line env var.
Related errors
- Invalid non-printable ASCII character in URL, {char!r} at po
- URL too long
- URL component '{key}' too long
- Invalid URL component '{key}'
- Invalid IPv4 address: {host!r}
AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04).
Data as JSON: /data/errors/80053e2d3c8c9dd0.json.
Report an issue: GitHub.