1from __future__ import annotations 
    2 
    3import typing as t 
    4from urllib.parse import quote 
    5 
    6from .._internal import _plain_int 
    7from ..exceptions import SecurityError 
    8from ..urls import uri_to_iri 
    9 
    10 
    11def host_is_trusted(hostname: str | None, trusted_list: t.Iterable[str]) -> bool: 
    12    """Check if a host matches a list of trusted names. 
    13 
    14    :param hostname: The name to check. 
    15    :param trusted_list: A list of valid names to match. If a name 
    16        starts with a dot it will match all subdomains. 
    17 
    18    .. versionadded:: 0.9 
    19    """ 
    20    if not hostname: 
    21        return False 
    22 
    23    try: 
    24        hostname = hostname.partition(":")[0].encode("idna").decode("ascii") 
    25    except UnicodeEncodeError: 
    26        return False 
    27 
    28    if isinstance(trusted_list, str): 
    29        trusted_list = [trusted_list] 
    30 
    31    for ref in trusted_list: 
    32        if ref.startswith("."): 
    33            ref = ref[1:] 
    34            suffix_match = True 
    35        else: 
    36            suffix_match = False 
    37 
    38        try: 
    39            ref = ref.partition(":")[0].encode("idna").decode("ascii") 
    40        except UnicodeEncodeError: 
    41            return False 
    42 
    43        if ref == hostname or (suffix_match and hostname.endswith(f".{ref}")): 
    44            return True 
    45 
    46    return False 
    47 
    48 
    49def get_host( 
    50    scheme: str, 
    51    host_header: str | None, 
    52    server: tuple[str, int | None] | None = None, 
    53    trusted_hosts: t.Iterable[str] | None = None, 
    54) -> str: 
    55    """Return the host for the given parameters. 
    56 
    57    This first checks the ``host_header``. If it's not present, then 
    58    ``server`` is used. The host will only contain the port if it is 
    59    different than the standard port for the protocol. 
    60 
    61    Optionally, verify that the host is trusted using 
    62    :func:`host_is_trusted` and raise a 
    63    :exc:`~werkzeug.exceptions.SecurityError` if it is not. 
    64 
    65    :param scheme: The protocol the request used, like ``"https"``. 
    66    :param host_header: The ``Host`` header value. 
    67    :param server: Address of the server. ``(host, port)``, or 
    68        ``(path, None)`` for unix sockets. 
    69    :param trusted_hosts: A list of trusted host names. 
    70 
    71    :return: Host, with port if necessary. 
    72    :raise ~werkzeug.exceptions.SecurityError: If the host is not 
    73        trusted. 
    74 
    75    .. versionchanged:: 3.1.3 
    76        If ``SERVER_NAME`` is IPv6, it is wrapped in ``[]``. 
    77    """ 
    78    host = "" 
    79 
    80    if host_header is not None: 
    81        host = host_header 
    82    elif server is not None: 
    83        host = server[0] 
    84 
    85        # If SERVER_NAME is IPv6, wrap it in [] to match Host header. 
    86        # Check for : because domain or IPv4 can't have that. 
    87        if ":" in host and host[0] != "[": 
    88            host = f"[{host}]" 
    89 
    90        if server[1] is not None: 
    91            host = f"{host}:{server[1]}" 
    92 
    93    if scheme in {"http", "ws"} and host.endswith(":80"): 
    94        host = host[:-3] 
    95    elif scheme in {"https", "wss"} and host.endswith(":443"): 
    96        host = host[:-4] 
    97 
    98    if trusted_hosts is not None: 
    99        if not host_is_trusted(host, trusted_hosts): 
    100            raise SecurityError(f"Host {host!r} is not trusted.") 
    101 
    102    return host 
    103 
    104 
    105def get_current_url( 
    106    scheme: str, 
    107    host: str, 
    108    root_path: str | None = None, 
    109    path: str | None = None, 
    110    query_string: bytes | None = None, 
    111) -> str: 
    112    """Recreate the URL for a request. If an optional part isn't 
    113    provided, it and subsequent parts are not included in the URL. 
    114 
    115    The URL is an IRI, not a URI, so it may contain Unicode characters. 
    116    Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII. 
    117 
    118    :param scheme: The protocol the request used, like ``"https"``. 
    119    :param host: The host the request was made to. See :func:`get_host`. 
    120    :param root_path: Prefix that the application is mounted under. This 
    121        is prepended to ``path``. 
    122    :param path: The path part of the URL after ``root_path``. 
    123    :param query_string: The portion of the URL after the "?". 
    124    """ 
    125    url = [scheme, "://", host] 
    126 
    127    if root_path is None: 
    128        url.append("/") 
    129        return uri_to_iri("".join(url)) 
    130 
    131    # safe = https://url.spec.whatwg.org/#url-path-segment-string 
    132    # as well as percent for things that are already quoted 
    133    url.append(quote(root_path.rstrip("/"), safe="!$&'()*+,/:;=@%")) 
    134    url.append("/") 
    135 
    136    if path is None: 
    137        return uri_to_iri("".join(url)) 
    138 
    139    url.append(quote(path.lstrip("/"), safe="!$&'()*+,/:;=@%")) 
    140 
    141    if query_string: 
    142        url.append("?") 
    143        url.append(quote(query_string, safe="!$&'()*+,/:;=?@%")) 
    144 
    145    return uri_to_iri("".join(url)) 
    146 
    147 
    148def get_content_length( 
    149    http_content_length: str | None = None, 
    150    http_transfer_encoding: str | None = None, 
    151) -> int | None: 
    152    """Return the ``Content-Length`` header value as an int. If the header is not given 
    153    or the ``Transfer-Encoding`` header is ``chunked``, ``None`` is returned to indicate 
    154    a streaming request. If the value is not an integer, or negative, 0 is returned. 
    155 
    156    :param http_content_length: The Content-Length HTTP header. 
    157    :param http_transfer_encoding: The Transfer-Encoding HTTP header. 
    158 
    159    .. versionadded:: 2.2 
    160    """ 
    161    if http_transfer_encoding == "chunked" or http_content_length is None: 
    162        return None 
    163 
    164    try: 
    165        return max(0, _plain_int(http_content_length)) 
    166    except ValueError: 
    167        return 0