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