{"id":"80053e2d3c8c9dd0","repo":"encode/httpx","slug":"invalid-non-printable-ascii-character-in-url-key","errorCode":null,"errorMessage":"Invalid non-printable ASCII character in URL {key} component, {char!r} at position {idx}.","messagePattern":"Invalid non-printable ASCII character in URL (.+?) component, (.+?) at position (.+?)\\.","errorType":"validation","errorClass":"InvalidURL","httpStatus":null,"severity":"error","filePath":"httpx/_urlparse.py","lineNumber":282,"sourceCode":"    # -------------------------------------------------------------\n\n    for key, value in kwargs.items():\n        if value is not None:\n            if len(value) > MAX_URL_LENGTH:\n                raise InvalidURL(f\"URL component '{key}' too long\")\n\n            # If a component includes any ASCII control characters including \\t, \\r, \\n,\n            # then treat it as invalid.\n            if any(char.isascii() and not char.isprintable() for char in value):\n                char = next(\n                    char for char in value if char.isascii() and not char.isprintable()\n                )\n                idx = value.find(char)\n                error = (\n                    f\"Invalid non-printable ASCII character in URL {key} component, \"\n                    f\"{char!r} at position {idx}.\"\n                )\n                raise InvalidURL(error)\n\n            # Ensure that keyword arguments match as a valid regex.\n            if not COMPONENT_REGEX[key].fullmatch(value):\n                raise InvalidURL(f\"Invalid URL component '{key}'\")\n\n    # The URL_REGEX will always match, but may have empty components.\n    url_match = URL_REGEX.match(url)\n    assert url_match is not None\n    url_dict = url_match.groupdict()\n\n    # * 'scheme', 'authority', and 'path' may be empty strings.\n    # * 'query' may be 'None', indicating no trailing \"?\" portion.\n    #   Any string including the empty string, indicates a trailing \"?\".\n    # * 'fragment' may be 'None', indicating no trailing \"#\" portion.\n    #   Any string including the empty string, indicates a trailing \"#\".\n    scheme = kwargs.get(\"scheme\", url_dict[\"scheme\"]) or \"\"\n    authority = kwargs.get(\"authority\", url_dict[\"authority\"]) or \"\"\n    path = kwargs.get(\"path\", url_dict[\"path\"]) or \"\"","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/encode/httpx/blob/b5addb64f0161ff6bfe94c124ef76f6a1fba5254/httpx/_urlparse.py#L264-L300","documentation":"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.","triggerScenarios":"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.","commonSituations":"User-supplied hostname/path that contains a newline; reading components from a malformed source file; building host from a multi-line env var.","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."],"exampleFix":"// before\nurl = httpx.URL(scheme=\"https\", host=user_host)  # user_host = \"evil\\r\\n\"\n\n// after\nfrom urllib.parse import quote\nurl = httpx.URL(scheme=\"https\", host=quote(user_host, safe=\"\"))","handlingStrategy":"validation","validationCode":"def clean_component(value: str) -> str:\n    return \"\".join(c for c in value if not (c.isascii() and not c.isprintable()))\n\nurl = httpx.URL(host=clean_component(user_host))","typeGuard":"def component_is_clean(value: str) -> bool:\n    return not any(c.isascii() and not c.isprintable() for c in value)","tryCatchPattern":"from httpx import InvalidURL\n\ntry:\n    url = httpx.URL(host=raw_host)\nexcept InvalidURL as e:\n    if \"non-printable\" in str(e):\n        from urllib.parse import quote\n        url = httpx.URL(host=quote(raw_host, safe=\"\"))\n    else:\n        raise","preventionTips":["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."],"tags":["url","security","injection","validation","sanitization"],"analyzedSha":"b5addb64f0161ff6bfe94c124ef76f6a1fba5254","analyzedAt":"2026-08-04T19:32:56.768Z","schemaVersion":2}