request.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. import typing as t
  2. from datetime import datetime
  3. from .._internal import _to_str
  4. from ..datastructures import Accept
  5. from ..datastructures import Authorization
  6. from ..datastructures import CharsetAccept
  7. from ..datastructures import ETags
  8. from ..datastructures import Headers
  9. from ..datastructures import HeaderSet
  10. from ..datastructures import IfRange
  11. from ..datastructures import ImmutableList
  12. from ..datastructures import ImmutableMultiDict
  13. from ..datastructures import LanguageAccept
  14. from ..datastructures import MIMEAccept
  15. from ..datastructures import MultiDict
  16. from ..datastructures import Range
  17. from ..datastructures import RequestCacheControl
  18. from ..http import parse_accept_header
  19. from ..http import parse_authorization_header
  20. from ..http import parse_cache_control_header
  21. from ..http import parse_cookie
  22. from ..http import parse_date
  23. from ..http import parse_etags
  24. from ..http import parse_if_range_header
  25. from ..http import parse_list_header
  26. from ..http import parse_options_header
  27. from ..http import parse_range_header
  28. from ..http import parse_set_header
  29. from ..urls import url_decode
  30. from ..user_agent import UserAgent
  31. from ..useragents import _UserAgent as _DeprecatedUserAgent
  32. from ..utils import cached_property
  33. from ..utils import header_property
  34. from .utils import get_current_url
  35. from .utils import get_host
  36. class Request:
  37. """Represents the non-IO parts of a HTTP request, including the
  38. method, URL info, and headers.
  39. This class is not meant for general use. It should only be used when
  40. implementing WSGI, ASGI, or another HTTP application spec. Werkzeug
  41. provides a WSGI implementation at :cls:`werkzeug.wrappers.Request`.
  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. .. versionadded:: 2.0
  56. """
  57. #: The charset used to decode most data in the request.
  58. charset = "utf-8"
  59. #: the error handling procedure for errors, defaults to 'replace'
  60. encoding_errors = "replace"
  61. #: the class to use for `args` and `form`. The default is an
  62. #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports
  63. #: multiple values per key. alternatively it makes sense to use an
  64. #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which
  65. #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict`
  66. #: which is the fastest but only remembers the last key. It is also
  67. #: possible to use mutable structures, but this is not recommended.
  68. #:
  69. #: .. versionadded:: 0.6
  70. parameter_storage_class: t.Type[MultiDict] = ImmutableMultiDict
  71. #: The type to be used for dict values from the incoming WSGI
  72. #: environment. (For example for :attr:`cookies`.) By default an
  73. #: :class:`~werkzeug.datastructures.ImmutableMultiDict` is used.
  74. #:
  75. #: .. versionchanged:: 1.0.0
  76. #: Changed to ``ImmutableMultiDict`` to support multiple values.
  77. #:
  78. #: .. versionadded:: 0.6
  79. dict_storage_class: t.Type[MultiDict] = ImmutableMultiDict
  80. #: the type to be used for list values from the incoming WSGI environment.
  81. #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used
  82. #: (for example for :attr:`access_list`).
  83. #:
  84. #: .. versionadded:: 0.6
  85. list_storage_class: t.Type[t.List] = ImmutableList
  86. user_agent_class: t.Type[UserAgent] = _DeprecatedUserAgent
  87. """The class used and returned by the :attr:`user_agent` property to
  88. parse the header. Defaults to
  89. :class:`~werkzeug.user_agent.UserAgent`, which does no parsing. An
  90. extension can provide a subclass that uses a parser to provide other
  91. data.
  92. .. versionadded:: 2.0
  93. """
  94. #: Valid host names when handling requests. By default all hosts are
  95. #: trusted, which means that whatever the client says the host is
  96. #: will be accepted.
  97. #:
  98. #: Because ``Host`` and ``X-Forwarded-Host`` headers can be set to
  99. #: any value by a malicious client, it is recommended to either set
  100. #: this property or implement similar validation in the proxy (if
  101. #: the application is being run behind one).
  102. #:
  103. #: .. versionadded:: 0.9
  104. trusted_hosts: t.Optional[t.List[str]] = None
  105. def __init__(
  106. self,
  107. method: str,
  108. scheme: str,
  109. server: t.Optional[t.Tuple[str, t.Optional[int]]],
  110. root_path: str,
  111. path: str,
  112. query_string: bytes,
  113. headers: Headers,
  114. remote_addr: t.Optional[str],
  115. ) -> None:
  116. #: The method the request was made with, such as ``GET``.
  117. self.method = method.upper()
  118. #: The URL scheme of the protocol the request used, such as
  119. #: ``https`` or ``wss``.
  120. self.scheme = scheme
  121. #: The address of the server. ``(host, port)``, ``(path, None)``
  122. #: for unix sockets, or ``None`` if not known.
  123. self.server = server
  124. #: The prefix that the application is mounted under, without a
  125. #: trailing slash. :attr:`path` comes after this.
  126. self.root_path = root_path.rstrip("/")
  127. #: The path part of the URL after :attr:`root_path`. This is the
  128. #: path used for routing within the application.
  129. self.path = "/" + path.lstrip("/")
  130. #: The part of the URL after the "?". This is the raw value, use
  131. #: :attr:`args` for the parsed values.
  132. self.query_string = query_string
  133. #: The headers received with the request.
  134. self.headers = headers
  135. #: The address of the client sending the request.
  136. self.remote_addr = remote_addr
  137. def __repr__(self) -> str:
  138. try:
  139. url = self.url
  140. except Exception as e:
  141. url = f"(invalid URL: {e})"
  142. return f"<{type(self).__name__} {url!r} [{self.method}]>"
  143. @property
  144. def url_charset(self) -> str:
  145. """The charset that is assumed for URLs. Defaults to the value
  146. of :attr:`charset`.
  147. .. versionadded:: 0.6
  148. """
  149. return self.charset
  150. @cached_property
  151. def args(self) -> "MultiDict[str, str]":
  152. """The parsed URL parameters (the part in the URL after the question
  153. mark).
  154. By default an
  155. :class:`~werkzeug.datastructures.ImmutableMultiDict`
  156. is returned from this function. This can be changed by setting
  157. :attr:`parameter_storage_class` to a different type. This might
  158. be necessary if the order of the form data is important.
  159. """
  160. return url_decode(
  161. self.query_string,
  162. self.url_charset,
  163. errors=self.encoding_errors,
  164. cls=self.parameter_storage_class,
  165. )
  166. @cached_property
  167. def access_route(self) -> t.List[str]:
  168. """If a forwarded header exists this is a list of all ip addresses
  169. from the client ip to the last proxy server.
  170. """
  171. if "X-Forwarded-For" in self.headers:
  172. return self.list_storage_class(
  173. parse_list_header(self.headers["X-Forwarded-For"])
  174. )
  175. elif self.remote_addr is not None:
  176. return self.list_storage_class([self.remote_addr])
  177. return self.list_storage_class()
  178. @cached_property
  179. def full_path(self) -> str:
  180. """Requested path, including the query string."""
  181. return f"{self.path}?{_to_str(self.query_string, self.url_charset)}"
  182. @property
  183. def is_xhr(self):
  184. """True if the request was triggered via a JavaScript XMLHttpRequest.
  185. This only works with libraries that support the ``X-Requested-With``
  186. header and set it to "XMLHttpRequest". Libraries that do that are
  187. prototype, jQuery and Mochikit and probably some more.
  188. .. deprecated:: 0.13
  189. ``X-Requested-With`` is not standard and is unreliable. You
  190. may be able to use :attr:`AcceptMixin.accept_mimetypes`
  191. instead.
  192. """
  193. import warnings
  194. warnings.warn(
  195. "'Request.is_xhr' is deprecated as of version 0.13 and will"
  196. " be removed in version 1.0. The 'X-Requested-With' header"
  197. " is not standard and is unreliable. You may be able to use"
  198. " 'accept_mimetypes' instead.",
  199. DeprecationWarning,
  200. stacklevel=2,
  201. )
  202. return self.environ.get("HTTP_X_REQUESTED_WITH", "").lower() == "xmlhttprequest"
  203. @property
  204. def is_secure(self) -> bool:
  205. """``True`` if the request was made with a secure protocol
  206. (HTTPS or WSS).
  207. """
  208. return self.scheme in {"https", "wss"}
  209. @cached_property
  210. def url(self) -> str:
  211. """The full request URL with the scheme, host, root path, path,
  212. and query string."""
  213. return get_current_url(
  214. self.scheme, self.host, self.root_path, self.path, self.query_string
  215. )
  216. @cached_property
  217. def base_url(self) -> str:
  218. """Like :attr:`url` but without the query string."""
  219. return get_current_url(self.scheme, self.host, self.root_path, self.path)
  220. @cached_property
  221. def root_url(self) -> str:
  222. """The request URL scheme, host, and root path. This is the root
  223. that the application is accessed from.
  224. """
  225. return get_current_url(self.scheme, self.host, self.root_path)
  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. @cached_property
  231. def host(self) -> str:
  232. """The host name the request was made to, including the port if
  233. it's non-standard. Validated with :attr:`trusted_hosts`.
  234. """
  235. return get_host(
  236. self.scheme, self.headers.get("host"), self.server, self.trusted_hosts
  237. )
  238. @cached_property
  239. def cookies(self) -> "ImmutableMultiDict[str, str]":
  240. """A :class:`dict` with the contents of all cookies transmitted with
  241. the request."""
  242. wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie"))
  243. return parse_cookie( # type: ignore
  244. wsgi_combined_cookie,
  245. self.charset,
  246. self.encoding_errors,
  247. cls=self.dict_storage_class,
  248. )
  249. # Common Descriptors
  250. content_type = header_property[str](
  251. "Content-Type",
  252. doc="""The Content-Type entity-header field indicates the media
  253. type of the entity-body sent to the recipient or, in the case of
  254. the HEAD method, the media type that would have been sent had
  255. the request been a GET.""",
  256. read_only=True,
  257. )
  258. @cached_property
  259. def content_length(self) -> t.Optional[int]:
  260. """The Content-Length entity-header field indicates the size of the
  261. entity-body in bytes or, in the case of the HEAD method, the size of
  262. the entity-body that would have been sent had the request been a
  263. GET.
  264. """
  265. if self.headers.get("Transfer-Encoding", "") == "chunked":
  266. return None
  267. content_length = self.headers.get("Content-Length")
  268. if content_length is not None:
  269. try:
  270. return max(0, int(content_length))
  271. except (ValueError, TypeError):
  272. pass
  273. return None
  274. content_encoding = header_property[str](
  275. "Content-Encoding",
  276. doc="""The Content-Encoding entity-header field is used as a
  277. modifier to the media-type. When present, its value indicates
  278. what additional content codings have been applied to the
  279. entity-body, and thus what decoding mechanisms must be applied
  280. in order to obtain the media-type referenced by the Content-Type
  281. header field.
  282. .. versionadded:: 0.9""",
  283. read_only=True,
  284. )
  285. content_md5 = header_property[str](
  286. "Content-MD5",
  287. doc="""The Content-MD5 entity-header field, as defined in
  288. RFC 1864, is an MD5 digest of the entity-body for the purpose of
  289. providing an end-to-end message integrity check (MIC) of the
  290. entity-body. (Note: a MIC is good for detecting accidental
  291. modification of the entity-body in transit, but is not proof
  292. against malicious attacks.)
  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. .. versionchanged:: 2.0
  312. The datetime object is timezone-aware.
  313. """,
  314. read_only=True,
  315. )
  316. max_forwards = header_property(
  317. "Max-Forwards",
  318. None,
  319. int,
  320. doc="""The Max-Forwards request-header field provides a
  321. mechanism with the TRACE and OPTIONS methods to limit the number
  322. of proxies or gateways that can forward the request to the next
  323. inbound server.""",
  324. read_only=True,
  325. )
  326. def _parse_content_type(self) -> None:
  327. if not hasattr(self, "_parsed_content_type"):
  328. self._parsed_content_type = parse_options_header(
  329. self.headers.get("Content-Type", "")
  330. )
  331. @property
  332. def mimetype(self) -> str:
  333. """Like :attr:`content_type`, but without parameters (eg, without
  334. charset, type etc.) and always lowercase. For example if the content
  335. type is ``text/HTML; charset=utf-8`` the mimetype would be
  336. ``'text/html'``.
  337. """
  338. self._parse_content_type()
  339. return self._parsed_content_type[0].lower()
  340. @property
  341. def mimetype_params(self) -> t.Dict[str, str]:
  342. """The mimetype parameters as dict. For example if the content
  343. type is ``text/html; charset=utf-8`` the params would be
  344. ``{'charset': 'utf-8'}``.
  345. """
  346. self._parse_content_type()
  347. return self._parsed_content_type[1]
  348. @cached_property
  349. def pragma(self) -> HeaderSet:
  350. """The Pragma general-header field is used to include
  351. implementation-specific directives that might apply to any recipient
  352. along the request/response chain. All pragma directives specify
  353. optional behavior from the viewpoint of the protocol; however, some
  354. systems MAY require that behavior be consistent with the directives.
  355. """
  356. return parse_set_header(self.headers.get("Pragma", ""))
  357. # Accept
  358. @cached_property
  359. def accept_mimetypes(self) -> MIMEAccept:
  360. """List of mimetypes this client supports as
  361. :class:`~werkzeug.datastructures.MIMEAccept` object.
  362. """
  363. return parse_accept_header(self.headers.get("Accept"), MIMEAccept)
  364. @cached_property
  365. def accept_charsets(self) -> CharsetAccept:
  366. """List of charsets this client supports as
  367. :class:`~werkzeug.datastructures.CharsetAccept` object.
  368. """
  369. return parse_accept_header(self.headers.get("Accept-Charset"), CharsetAccept)
  370. @cached_property
  371. def accept_encodings(self) -> Accept:
  372. """List of encodings this client accepts. Encodings in a HTTP term
  373. are compression encodings such as gzip. For charsets have a look at
  374. :attr:`accept_charset`.
  375. """
  376. return parse_accept_header(self.headers.get("Accept-Encoding"))
  377. @cached_property
  378. def accept_languages(self) -> LanguageAccept:
  379. """List of languages this client accepts as
  380. :class:`~werkzeug.datastructures.LanguageAccept` object.
  381. .. versionchanged 0.5
  382. In previous versions this was a regular
  383. :class:`~werkzeug.datastructures.Accept` object.
  384. """
  385. return parse_accept_header(self.headers.get("Accept-Language"), LanguageAccept)
  386. # ETag
  387. @cached_property
  388. def cache_control(self) -> RequestCacheControl:
  389. """A :class:`~werkzeug.datastructures.RequestCacheControl` object
  390. for the incoming cache control headers.
  391. """
  392. cache_control = self.headers.get("Cache-Control")
  393. return parse_cache_control_header(cache_control, None, RequestCacheControl)
  394. @cached_property
  395. def if_match(self) -> ETags:
  396. """An object containing all the etags in the `If-Match` header.
  397. :rtype: :class:`~werkzeug.datastructures.ETags`
  398. """
  399. return parse_etags(self.headers.get("If-Match"))
  400. @cached_property
  401. def if_none_match(self) -> ETags:
  402. """An object containing all the etags in the `If-None-Match` header.
  403. :rtype: :class:`~werkzeug.datastructures.ETags`
  404. """
  405. return parse_etags(self.headers.get("If-None-Match"))
  406. @cached_property
  407. def if_modified_since(self) -> t.Optional[datetime]:
  408. """The parsed `If-Modified-Since` header as a datetime object.
  409. .. versionchanged:: 2.0
  410. The datetime object is timezone-aware.
  411. """
  412. return parse_date(self.headers.get("If-Modified-Since"))
  413. @cached_property
  414. def if_unmodified_since(self) -> t.Optional[datetime]:
  415. """The parsed `If-Unmodified-Since` header as a datetime object.
  416. .. versionchanged:: 2.0
  417. The datetime object is timezone-aware.
  418. """
  419. return parse_date(self.headers.get("If-Unmodified-Since"))
  420. @cached_property
  421. def if_range(self) -> IfRange:
  422. """The parsed ``If-Range`` header.
  423. .. versionchanged:: 2.0
  424. ``IfRange.date`` is timezone-aware.
  425. .. versionadded:: 0.7
  426. """
  427. return parse_if_range_header(self.headers.get("If-Range"))
  428. @cached_property
  429. def range(self) -> t.Optional[Range]:
  430. """The parsed `Range` header.
  431. .. versionadded:: 0.7
  432. :rtype: :class:`~werkzeug.datastructures.Range`
  433. """
  434. return parse_range_header(self.headers.get("Range"))
  435. # User Agent
  436. @cached_property
  437. def user_agent(self) -> UserAgent:
  438. """The user agent. Use ``user_agent.string`` to get the header
  439. value. Set :attr:`user_agent_class` to a subclass of
  440. :class:`~werkzeug.user_agent.UserAgent` to provide parsing for
  441. the other properties or other extended data.
  442. .. versionchanged:: 2.0
  443. The built in parser is deprecated and will be removed in
  444. Werkzeug 2.1. A ``UserAgent`` subclass must be set to parse
  445. data from the string.
  446. """
  447. return self.user_agent_class(self.headers.get("User-Agent", ""))
  448. # Authorization
  449. @cached_property
  450. def authorization(self) -> t.Optional[Authorization]:
  451. """The `Authorization` object in parsed form."""
  452. return parse_authorization_header(self.headers.get("Authorization"))
  453. # CORS
  454. origin = header_property[str](
  455. "Origin",
  456. doc=(
  457. "The host that the request originated from. Set"
  458. " :attr:`~CORSResponseMixin.access_control_allow_origin` on"
  459. " the response to indicate which origins are allowed."
  460. ),
  461. read_only=True,
  462. )
  463. access_control_request_headers = header_property(
  464. "Access-Control-Request-Headers",
  465. load_func=parse_set_header,
  466. doc=(
  467. "Sent with a preflight request to indicate which headers"
  468. " will be sent with the cross origin request. Set"
  469. " :attr:`~CORSResponseMixin.access_control_allow_headers`"
  470. " on the response to indicate which headers are allowed."
  471. ),
  472. read_only=True,
  473. )
  474. access_control_request_method = header_property[str](
  475. "Access-Control-Request-Method",
  476. doc=(
  477. "Sent with a preflight request to indicate which method"
  478. " will be used for the cross origin request. Set"
  479. " :attr:`~CORSResponseMixin.access_control_allow_methods`"
  480. " on the response to indicate which methods are allowed."
  481. ),
  482. read_only=True,
  483. )
  484. @property
  485. def is_json(self) -> bool:
  486. """Check if the mimetype indicates JSON data, either
  487. :mimetype:`application/json` or :mimetype:`application/*+json`.
  488. """
  489. mt = self.mimetype
  490. return (
  491. mt == "application/json"
  492. or mt.startswith("application/")
  493. and mt.endswith("+json")
  494. )