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 )