1from __future__ import annotations 
    2 
    3import typing as t 
    4from datetime import datetime 
    5from urllib.parse import parse_qsl 
    6 
    7from ..datastructures import Accept 
    8from ..datastructures import Authorization 
    9from ..datastructures import CharsetAccept 
    10from ..datastructures import ETags 
    11from ..datastructures import Headers 
    12from ..datastructures import HeaderSet 
    13from ..datastructures import IfRange 
    14from ..datastructures import ImmutableList 
    15from ..datastructures import ImmutableMultiDict 
    16from ..datastructures import LanguageAccept 
    17from ..datastructures import MIMEAccept 
    18from ..datastructures import MultiDict 
    19from ..datastructures import Range 
    20from ..datastructures import RequestCacheControl 
    21from ..http import parse_accept_header 
    22from ..http import parse_cache_control_header 
    23from ..http import parse_date 
    24from ..http import parse_etags 
    25from ..http import parse_if_range_header 
    26from ..http import parse_list_header 
    27from ..http import parse_options_header 
    28from ..http import parse_range_header 
    29from ..http import parse_set_header 
    30from ..user_agent import UserAgent 
    31from ..utils import cached_property 
    32from ..utils import header_property 
    33from .http import parse_cookie 
    34from .utils import get_content_length 
    35from .utils import get_current_url 
    36from .utils import get_host 
    37 
    38 
    39class Request: 
    40    """Represents the non-IO parts of a HTTP request, including the 
    41    method, URL info, and headers. 
    42 
    43    This class is not meant for general use. It should only be used when 
    44    implementing WSGI, ASGI, or another HTTP application spec. Werkzeug 
    45    provides a WSGI implementation at :cls:`werkzeug.wrappers.Request`. 
    46 
    47    :param method: The method the request was made with, such as 
    48        ``GET``. 
    49    :param scheme: The URL scheme of the protocol the request used, such 
    50        as ``https`` or ``wss``. 
    51    :param server: The address of the server. ``(host, port)``, 
    52        ``(path, None)`` for unix sockets, or ``None`` if not known. 
    53    :param root_path: The prefix that the application is mounted under. 
    54        This is prepended to generated URLs, but is not part of route 
    55        matching. 
    56    :param path: The path part of the URL after ``root_path``. 
    57    :param query_string: The part of the URL after the "?". 
    58    :param headers: The headers received with the request. 
    59    :param remote_addr: The address of the client sending the request. 
    60 
    61    .. versionchanged:: 3.0 
    62        The ``charset``, ``url_charset``, and ``encoding_errors`` attributes 
    63        were removed. 
    64 
    65    .. versionadded:: 2.0 
    66    """ 
    67 
    68    #: the class to use for `args` and `form`.  The default is an 
    69    #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports 
    70    #: multiple values per key.  alternatively it makes sense to use an 
    71    #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which 
    72    #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict` 
    73    #: which is the fastest but only remembers the last key.  It is also 
    74    #: possible to use mutable structures, but this is not recommended. 
    75    #: 
    76    #: .. versionadded:: 0.6 
    77    parameter_storage_class: type[MultiDict[str, t.Any]] = ImmutableMultiDict 
    78 
    79    #: The type to be used for dict values from the incoming WSGI 
    80    #: environment. (For example for :attr:`cookies`.) By default an 
    81    #: :class:`~werkzeug.datastructures.ImmutableMultiDict` is used. 
    82    #: 
    83    #: .. versionchanged:: 1.0.0 
    84    #:     Changed to ``ImmutableMultiDict`` to support multiple values. 
    85    #: 
    86    #: .. versionadded:: 0.6 
    87    dict_storage_class: type[MultiDict[str, t.Any]] = ImmutableMultiDict 
    88 
    89    #: the type to be used for list values from the incoming WSGI environment. 
    90    #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used 
    91    #: (for example for :attr:`access_list`). 
    92    #: 
    93    #: .. versionadded:: 0.6 
    94    list_storage_class: type[list[t.Any]] = ImmutableList 
    95 
    96    user_agent_class: type[UserAgent] = UserAgent 
    97    """The class used and returned by the :attr:`user_agent` property to 
    98    parse the header. Defaults to 
    99    :class:`~werkzeug.user_agent.UserAgent`, which does no parsing. An 
    100    extension can provide a subclass that uses a parser to provide other 
    101    data. 
    102 
    103    .. versionadded:: 2.0 
    104    """ 
    105 
    106    #: Valid host names when handling requests. By default all hosts are 
    107    #: trusted, which means that whatever the client says the host is 
    108    #: will be accepted. 
    109    #: 
    110    #: Because ``Host`` and ``X-Forwarded-Host`` headers can be set to 
    111    #: any value by a malicious client, it is recommended to either set 
    112    #: this property or implement similar validation in the proxy (if 
    113    #: the application is being run behind one). 
    114    #: 
    115    #: .. versionadded:: 0.9 
    116    trusted_hosts: list[str] | None = None 
    117 
    118    def __init__( 
    119        self, 
    120        method: str, 
    121        scheme: str, 
    122        server: tuple[str, int | None] | None, 
    123        root_path: str, 
    124        path: str, 
    125        query_string: bytes, 
    126        headers: Headers, 
    127        remote_addr: str | None, 
    128    ) -> None: 
    129        #: The method the request was made with, such as ``GET``. 
    130        self.method = method.upper() 
    131        #: The URL scheme of the protocol the request used, such as 
    132        #: ``https`` or ``wss``. 
    133        self.scheme = scheme 
    134        #: The address of the server. ``(host, port)``, ``(path, None)`` 
    135        #: for unix sockets, or ``None`` if not known. 
    136        self.server = server 
    137        #: The prefix that the application is mounted under, without a 
    138        #: trailing slash. :attr:`path` comes after this. 
    139        self.root_path = root_path.rstrip("/") 
    140        #: The path part of the URL after :attr:`root_path`. This is the 
    141        #: path used for routing within the application. 
    142        self.path = "/" + path.lstrip("/") 
    143        #: The part of the URL after the "?". This is the raw value, use 
    144        #: :attr:`args` for the parsed values. 
    145        self.query_string = query_string 
    146        #: The headers received with the request. 
    147        self.headers = headers 
    148        #: The address of the client sending the request. 
    149        self.remote_addr = remote_addr 
    150 
    151    def __repr__(self) -> str: 
    152        try: 
    153            url = self.url 
    154        except Exception as e: 
    155            url = f"(invalid URL: {e})" 
    156 
    157        return f"<{type(self).__name__} {url!r} [{self.method}]>" 
    158 
    159    @cached_property 
    160    def args(self) -> MultiDict[str, str]: 
    161        """The parsed URL parameters (the part in the URL after the question 
    162        mark). 
    163 
    164        By default an 
    165        :class:`~werkzeug.datastructures.ImmutableMultiDict` 
    166        is returned from this function.  This can be changed by setting 
    167        :attr:`parameter_storage_class` to a different type.  This might 
    168        be necessary if the order of the form data is important. 
    169 
    170        .. versionchanged:: 2.3 
    171            Invalid bytes remain percent encoded. 
    172        """ 
    173        return self.parameter_storage_class( 
    174            parse_qsl( 
    175                self.query_string.decode(), 
    176                keep_blank_values=True, 
    177                errors="werkzeug.url_quote", 
    178            ) 
    179        ) 
    180 
    181    @cached_property 
    182    def access_route(self) -> list[str]: 
    183        """If a forwarded header exists this is a list of all ip addresses 
    184        from the client ip to the last proxy server. 
    185        """ 
    186        if "X-Forwarded-For" in self.headers: 
    187            return self.list_storage_class( 
    188                parse_list_header(self.headers["X-Forwarded-For"]) 
    189            ) 
    190        elif self.remote_addr is not None: 
    191            return self.list_storage_class([self.remote_addr]) 
    192        return self.list_storage_class() 
    193 
    194    @cached_property 
    195    def full_path(self) -> str: 
    196        """Requested path, including the query string.""" 
    197        return f"{self.path}?{self.query_string.decode()}" 
    198 
    199    @property 
    200    def is_secure(self) -> bool: 
    201        """``True`` if the request was made with a secure protocol 
    202        (HTTPS or WSS). 
    203        """ 
    204        return self.scheme in {"https", "wss"} 
    205 
    206    @cached_property 
    207    def url(self) -> str: 
    208        """The full request URL with the scheme, host, root path, path, 
    209        and query string.""" 
    210        return get_current_url( 
    211            self.scheme, self.host, self.root_path, self.path, self.query_string 
    212        ) 
    213 
    214    @cached_property 
    215    def base_url(self) -> str: 
    216        """Like :attr:`url` but without the query string.""" 
    217        return get_current_url(self.scheme, self.host, self.root_path, self.path) 
    218 
    219    @cached_property 
    220    def root_url(self) -> str: 
    221        """The request URL scheme, host, and root path. This is the root 
    222        that the application is accessed from. 
    223        """ 
    224        return get_current_url(self.scheme, self.host, self.root_path) 
    225 
    226    @cached_property 
    227    def host_url(self) -> str: 
    228        """The request URL scheme and host only.""" 
    229        return get_current_url(self.scheme, self.host) 
    230 
    231    @cached_property 
    232    def host(self) -> str: 
    233        """The host name the request was made to, including the port if 
    234        it's non-standard. Validated with :attr:`trusted_hosts`. 
    235        """ 
    236        return get_host( 
    237            self.scheme, self.headers.get("host"), self.server, self.trusted_hosts 
    238        ) 
    239 
    240    @cached_property 
    241    def cookies(self) -> ImmutableMultiDict[str, str]: 
    242        """A :class:`dict` with the contents of all cookies transmitted with 
    243        the request.""" 
    244        wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie")) 
    245        return parse_cookie(  # type: ignore 
    246            wsgi_combined_cookie, cls=self.dict_storage_class 
    247        ) 
    248 
    249    # Common Descriptors 
    250 
    251    content_type = header_property[str]( 
    252        "Content-Type", 
    253        doc="""The Content-Type entity-header field indicates the media 
    254        type of the entity-body sent to the recipient or, in the case of 
    255        the HEAD method, the media type that would have been sent had 
    256        the request been a GET.""", 
    257        read_only=True, 
    258    ) 
    259 
    260    @cached_property 
    261    def content_length(self) -> int | None: 
    262        """The Content-Length entity-header field indicates the size of the 
    263        entity-body in bytes or, in the case of the HEAD method, the size of 
    264        the entity-body that would have been sent had the request been a 
    265        GET. 
    266        """ 
    267        return get_content_length( 
    268            http_content_length=self.headers.get("Content-Length"), 
    269            http_transfer_encoding=self.headers.get("Transfer-Encoding"), 
    270        ) 
    271 
    272    content_encoding = header_property[str]( 
    273        "Content-Encoding", 
    274        doc="""The Content-Encoding entity-header field is used as a 
    275        modifier to the media-type. When present, its value indicates 
    276        what additional content codings have been applied to the 
    277        entity-body, and thus what decoding mechanisms must be applied 
    278        in order to obtain the media-type referenced by the Content-Type 
    279        header field. 
    280 
    281        .. versionadded:: 0.9""", 
    282        read_only=True, 
    283    ) 
    284    content_md5 = header_property[str]( 
    285        "Content-MD5", 
    286        doc="""The Content-MD5 entity-header field, as defined in 
    287        RFC 1864, is an MD5 digest of the entity-body for the purpose of 
    288        providing an end-to-end message integrity check (MIC) of the 
    289        entity-body. (Note: a MIC is good for detecting accidental 
    290        modification of the entity-body in transit, but is not proof 
    291        against malicious attacks.) 
    292 
    293        .. versionadded:: 0.9""", 
    294        read_only=True, 
    295    ) 
    296    referrer = header_property[str]( 
    297        "Referer", 
    298        doc="""The Referer[sic] request-header field allows the client 
    299        to specify, for the server's benefit, the address (URI) of the 
    300        resource from which the Request-URI was obtained (the 
    301        "referrer", although the header field is misspelled).""", 
    302        read_only=True, 
    303    ) 
    304    date = header_property( 
    305        "Date", 
    306        None, 
    307        parse_date, 
    308        doc="""The Date general-header field represents the date and 
    309        time at which the message was originated, having the same 
    310        semantics as orig-date in RFC 822. 
    311 
    312        .. versionchanged:: 2.0 
    313            The datetime object is timezone-aware. 
    314        """, 
    315        read_only=True, 
    316    ) 
    317    max_forwards = header_property( 
    318        "Max-Forwards", 
    319        None, 
    320        int, 
    321        doc="""The Max-Forwards request-header field provides a 
    322        mechanism with the TRACE and OPTIONS methods to limit the number 
    323        of proxies or gateways that can forward the request to the next 
    324        inbound server.""", 
    325        read_only=True, 
    326    ) 
    327 
    328    def _parse_content_type(self) -> None: 
    329        if not hasattr(self, "_parsed_content_type"): 
    330            self._parsed_content_type = parse_options_header( 
    331                self.headers.get("Content-Type", "") 
    332            ) 
    333 
    334    @property 
    335    def mimetype(self) -> str: 
    336        """Like :attr:`content_type`, but without parameters (eg, without 
    337        charset, type etc.) and always lowercase.  For example if the content 
    338        type is ``text/HTML; charset=utf-8`` the mimetype would be 
    339        ``'text/html'``. 
    340        """ 
    341        self._parse_content_type() 
    342        return self._parsed_content_type[0].lower() 
    343 
    344    @property 
    345    def mimetype_params(self) -> dict[str, str]: 
    346        """The mimetype parameters as dict.  For example if the content 
    347        type is ``text/html; charset=utf-8`` the params would be 
    348        ``{'charset': 'utf-8'}``. 
    349        """ 
    350        self._parse_content_type() 
    351        return self._parsed_content_type[1] 
    352 
    353    @cached_property 
    354    def pragma(self) -> HeaderSet: 
    355        """The Pragma general-header field is used to include 
    356        implementation-specific directives that might apply to any recipient 
    357        along the request/response chain.  All pragma directives specify 
    358        optional behavior from the viewpoint of the protocol; however, some 
    359        systems MAY require that behavior be consistent with the directives. 
    360        """ 
    361        return parse_set_header(self.headers.get("Pragma", "")) 
    362 
    363    # Accept 
    364 
    365    @cached_property 
    366    def accept_mimetypes(self) -> MIMEAccept: 
    367        """List of mimetypes this client supports as 
    368        :class:`~werkzeug.datastructures.MIMEAccept` object. 
    369        """ 
    370        return parse_accept_header(self.headers.get("Accept"), MIMEAccept) 
    371 
    372    @cached_property 
    373    def accept_charsets(self) -> CharsetAccept: 
    374        """List of charsets this client supports as 
    375        :class:`~werkzeug.datastructures.CharsetAccept` object. 
    376        """ 
    377        return parse_accept_header(self.headers.get("Accept-Charset"), CharsetAccept) 
    378 
    379    @cached_property 
    380    def accept_encodings(self) -> Accept: 
    381        """List of encodings this client accepts.  Encodings in a HTTP term 
    382        are compression encodings such as gzip.  For charsets have a look at 
    383        :attr:`accept_charset`. 
    384        """ 
    385        return parse_accept_header(self.headers.get("Accept-Encoding")) 
    386 
    387    @cached_property 
    388    def accept_languages(self) -> LanguageAccept: 
    389        """List of languages this client accepts as 
    390        :class:`~werkzeug.datastructures.LanguageAccept` object. 
    391 
    392        .. versionchanged 0.5 
    393           In previous versions this was a regular 
    394           :class:`~werkzeug.datastructures.Accept` object. 
    395        """ 
    396        return parse_accept_header(self.headers.get("Accept-Language"), LanguageAccept) 
    397 
    398    # ETag 
    399 
    400    @cached_property 
    401    def cache_control(self) -> RequestCacheControl: 
    402        """A :class:`~werkzeug.datastructures.RequestCacheControl` object 
    403        for the incoming cache control headers. 
    404        """ 
    405        cache_control = self.headers.get("Cache-Control") 
    406        return parse_cache_control_header(cache_control, None, RequestCacheControl) 
    407 
    408    @cached_property 
    409    def if_match(self) -> ETags: 
    410        """An object containing all the etags in the `If-Match` header. 
    411 
    412        :rtype: :class:`~werkzeug.datastructures.ETags` 
    413        """ 
    414        return parse_etags(self.headers.get("If-Match")) 
    415 
    416    @cached_property 
    417    def if_none_match(self) -> ETags: 
    418        """An object containing all the etags in the `If-None-Match` header. 
    419 
    420        :rtype: :class:`~werkzeug.datastructures.ETags` 
    421        """ 
    422        return parse_etags(self.headers.get("If-None-Match")) 
    423 
    424    @cached_property 
    425    def if_modified_since(self) -> datetime | None: 
    426        """The parsed `If-Modified-Since` header as a datetime object. 
    427 
    428        .. versionchanged:: 2.0 
    429            The datetime object is timezone-aware. 
    430        """ 
    431        return parse_date(self.headers.get("If-Modified-Since")) 
    432 
    433    @cached_property 
    434    def if_unmodified_since(self) -> datetime | None: 
    435        """The parsed `If-Unmodified-Since` header as a datetime object. 
    436 
    437        .. versionchanged:: 2.0 
    438            The datetime object is timezone-aware. 
    439        """ 
    440        return parse_date(self.headers.get("If-Unmodified-Since")) 
    441 
    442    @cached_property 
    443    def if_range(self) -> IfRange: 
    444        """The parsed ``If-Range`` header. 
    445 
    446        .. versionchanged:: 2.0 
    447            ``IfRange.date`` is timezone-aware. 
    448 
    449        .. versionadded:: 0.7 
    450        """ 
    451        return parse_if_range_header(self.headers.get("If-Range")) 
    452 
    453    @cached_property 
    454    def range(self) -> Range | None: 
    455        """The parsed `Range` header. 
    456 
    457        .. versionadded:: 0.7 
    458 
    459        :rtype: :class:`~werkzeug.datastructures.Range` 
    460        """ 
    461        return parse_range_header(self.headers.get("Range")) 
    462 
    463    # User Agent 
    464 
    465    @cached_property 
    466    def user_agent(self) -> UserAgent: 
    467        """The user agent. Use ``user_agent.string`` to get the header 
    468        value. Set :attr:`user_agent_class` to a subclass of 
    469        :class:`~werkzeug.user_agent.UserAgent` to provide parsing for 
    470        the other properties or other extended data. 
    471 
    472        .. versionchanged:: 2.1 
    473            The built-in parser was removed. Set ``user_agent_class`` to a ``UserAgent`` 
    474            subclass to parse data from the string. 
    475        """ 
    476        return self.user_agent_class(self.headers.get("User-Agent", "")) 
    477 
    478    # Authorization 
    479 
    480    @cached_property 
    481    def authorization(self) -> Authorization | None: 
    482        """The ``Authorization`` header parsed into an :class:`.Authorization` object. 
    483        ``None`` if the header is not present. 
    484 
    485        .. versionchanged:: 2.3 
    486            :class:`Authorization` is no longer a ``dict``. The ``token`` attribute 
    487            was added for auth schemes that use a token instead of parameters. 
    488        """ 
    489        return Authorization.from_header(self.headers.get("Authorization")) 
    490 
    491    # CORS 
    492 
    493    origin = header_property[str]( 
    494        "Origin", 
    495        doc=( 
    496            "The host that the request originated from. Set" 
    497            " :attr:`~CORSResponseMixin.access_control_allow_origin` on" 
    498            " the response to indicate which origins are allowed." 
    499        ), 
    500        read_only=True, 
    501    ) 
    502 
    503    access_control_request_headers = header_property( 
    504        "Access-Control-Request-Headers", 
    505        load_func=parse_set_header, 
    506        doc=( 
    507            "Sent with a preflight request to indicate which headers" 
    508            " will be sent with the cross origin request. Set" 
    509            " :attr:`~CORSResponseMixin.access_control_allow_headers`" 
    510            " on the response to indicate which headers are allowed." 
    511        ), 
    512        read_only=True, 
    513    ) 
    514 
    515    access_control_request_method = header_property[str]( 
    516        "Access-Control-Request-Method", 
    517        doc=( 
    518            "Sent with a preflight request to indicate which method" 
    519            " will be used for the cross origin request. Set" 
    520            " :attr:`~CORSResponseMixin.access_control_allow_methods`" 
    521            " on the response to indicate which methods are allowed." 
    522        ), 
    523        read_only=True, 
    524    ) 
    525 
    526    @property 
    527    def is_json(self) -> bool: 
    528        """Check if the mimetype indicates JSON data, either 
    529        :mimetype:`application/json` or :mimetype:`application/*+json`. 
    530        """ 
    531        mt = self.mimetype 
    532        return ( 
    533            mt == "application/json" 
    534            or mt.startswith("application/") 
    535            and mt.endswith("+json") 
    536        )