Source code for httk.core.validation.iris
"""Predicates for minimally validating IRI and URL syntax.
These predicates check only surface syntax; they neither resolve references nor
verify that a target exists. Callers raise their own domain-specific errors
based on the boolean result.
"""
from urllib.parse import urlsplit
_FORBIDDEN_IRI_ASCII = frozenset(' <>"{}|\\^`')
_HEXDIGITS = frozenset("0123456789abcdefABCDEF")
[docs]
def has_valid_percent_escapes(value: str) -> bool:
"""Return whether every ``%`` in *value* introduces a valid percent-escape.
Each ``%`` must be followed by exactly two hexadecimal digits, including
when the ``%`` sits at the end of the string, where the missing followers
make the escape invalid.
:param value: The candidate string.
:return: ``True`` if every ``%`` in *value* is followed by two hex digits.
"""
for index, character in enumerate(value):
if character == "%" and (
index + 2 >= len(value) or value[index + 1] not in _HEXDIGITS or value[index + 2] not in _HEXDIGITS
):
return False
return True
[docs]
def is_absolute_iri(value: str) -> bool:
"""Return whether *value* is a minimally well-formed absolute IRI.
The check rejects whitespace, control characters, the C1 range, lone
surrogates, the ASCII set ``<>"{}|\\^```, and malformed percent-escapes,
then requires a non-empty URL scheme.
:param value: The candidate IRI string.
:return: ``True`` if *value* is a minimally well-formed absolute IRI.
"""
for character in value:
codepoint = ord(character)
if (
character.isspace()
or codepoint < 32
or 0x7F <= codepoint <= 0x9F
or 0xD800 <= codepoint <= 0xDFFF
or (codepoint < 128 and character in _FORBIDDEN_IRI_ASCII)
):
return False
if not has_valid_percent_escapes(value):
return False
try:
return bool(urlsplit(value).scheme)
except ValueError:
return False
[docs]
def is_root_relative_url(value: str) -> bool:
"""Return whether *value* is a minimally well-formed root-relative URL.
A root-relative URL begins with a single ``/``, carries no scheme, network
location, or fragment, and its path component is valid IRI syntax.
:param value: The candidate URL string.
:return: ``True`` if *value* is a minimally well-formed root-relative URL.
"""
if not value.startswith("/") or value.startswith("//"):
return False
try:
parsed = urlsplit(value)
except ValueError:
return False
return (
not parsed.scheme
and not parsed.netloc
and not parsed.fragment
and is_absolute_iri(f"https://example.invalid{value}")
)
[docs]
def is_https_url(value: str, *, allow_query: bool = False) -> bool:
"""Return whether *value* is a minimally well-formed absolute HTTPS URL.
The value must be a well-formed absolute IRI whose scheme is exactly
``https``, with a non-empty host, no userinfo, and no fragment; any explicit
port must lie in ``1..65535``. A non-empty query is rejected unless
*allow_query* is ``True``.
:param value: The candidate URL string.
:param allow_query: Whether a non-empty query component is permitted.
:return: ``True`` if *value* is a minimally well-formed absolute HTTPS URL.
"""
if not is_absolute_iri(value):
return False
try:
parsed = urlsplit(value)
port = parsed.port
except ValueError:
return False
return (
parsed.scheme == "https"
and bool(parsed.hostname)
and parsed.username is None
and parsed.password is None
and not parsed.fragment
and (allow_query or not parsed.query)
and (port is None or 1 <= port <= 65_535)
)