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