Made an application for supporting sustainable local businesses in San Pancho.
Never really got completed, but it has some useful Svelte components for maps that we can reuse.
http://greenspots.dctrl.space
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
548 lines
20 KiB
548 lines
20 KiB
import typing as t |
|
from datetime import datetime |
|
|
|
from .._internal import _to_str |
|
from ..datastructures import Accept |
|
from ..datastructures import Authorization |
|
from ..datastructures import CharsetAccept |
|
from ..datastructures import ETags |
|
from ..datastructures import Headers |
|
from ..datastructures import HeaderSet |
|
from ..datastructures import IfRange |
|
from ..datastructures import ImmutableList |
|
from ..datastructures import ImmutableMultiDict |
|
from ..datastructures import LanguageAccept |
|
from ..datastructures import MIMEAccept |
|
from ..datastructures import MultiDict |
|
from ..datastructures import Range |
|
from ..datastructures import RequestCacheControl |
|
from ..http import parse_accept_header |
|
from ..http import parse_authorization_header |
|
from ..http import parse_cache_control_header |
|
from ..http import parse_cookie |
|
from ..http import parse_date |
|
from ..http import parse_etags |
|
from ..http import parse_if_range_header |
|
from ..http import parse_list_header |
|
from ..http import parse_options_header |
|
from ..http import parse_range_header |
|
from ..http import parse_set_header |
|
from ..urls import url_decode |
|
from ..user_agent import UserAgent |
|
from ..useragents import _UserAgent as _DeprecatedUserAgent |
|
from ..utils import cached_property |
|
from ..utils import header_property |
|
from .utils import get_current_url |
|
from .utils import get_host |
|
|
|
|
|
class Request: |
|
"""Represents the non-IO parts of a HTTP request, including the |
|
method, URL info, and headers. |
|
|
|
This class is not meant for general use. It should only be used when |
|
implementing WSGI, ASGI, or another HTTP application spec. Werkzeug |
|
provides a WSGI implementation at :cls:`werkzeug.wrappers.Request`. |
|
|
|
:param method: The method the request was made with, such as |
|
``GET``. |
|
:param scheme: The URL scheme of the protocol the request used, such |
|
as ``https`` or ``wss``. |
|
:param server: The address of the server. ``(host, port)``, |
|
``(path, None)`` for unix sockets, or ``None`` if not known. |
|
:param root_path: The prefix that the application is mounted under. |
|
This is prepended to generated URLs, but is not part of route |
|
matching. |
|
:param path: The path part of the URL after ``root_path``. |
|
:param query_string: The part of the URL after the "?". |
|
:param headers: The headers received with the request. |
|
:param remote_addr: The address of the client sending the request. |
|
|
|
.. versionadded:: 2.0 |
|
""" |
|
|
|
#: The charset used to decode most data in the request. |
|
charset = "utf-8" |
|
|
|
#: the error handling procedure for errors, defaults to 'replace' |
|
encoding_errors = "replace" |
|
|
|
#: the class to use for `args` and `form`. The default is an |
|
#: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports |
|
#: multiple values per key. alternatively it makes sense to use an |
|
#: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which |
|
#: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict` |
|
#: which is the fastest but only remembers the last key. It is also |
|
#: possible to use mutable structures, but this is not recommended. |
|
#: |
|
#: .. versionadded:: 0.6 |
|
parameter_storage_class: t.Type[MultiDict] = ImmutableMultiDict |
|
|
|
#: The type to be used for dict values from the incoming WSGI |
|
#: environment. (For example for :attr:`cookies`.) By default an |
|
#: :class:`~werkzeug.datastructures.ImmutableMultiDict` is used. |
|
#: |
|
#: .. versionchanged:: 1.0.0 |
|
#: Changed to ``ImmutableMultiDict`` to support multiple values. |
|
#: |
|
#: .. versionadded:: 0.6 |
|
dict_storage_class: t.Type[MultiDict] = ImmutableMultiDict |
|
|
|
#: the type to be used for list values from the incoming WSGI environment. |
|
#: By default an :class:`~werkzeug.datastructures.ImmutableList` is used |
|
#: (for example for :attr:`access_list`). |
|
#: |
|
#: .. versionadded:: 0.6 |
|
list_storage_class: t.Type[t.List] = ImmutableList |
|
|
|
user_agent_class: t.Type[UserAgent] = _DeprecatedUserAgent |
|
"""The class used and returned by the :attr:`user_agent` property to |
|
parse the header. Defaults to |
|
:class:`~werkzeug.user_agent.UserAgent`, which does no parsing. An |
|
extension can provide a subclass that uses a parser to provide other |
|
data. |
|
|
|
.. versionadded:: 2.0 |
|
""" |
|
|
|
#: Valid host names when handling requests. By default all hosts are |
|
#: trusted, which means that whatever the client says the host is |
|
#: will be accepted. |
|
#: |
|
#: Because ``Host`` and ``X-Forwarded-Host`` headers can be set to |
|
#: any value by a malicious client, it is recommended to either set |
|
#: this property or implement similar validation in the proxy (if |
|
#: the application is being run behind one). |
|
#: |
|
#: .. versionadded:: 0.9 |
|
trusted_hosts: t.Optional[t.List[str]] = None |
|
|
|
def __init__( |
|
self, |
|
method: str, |
|
scheme: str, |
|
server: t.Optional[t.Tuple[str, t.Optional[int]]], |
|
root_path: str, |
|
path: str, |
|
query_string: bytes, |
|
headers: Headers, |
|
remote_addr: t.Optional[str], |
|
) -> None: |
|
#: The method the request was made with, such as ``GET``. |
|
self.method = method.upper() |
|
#: The URL scheme of the protocol the request used, such as |
|
#: ``https`` or ``wss``. |
|
self.scheme = scheme |
|
#: The address of the server. ``(host, port)``, ``(path, None)`` |
|
#: for unix sockets, or ``None`` if not known. |
|
self.server = server |
|
#: The prefix that the application is mounted under, without a |
|
#: trailing slash. :attr:`path` comes after this. |
|
self.root_path = root_path.rstrip("/") |
|
#: The path part of the URL after :attr:`root_path`. This is the |
|
#: path used for routing within the application. |
|
self.path = "/" + path.lstrip("/") |
|
#: The part of the URL after the "?". This is the raw value, use |
|
#: :attr:`args` for the parsed values. |
|
self.query_string = query_string |
|
#: The headers received with the request. |
|
self.headers = headers |
|
#: The address of the client sending the request. |
|
self.remote_addr = remote_addr |
|
|
|
def __repr__(self) -> str: |
|
try: |
|
url = self.url |
|
except Exception as e: |
|
url = f"(invalid URL: {e})" |
|
|
|
return f"<{type(self).__name__} {url!r} [{self.method}]>" |
|
|
|
@property |
|
def url_charset(self) -> str: |
|
"""The charset that is assumed for URLs. Defaults to the value |
|
of :attr:`charset`. |
|
|
|
.. versionadded:: 0.6 |
|
""" |
|
return self.charset |
|
|
|
@cached_property |
|
def args(self) -> "MultiDict[str, str]": |
|
"""The parsed URL parameters (the part in the URL after the question |
|
mark). |
|
|
|
By default an |
|
:class:`~werkzeug.datastructures.ImmutableMultiDict` |
|
is returned from this function. This can be changed by setting |
|
:attr:`parameter_storage_class` to a different type. This might |
|
be necessary if the order of the form data is important. |
|
""" |
|
return url_decode( |
|
self.query_string, |
|
self.url_charset, |
|
errors=self.encoding_errors, |
|
cls=self.parameter_storage_class, |
|
) |
|
|
|
@cached_property |
|
def access_route(self) -> t.List[str]: |
|
"""If a forwarded header exists this is a list of all ip addresses |
|
from the client ip to the last proxy server. |
|
""" |
|
if "X-Forwarded-For" in self.headers: |
|
return self.list_storage_class( |
|
parse_list_header(self.headers["X-Forwarded-For"]) |
|
) |
|
elif self.remote_addr is not None: |
|
return self.list_storage_class([self.remote_addr]) |
|
return self.list_storage_class() |
|
|
|
@cached_property |
|
def full_path(self) -> str: |
|
"""Requested path, including the query string.""" |
|
return f"{self.path}?{_to_str(self.query_string, self.url_charset)}" |
|
|
|
@property |
|
def is_secure(self) -> bool: |
|
"""``True`` if the request was made with a secure protocol |
|
(HTTPS or WSS). |
|
""" |
|
return self.scheme in {"https", "wss"} |
|
|
|
@cached_property |
|
def url(self) -> str: |
|
"""The full request URL with the scheme, host, root path, path, |
|
and query string.""" |
|
return get_current_url( |
|
self.scheme, self.host, self.root_path, self.path, self.query_string |
|
) |
|
|
|
@cached_property |
|
def base_url(self) -> str: |
|
"""Like :attr:`url` but without the query string.""" |
|
return get_current_url(self.scheme, self.host, self.root_path, self.path) |
|
|
|
@cached_property |
|
def root_url(self) -> str: |
|
"""The request URL scheme, host, and root path. This is the root |
|
that the application is accessed from. |
|
""" |
|
return get_current_url(self.scheme, self.host, self.root_path) |
|
|
|
@cached_property |
|
def host_url(self) -> str: |
|
"""The request URL scheme and host only.""" |
|
return get_current_url(self.scheme, self.host) |
|
|
|
@cached_property |
|
def host(self) -> str: |
|
"""The host name the request was made to, including the port if |
|
it's non-standard. Validated with :attr:`trusted_hosts`. |
|
""" |
|
return get_host( |
|
self.scheme, self.headers.get("host"), self.server, self.trusted_hosts |
|
) |
|
|
|
@cached_property |
|
def cookies(self) -> "ImmutableMultiDict[str, str]": |
|
"""A :class:`dict` with the contents of all cookies transmitted with |
|
the request.""" |
|
wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie")) |
|
return parse_cookie( # type: ignore |
|
wsgi_combined_cookie, |
|
self.charset, |
|
self.encoding_errors, |
|
cls=self.dict_storage_class, |
|
) |
|
|
|
# Common Descriptors |
|
|
|
content_type = header_property[str]( |
|
"Content-Type", |
|
doc="""The Content-Type entity-header field indicates the media |
|
type of the entity-body sent to the recipient or, in the case of |
|
the HEAD method, the media type that would have been sent had |
|
the request been a GET.""", |
|
read_only=True, |
|
) |
|
|
|
@cached_property |
|
def content_length(self) -> t.Optional[int]: |
|
"""The Content-Length entity-header field indicates the size of the |
|
entity-body in bytes or, in the case of the HEAD method, the size of |
|
the entity-body that would have been sent had the request been a |
|
GET. |
|
""" |
|
if self.headers.get("Transfer-Encoding", "") == "chunked": |
|
return None |
|
|
|
content_length = self.headers.get("Content-Length") |
|
if content_length is not None: |
|
try: |
|
return max(0, int(content_length)) |
|
except (ValueError, TypeError): |
|
pass |
|
|
|
return None |
|
|
|
content_encoding = header_property[str]( |
|
"Content-Encoding", |
|
doc="""The Content-Encoding entity-header field is used as a |
|
modifier to the media-type. When present, its value indicates |
|
what additional content codings have been applied to the |
|
entity-body, and thus what decoding mechanisms must be applied |
|
in order to obtain the media-type referenced by the Content-Type |
|
header field. |
|
|
|
.. versionadded:: 0.9""", |
|
read_only=True, |
|
) |
|
content_md5 = header_property[str]( |
|
"Content-MD5", |
|
doc="""The Content-MD5 entity-header field, as defined in |
|
RFC 1864, is an MD5 digest of the entity-body for the purpose of |
|
providing an end-to-end message integrity check (MIC) of the |
|
entity-body. (Note: a MIC is good for detecting accidental |
|
modification of the entity-body in transit, but is not proof |
|
against malicious attacks.) |
|
|
|
.. versionadded:: 0.9""", |
|
read_only=True, |
|
) |
|
referrer = header_property[str]( |
|
"Referer", |
|
doc="""The Referer[sic] request-header field allows the client |
|
to specify, for the server's benefit, the address (URI) of the |
|
resource from which the Request-URI was obtained (the |
|
"referrer", although the header field is misspelled).""", |
|
read_only=True, |
|
) |
|
date = header_property( |
|
"Date", |
|
None, |
|
parse_date, |
|
doc="""The Date general-header field represents the date and |
|
time at which the message was originated, having the same |
|
semantics as orig-date in RFC 822. |
|
|
|
.. versionchanged:: 2.0 |
|
The datetime object is timezone-aware. |
|
""", |
|
read_only=True, |
|
) |
|
max_forwards = header_property( |
|
"Max-Forwards", |
|
None, |
|
int, |
|
doc="""The Max-Forwards request-header field provides a |
|
mechanism with the TRACE and OPTIONS methods to limit the number |
|
of proxies or gateways that can forward the request to the next |
|
inbound server.""", |
|
read_only=True, |
|
) |
|
|
|
def _parse_content_type(self) -> None: |
|
if not hasattr(self, "_parsed_content_type"): |
|
self._parsed_content_type = parse_options_header( |
|
self.headers.get("Content-Type", "") |
|
) |
|
|
|
@property |
|
def mimetype(self) -> str: |
|
"""Like :attr:`content_type`, but without parameters (eg, without |
|
charset, type etc.) and always lowercase. For example if the content |
|
type is ``text/HTML; charset=utf-8`` the mimetype would be |
|
``'text/html'``. |
|
""" |
|
self._parse_content_type() |
|
return self._parsed_content_type[0].lower() |
|
|
|
@property |
|
def mimetype_params(self) -> t.Dict[str, str]: |
|
"""The mimetype parameters as dict. For example if the content |
|
type is ``text/html; charset=utf-8`` the params would be |
|
``{'charset': 'utf-8'}``. |
|
""" |
|
self._parse_content_type() |
|
return self._parsed_content_type[1] |
|
|
|
@cached_property |
|
def pragma(self) -> HeaderSet: |
|
"""The Pragma general-header field is used to include |
|
implementation-specific directives that might apply to any recipient |
|
along the request/response chain. All pragma directives specify |
|
optional behavior from the viewpoint of the protocol; however, some |
|
systems MAY require that behavior be consistent with the directives. |
|
""" |
|
return parse_set_header(self.headers.get("Pragma", "")) |
|
|
|
# Accept |
|
|
|
@cached_property |
|
def accept_mimetypes(self) -> MIMEAccept: |
|
"""List of mimetypes this client supports as |
|
:class:`~werkzeug.datastructures.MIMEAccept` object. |
|
""" |
|
return parse_accept_header(self.headers.get("Accept"), MIMEAccept) |
|
|
|
@cached_property |
|
def accept_charsets(self) -> CharsetAccept: |
|
"""List of charsets this client supports as |
|
:class:`~werkzeug.datastructures.CharsetAccept` object. |
|
""" |
|
return parse_accept_header(self.headers.get("Accept-Charset"), CharsetAccept) |
|
|
|
@cached_property |
|
def accept_encodings(self) -> Accept: |
|
"""List of encodings this client accepts. Encodings in a HTTP term |
|
are compression encodings such as gzip. For charsets have a look at |
|
:attr:`accept_charset`. |
|
""" |
|
return parse_accept_header(self.headers.get("Accept-Encoding")) |
|
|
|
@cached_property |
|
def accept_languages(self) -> LanguageAccept: |
|
"""List of languages this client accepts as |
|
:class:`~werkzeug.datastructures.LanguageAccept` object. |
|
|
|
.. versionchanged 0.5 |
|
In previous versions this was a regular |
|
:class:`~werkzeug.datastructures.Accept` object. |
|
""" |
|
return parse_accept_header(self.headers.get("Accept-Language"), LanguageAccept) |
|
|
|
# ETag |
|
|
|
@cached_property |
|
def cache_control(self) -> RequestCacheControl: |
|
"""A :class:`~werkzeug.datastructures.RequestCacheControl` object |
|
for the incoming cache control headers. |
|
""" |
|
cache_control = self.headers.get("Cache-Control") |
|
return parse_cache_control_header(cache_control, None, RequestCacheControl) |
|
|
|
@cached_property |
|
def if_match(self) -> ETags: |
|
"""An object containing all the etags in the `If-Match` header. |
|
|
|
:rtype: :class:`~werkzeug.datastructures.ETags` |
|
""" |
|
return parse_etags(self.headers.get("If-Match")) |
|
|
|
@cached_property |
|
def if_none_match(self) -> ETags: |
|
"""An object containing all the etags in the `If-None-Match` header. |
|
|
|
:rtype: :class:`~werkzeug.datastructures.ETags` |
|
""" |
|
return parse_etags(self.headers.get("If-None-Match")) |
|
|
|
@cached_property |
|
def if_modified_since(self) -> t.Optional[datetime]: |
|
"""The parsed `If-Modified-Since` header as a datetime object. |
|
|
|
.. versionchanged:: 2.0 |
|
The datetime object is timezone-aware. |
|
""" |
|
return parse_date(self.headers.get("If-Modified-Since")) |
|
|
|
@cached_property |
|
def if_unmodified_since(self) -> t.Optional[datetime]: |
|
"""The parsed `If-Unmodified-Since` header as a datetime object. |
|
|
|
.. versionchanged:: 2.0 |
|
The datetime object is timezone-aware. |
|
""" |
|
return parse_date(self.headers.get("If-Unmodified-Since")) |
|
|
|
@cached_property |
|
def if_range(self) -> IfRange: |
|
"""The parsed ``If-Range`` header. |
|
|
|
.. versionchanged:: 2.0 |
|
``IfRange.date`` is timezone-aware. |
|
|
|
.. versionadded:: 0.7 |
|
""" |
|
return parse_if_range_header(self.headers.get("If-Range")) |
|
|
|
@cached_property |
|
def range(self) -> t.Optional[Range]: |
|
"""The parsed `Range` header. |
|
|
|
.. versionadded:: 0.7 |
|
|
|
:rtype: :class:`~werkzeug.datastructures.Range` |
|
""" |
|
return parse_range_header(self.headers.get("Range")) |
|
|
|
# User Agent |
|
|
|
@cached_property |
|
def user_agent(self) -> UserAgent: |
|
"""The user agent. Use ``user_agent.string`` to get the header |
|
value. Set :attr:`user_agent_class` to a subclass of |
|
:class:`~werkzeug.user_agent.UserAgent` to provide parsing for |
|
the other properties or other extended data. |
|
|
|
.. versionchanged:: 2.0 |
|
The built in parser is deprecated and will be removed in |
|
Werkzeug 2.1. A ``UserAgent`` subclass must be set to parse |
|
data from the string. |
|
""" |
|
return self.user_agent_class(self.headers.get("User-Agent", "")) |
|
|
|
# Authorization |
|
|
|
@cached_property |
|
def authorization(self) -> t.Optional[Authorization]: |
|
"""The `Authorization` object in parsed form.""" |
|
return parse_authorization_header(self.headers.get("Authorization")) |
|
|
|
# CORS |
|
|
|
origin = header_property[str]( |
|
"Origin", |
|
doc=( |
|
"The host that the request originated from. Set" |
|
" :attr:`~CORSResponseMixin.access_control_allow_origin` on" |
|
" the response to indicate which origins are allowed." |
|
), |
|
read_only=True, |
|
) |
|
|
|
access_control_request_headers = header_property( |
|
"Access-Control-Request-Headers", |
|
load_func=parse_set_header, |
|
doc=( |
|
"Sent with a preflight request to indicate which headers" |
|
" will be sent with the cross origin request. Set" |
|
" :attr:`~CORSResponseMixin.access_control_allow_headers`" |
|
" on the response to indicate which headers are allowed." |
|
), |
|
read_only=True, |
|
) |
|
|
|
access_control_request_method = header_property[str]( |
|
"Access-Control-Request-Method", |
|
doc=( |
|
"Sent with a preflight request to indicate which method" |
|
" will be used for the cross origin request. Set" |
|
" :attr:`~CORSResponseMixin.access_control_allow_methods`" |
|
" on the response to indicate which methods are allowed." |
|
), |
|
read_only=True, |
|
) |
|
|
|
@property |
|
def is_json(self) -> bool: |
|
"""Check if the mimetype indicates JSON data, either |
|
:mimetype:`application/json` or :mimetype:`application/*+json`. |
|
""" |
|
mt = self.mimetype |
|
return ( |
|
mt == "application/json" |
|
or mt.startswith("application/") |
|
and mt.endswith("+json") |
|
)
|
|
|