1from __future__ import annotations
2
3import collections.abc as cabc
4import typing as t
5from datetime import datetime
6from urllib.parse import parse_qsl
7
8from .._header_property import header_property
9from .._internal import _plain_int
10from ..datastructures.accept import Accept
11from ..datastructures.accept import LanguageAccept
12from ..datastructures.accept import MIMEAccept
13from ..datastructures.auth import Authorization
14from ..datastructures.cache_control import RequestCacheControl
15from ..datastructures.etag import ETagSet
16from ..datastructures.headers import Headers
17from ..datastructures.range import IfRange
18from ..datastructures.range import Range
19from ..datastructures.set import HeaderSet
20from ..datastructures.structures import ImmutableMultiDict
21from ..http import parse_date
22from ..http import parse_list_header
23from ..http import parse_options_header
24from ..http import SecFetchDest
25from ..http import SecFetchMode
26from ..http import SecFetchSite
27from ..user_agent import _UserAgent
28from ..utils import cached_property
29from .http import parse_cookie
30from .utils import get_current_url
31from .utils import get_host
32
33
34class Request:
35 """Represents the non-IO parts of a HTTP request, including the
36 method, URL info, and headers.
37
38 This class is not meant for general use. It should only be used when
39 implementing WSGI, ASGI, or another HTTP application spec. Werkzeug
40 provides a WSGI implementation at :cls:`werkzeug.wrappers.Request`.
41
42 :param method: The method the request was made with, such as
43 ``GET``.
44 :param scheme: The URL scheme of the protocol the request used, such
45 as ``https`` or ``wss``.
46 :param server: The address of the server. ``(host, port)``,
47 ``(path, None)`` for unix sockets, or ``None`` if not known.
48 :param root_path: The prefix that the application is mounted under.
49 This is prepended to generated URLs, but is not part of route
50 matching.
51 :param path: The path part of the URL after ``root_path``.
52 :param query_string: The part of the URL after the "?".
53 :param headers: The headers received with the request.
54 :param remote_addr: The address of the client sending the request.
55
56 .. versionchanged:: 3.0
57 The ``charset``, ``url_charset``, and ``encoding_errors`` attributes
58 were removed.
59
60 .. versionadded:: 2.0
61 """
62
63 #: The class to use for :attr:`args`, :attr:`form`, and :attr:`files`.
64 #:
65 #: .. deprecated:: 3.2
66 #: Will be removed in Werkzeug 4.0. It will always be ``ImmutableMultiDict``.
67 #:
68 #: .. versionadded:: 0.6
69 parameter_storage_class: None = None
70
71 #: The class to use for parsed dict values, such as :attr:`cookies`.
72 #:
73 #: .. deprecated:: 3.2
74 #: Will be removed in Werkzeug 4.0. It will always be ``ImmutableMultiDict``.
75 #:
76 #: .. versionchanged:: 1.0.0
77 #: Changed to ``ImmutableMultiDict`` to support multiple values.
78 #:
79 #: .. versionadded:: 0.6
80 dict_storage_class: None = None
81
82 #: The class to use for parsed list values, such as :attr:`access_route`.
83 #:
84 #: .. deprecated:: 3.2
85 #: Will be removed in Werkzeug 4.0. It will always be ``Sequence``.
86 #:
87 #: .. versionadded:: 0.6
88 list_storage_class: None = None
89
90 user_agent_class: None = None
91 """The class used and returned by the :attr:`user_agent` property to
92 parse the header. Defaults to
93 :class:`~werkzeug.user_agent.UserAgent`, which does no parsing. An
94 extension can provide a subclass that uses a parser to provide other
95 data.
96
97 .. deprecated 3.2
98 Will be removed in Werkzeug 4.0. ``user_agent`` is a string and can be
99 parsed directly if needed.
100
101 .. versionadded:: 2.0
102 """
103
104 #: Valid host names when handling requests. By default all hosts are
105 #: trusted, which means that whatever the client says the host is
106 #: will be accepted.
107 #:
108 #: Because ``Host`` and ``X-Forwarded-Host`` headers can be set to
109 #: any value by a malicious client, it is recommended to either set
110 #: this property or implement similar validation in the proxy (if
111 #: the application is being run behind one).
112 #:
113 #: .. versionadded:: 0.9
114 trusted_hosts: list[str] | None = None
115
116 def __init__(
117 self,
118 method: str,
119 scheme: str,
120 server: tuple[str, int | None] | None,
121 root_path: str,
122 path: str,
123 query_string: bytes,
124 headers: Headers,
125 remote_addr: str | None,
126 ) -> None:
127 #: The method the request was made with, such as ``GET``.
128 self.method = method.upper()
129 #: The URL scheme of the protocol the request used, such as
130 #: ``https`` or ``wss``.
131 self.scheme = scheme
132 #: The address of the server. ``(host, port)``, ``(path, None)``
133 #: for unix sockets, or ``None`` if not known.
134 self.server = server
135 #: The prefix that the application is mounted under, without a
136 #: trailing slash. :attr:`path` comes after this.
137 self.root_path = root_path.rstrip("/")
138 #: The path part of the URL after :attr:`root_path`. This is the
139 #: path used for routing within the application.
140 self.path = "/" + path.lstrip("/")
141 #: The part of the URL after the "?". This is the raw value, use
142 #: :attr:`args` for the parsed values.
143 self.query_string = query_string
144 #: The headers received with the request.
145 self.headers = headers
146 #: The address of the client sending the request.
147 self.remote_addr = remote_addr
148
149 def __repr__(self) -> str:
150 try:
151 url = self.url
152 except Exception as e:
153 url = f"(invalid URL: {e})"
154
155 return f"<{type(self).__name__} {url!r} [{self.method}]>"
156
157 @cached_property
158 def args(self) -> ImmutableMultiDict[str, str]:
159 """The parsed URL query parameters (the ``?key=value&a=b`` part of a
160 URL) as an :class:`ImmutableMultiDict`.
161
162 .. versionchanged:: 2.3
163 Invalid bytes remain percent encoded.
164 """
165 items = parse_qsl(
166 self.query_string.decode(),
167 keep_blank_values=True,
168 errors="werkzeug.url_quote",
169 )
170
171 if self.parameter_storage_class is not None:
172 import warnings
173
174 warnings.warn(
175 "Setting 'Request.parameter_storage_class' is deprecated and will be"
176 " removed in Werkzeug 4.0. It will always be 'ImmutableMultiDict'.",
177 DeprecationWarning,
178 stacklevel=2,
179 )
180 return self.parameter_storage_class(items)
181
182 return ImmutableMultiDict(items)
183
184 @cached_property
185 def access_route(self) -> cabc.Sequence[str]:
186 """The route taken from the client to the application.
187
188 This is ``X-Forwarded-For`` if it is set. Remember to only trust the
189 last N values, where N is the number of servers setting this header in
190 front of the application.
191
192 Otherwise, this only contains :attr:`remote_addr`, or is empty.
193 """
194 if "X-Forwarded-For" in self.headers:
195 items = tuple(parse_list_header(self.headers["X-Forwarded-For"]))
196 elif self.remote_addr is not None:
197 items = (self.remote_addr,)
198 else:
199 items = ()
200
201 if self.list_storage_class is not None:
202 import warnings
203
204 warnings.warn(
205 "Setting 'Request.list_storage_class' is deprecated and will be"
206 " removed in Werkzeug 4.0. It will always be 'Sequence'.",
207 DeprecationWarning,
208 stacklevel=2,
209 )
210 return self.list_storage_class(items)
211
212 return items
213
214 @cached_property
215 def full_path(self) -> str:
216 """Requested path, including the query string."""
217 return f"{self.path}?{self.query_string.decode()}"
218
219 @cached_property
220 def is_secure(self) -> bool:
221 """``True`` if the request was made with a secure protocol
222 (HTTPS or WSS).
223 """
224 return self.scheme in {"https", "wss"}
225
226 @cached_property
227 def url(self) -> str:
228 """The full request URL with the scheme, host, root path, path,
229 and query string."""
230 return get_current_url(
231 self.scheme, self.host, self.root_path, self.path, self.query_string
232 )
233
234 @cached_property
235 def base_url(self) -> str:
236 """Like :attr:`url` but without the query string."""
237 return get_current_url(self.scheme, self.host, self.root_path, self.path)
238
239 @cached_property
240 def root_url(self) -> str:
241 """The request URL scheme, host, and root path. This is the root
242 that the application is accessed from.
243 """
244 return get_current_url(self.scheme, self.host, self.root_path)
245
246 @cached_property
247 def host_url(self) -> str:
248 """The request URL scheme and host only."""
249 return get_current_url(self.scheme, self.host)
250
251 @cached_property
252 def host(self) -> str:
253 """The host name the request was made to, including the port if
254 it's non-standard. Validated with :attr:`trusted_hosts`.
255
256 See :func:`.get_host` for a detailed explanation.
257 """
258 return get_host(
259 self.scheme, self.headers.get("Host"), self.server, self.trusted_hosts
260 )
261
262 @cached_property
263 def cookies(self) -> ImmutableMultiDict[str, str]:
264 """A :class:`dict` with the contents of all cookies transmitted with
265 the request."""
266 wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie"))
267 kwargs: dict[str, t.Any] = {}
268
269 if self.dict_storage_class is not None:
270 import warnings
271
272 warnings.warn(
273 "Setting 'Request.dict_storage_class' is deprecated and will be"
274 " removed in Werkzeug 4.0. It will always be 'ImmutableMultiDict'.",
275 DeprecationWarning,
276 stacklevel=2,
277 )
278 kwargs["cls"] = self.dict_storage_class
279
280 return parse_cookie(wsgi_combined_cookie, **kwargs)
281
282 # Common Descriptors
283
284 content_type = header_property[str | None](
285 "Content-Type",
286 read_only=True,
287 doc="""The ``Content-Type`` header. The type of data in the body, with
288 optional parameters for additional detail.
289
290 A ``str``, or ``None`` if not set.
291
292 :attr:`mimetype` and :attr:`mimetype_params` allow working with the two
293 parts of the value separately.
294 """,
295 )
296
297 @cached_property
298 def content_length(self) -> int | None:
299 """The ``Content-Length`` header. The size of the body in bytes.
300
301 An ``int``, or ``None`` if not set.
302 """
303 if not (
304 value := self.headers.get("Content-Length")
305 ) or "chunked" in HeaderSet.from_header(self.headers.get("Transfer-Encoding")):
306 return None
307
308 try:
309 return max(0, _plain_int(value))
310 except ValueError:
311 return 0
312
313 content_encoding = header_property[str | None](
314 "Content-Encoding",
315 read_only=True,
316 doc="""The ``Content-Encoding`` header. An additional encoding applied
317 to the body beyond the ``Content-Type``.
318
319 A ``str``, or ``None`` if not set.
320
321 .. versionadded:: 0.9
322 """,
323 )
324
325 @cached_property
326 def content_md5(self) -> str | None:
327 """The ``Content-MD5`` header. An MD5 digest of the body.
328
329 A ``str``, or ``None`` if not set.
330
331 .. deprecated:: 3.2
332 The header has not been used for a long time. Will be removed
333 in Werkzeug 4.0.
334
335 .. versionadded:: 0.9
336 """
337 import warnings
338
339 warnings.warn(
340 "The 'content_md5' attribute is deprecated and will be removed in"
341 " Werkzeug 4.0. The header has not been used for a long time.",
342 DeprecationWarning,
343 stacklevel=2,
344 )
345 return self.headers.get("Content-MD5")
346
347 referrer = header_property[str | None](
348 "Referer",
349 read_only=True,
350 doc="""The ``Referer`` [sic] header. The URL the client made the request
351 from.
352
353 A ``str``, or ``None`` if not set.
354 """,
355 )
356
357 date = header_property[datetime | None](
358 "Date",
359 load_func=parse_date,
360 read_only=True,
361 doc="""The ``Date`` header. When the client generated the request.
362
363 A :class:`~datetime.datetime`, or ``None`` if not set.
364
365 .. versionchanged:: 2.0
366 The datetime object is timezone-aware.
367 """,
368 )
369
370 max_forwards = header_property[int | None](
371 "Max-Forwards",
372 load_func=int,
373 read_only=True,
374 doc="""The ``Max-Forwards`` header. How many times to forward the
375 request for ``TRACE`` or ``OPTIONS`` methods.
376
377 An ``int``, or ``None`` if not set.
378 """,
379 )
380
381 _parsed_content_type: tuple[str, dict[str, str]] | None = None
382
383 def _parse_content_type(self) -> None:
384 if self._parsed_content_type is None:
385 self._parsed_content_type = parse_options_header(self.content_type)
386
387 @cached_property
388 def mimetype(self) -> str:
389 """The value from :attr:`content_type`, lowercase. For example,
390 ``text/HTML; charset=utf-8`` becomes ``text/html``.
391
392 Unlike :attr:`.Response.mimetype`, this will be ``""`` if not set, and
393 will be lowercase rather than the exact value.
394 """
395 self._parse_content_type()
396 # Unlike content_type, this will be "" if the header isn't present,
397 # instead of None. Checking ==/startswith/endswith is common for this,
398 # so it's more convenient than None and essentially as accurate.
399 return self._parsed_content_type[0].lower() # type: ignore[index]
400
401 @cached_property
402 def mimetype_params(self) -> cabc.Mapping[str, str]:
403 """The parameters from :attr:`content_type``. For example,
404 ``text/html; charset=utf-8`` becomes ``{"charset": "utf-8"}``.
405 """
406 self._parse_content_type()
407 return self._parsed_content_type[1] # type: ignore[index]
408
409 @cached_property
410 def pragma(self) -> HeaderSet:
411 """The ``Pragma`` header.
412
413 A :class:`.HeaderSet`, empty if not set.
414
415 .. deprecated:: 3.2
416 Use ``cache_control`` instead. Will be removed in Werkzeug 4.0.
417 """
418 import warnings
419
420 warnings.warn(
421 "The 'pragma' attribute is deprecated and will be removed in"
422 " Werkzeug 4.0. Use 'cache_control' instead.",
423 DeprecationWarning,
424 stacklevel=2,
425 )
426 return HeaderSet.from_header(self.headers.get("Pragma"))
427
428 # Accept
429
430 accept_mimetypes = header_property[MIMEAccept](
431 "Accept",
432 load_func=MIMEAccept.from_header,
433 read_only=True,
434 doc="""The ``Accept`` header. The client's preferences for the content
435 type of the response body.
436
437 A :class:`.MIMEAccept`, empty if not set.
438 """,
439 )
440
441 @cached_property
442 def accept_charsets(self) -> Accept:
443 """The ``Accept-Charset`` header. The client's preferences for the text
444 encoding of the response body.
445
446 An :class:`.Accept`, empty if not set.
447
448 .. deprecated:: 3.2
449 The header has not been used for a long time. Clients do not send
450 it. Assume UTF-8. Will be removed in Werkzeug 4.0.
451 """
452 import warnings
453
454 from ..datastructures.accept import _CharsetAccept
455
456 warnings.warn(
457 "The 'accept_charsets' attribute is deprecated and will be removed"
458 " in Werkzeug 4.0. The header is not sent by browsers, and UTF-8 is"
459 " assumed.",
460 DeprecationWarning,
461 stacklevel=2,
462 )
463 return _CharsetAccept.from_header(self.headers.get("Accept-Charset"))
464
465 accept_encodings = header_property[Accept](
466 "Accept-Encoding",
467 load_func=Accept.from_header,
468 read_only=True,
469 doc="""The ``Accept-Encoding`` header. The client's preferences for a
470 further encoding applied to the response body beyond its content type.
471
472 An :class:`.Accept`, empty if not set.
473 """,
474 )
475
476 accept_languages = header_property[LanguageAccept](
477 "Accept-Language",
478 load_func=LanguageAccept.from_header,
479 read_only=True,
480 doc="""The ``Accept-Language`` header. The client's preferences for the
481 natural language of the response body.
482
483 A :class:`.LanguageAccept`, empty if not set.
484
485 .. versionchanged 0.5
486 Returns ``LanguageAccept`` instead of ``Accept``.
487 """,
488 )
489
490 # ETag
491
492 cache_control = header_property[RequestCacheControl](
493 "Cache-Control",
494 load_func=RequestCacheControl.from_header,
495 read_only=True,
496 doc="""The ``Cache-Control`` header. Controls how the application should
497 cache the request.
498
499 A :class:`.RequestCacheControl`, empty if not set.
500 """,
501 )
502
503 if_match = header_property[ETagSet](
504 "If-Match",
505 load_func=ETagSet.from_header,
506 read_only=True,
507 doc="""The ``If-Match`` header. If the response's ETag is present in
508 this set, it returns ``412`` instead.
509
510 An :class:`.ETags`, empty if not set.
511 """,
512 )
513
514 if_none_match = header_property[ETagSet](
515 "If-None-Match",
516 load_func=ETagSet.from_header,
517 read_only=True,
518 doc="""The ``If-None-Match`` header. If the response's ETag is present
519 in this set, it returns ``304`` for ``GET`` requests, or ``412`` for
520 other requests.
521
522 An :class:`.ETags`, empty if not set.
523 """,
524 )
525
526 if_modified_since = header_property[datetime | None](
527 "If-Modified-Since",
528 load_func=parse_date,
529 read_only=True,
530 doc="""The ``If-Modified-Since`` header. If the response's modification
531 time is not more recent than this, it returns ``304`` instead.
532
533 A :class:`~datetime.datetime`, or ``None`` if not set.
534
535 .. versionchanged:: 2.0
536 The datetime object is timezone-aware.
537 """,
538 )
539
540 if_unmodified_since = header_property[datetime | None](
541 "If-Unmodified-Since",
542 load_func=parse_date,
543 read_only=True,
544 doc="""The ``If-Unmodified-Since`` header. If the response's
545 modification time is more recent than this, it returns ``412`` instead.
546
547 A :class:`~datetime.datetime`, or ``None`` if not set.
548
549 .. versionchanged:: 2.0
550 The datetime object is timezone-aware.
551 """,
552 )
553
554 if_range = header_property[IfRange](
555 "If-Range",
556 load_func=IfRange.from_header,
557 read_only=True,
558 doc="""The ``If-Range`` header. If the response does not satisfy the
559 condition, it ignores the ``Range`` header.
560
561 An :class:`.IfRange`, empty if not set.
562
563 .. versionchanged:: 3.2
564 A weak ETag is discarded.
565
566 .. versionchanged:: 2.0
567 ``IfRange.date`` is timezone-aware.
568
569 .. versionadded:: 0.7
570 """,
571 )
572
573 range = header_property[Range | None](
574 "Range",
575 load_func=Range.from_header,
576 read_only=True,
577 doc="""The ``Range`` header. Partial ranges to return instead of the
578 full representation.
579
580 A :class:`.Range`, or ``None`` if not set.
581
582 .. versionadded:: 0.7
583 """,
584 )
585
586 # User Agent
587
588 @cached_property
589 def user_agent(self) -> str | None:
590 """The ``User-Agent`` header. Identifies the client application to some
591 degree. There are libraries that can parse this value, but it is
592 generally a bad idea to change the response based on it.
593
594 A ``str``, or ``None`` if not set.
595
596 .. versionchanged:: 3.2
597 This is a string. ``UserAgent`` and ``user_agent_class`` are
598 deprecated. Parse this directly if needed.
599
600 .. versionchanged:: 2.1
601 The built-in parser was removed. Set ``user_agent_class`` to a ``UserAgent``
602 subclass to parse data from the string.
603 """
604 value = self.headers.get("User-Agent", "")
605
606 if self.user_agent_class is not None:
607 import warnings
608
609 warnings.warn(
610 "Setting 'Request.user_agent_class' is deprecated and will be"
611 " removed in Werkzeug 4.0. 'user_agent' is a string and can be"
612 " parsed directly if needed.",
613 DeprecationWarning,
614 stacklevel=2,
615 )
616 return self.user_agent_class(value)
617
618 return _UserAgent(value)
619
620 # Authorization
621
622 authorization = header_property[Authorization | None](
623 "Authorization",
624 load_func=Authorization.from_header,
625 read_only=True,
626 doc="""The ``Authorization`` header. Credentials used when accessing a
627 protected part of the application.
628
629 An :class:`.Authorization`, or ``None`` if not set.
630
631 .. versionchanged:: 2.3
632 The ``Authorization`` class is no longer a ``dict``. The ``token``
633 attribute was added for auth schemes that use a token instead of
634 parameters.
635 """,
636 )
637
638 # CORS
639
640 origin = header_property[str | None](
641 "Origin",
642 read_only=True,
643 doc="""The ``Origin`` header. The scheme, hostname, and port of the
644 location the client made the request from.
645
646 A ``str``, or ``None`` if not set.
647
648 Set :attr:`.Response.access_control_allow_origin` to indicate that this
649 origin is allowed.
650 """,
651 )
652
653 access_control_request_headers = header_property[HeaderSet](
654 "Access-Control-Request-Headers",
655 load_func=HeaderSet.from_header,
656 read_only=True,
657 doc="""The ``Access-Control-Request-Headers`` header. Sent in a
658 preflight request to indicate which headers will be sent in the
659 cross-origin request.
660
661 A :class:`.HeaderSet`, empty if not set.
662
663 Set :attr:`.Response.access_control_allow_headers` to indicate which
664 headers are allowed.
665 """,
666 )
667
668 access_control_request_method = header_property[str | None](
669 "Access-Control-Request-Method",
670 read_only=True,
671 doc="""The ``Access-Control-Request-Method`` header. Sent in a
672 preflight request to indicate which method will be used for the
673 cross-origin request.
674
675 A ``str``, or ``None`` if not set.
676
677 Set :attr:`.Response.access_control_allow_methods` to indicate which
678 methods are allowed.
679 """,
680 )
681
682 sec_fetch_site = header_property[SecFetchSite | None](
683 "Sec-Fetch-Site",
684 load_func=SecFetchSite,
685 read_only=True,
686 doc="""The ``Sec-Fetch-Site`` header. The relationship between the
687 client's current URL and the origin of the requested resource.
688
689 A member of :class:`.SecFetchSite`, or ``None`` if not set.
690
691 .. versionadded:: 3.2
692 """,
693 )
694
695 sec_fetch_mode = header_property[SecFetchMode | None](
696 "Sec-Fetch-Mode",
697 load_func=SecFetchMode,
698 read_only=True,
699 doc="""The ``Sec-Fetch-Mode`` header. Distinguishes between requests
700 originating from a user navigating between HTML pages, and requests to
701 load images and other resources.
702
703 A member of :class:`.SecFetchMode`, or ``None`` if not set.
704
705 .. versionadded:: 3.2
706 """,
707 )
708
709 sec_fetch_user = header_property[bool](
710 "Sec-Fetch-User",
711 default=False,
712 load_func=lambda value: value == "?1",
713 read_only=True,
714 doc="""The ``Sec-Fetch-User`` header. Whether a navigation request was
715 originated by the user.
716
717 A ``bool``, ``False`` if not set.
718
719 .. versionadded:: 3.2
720 """,
721 )
722
723 sec_fetch_dest = header_property[SecFetchDest | None](
724 "Sec-Fetch-Dest",
725 load_func=SecFetchDest,
726 read_only=True,
727 doc="""The ``Sec-Fetch-Dest`` header. How the response to the request
728 is expected to be used.
729
730 A member of :class:`.SecFetchDest`, or ``None`` if not set.
731
732 .. versionadded:: 3.2
733 """,
734 )
735
736 @property
737 def is_json(self) -> bool:
738 """Check if the mimetype indicates JSON data, either
739 :mimetype:`application/json` or :mimetype:`application/*+json`.
740 """
741 mt = self.mimetype
742 return (
743 mt == "application/json"
744 or mt.startswith("application/")
745 and mt.endswith("+json")
746 )