You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

base_request.py 26KB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. import warnings
  2. from functools import update_wrapper
  3. from io import BytesIO
  4. from .._compat import to_native
  5. from .._compat import to_unicode
  6. from .._compat import wsgi_decoding_dance
  7. from .._compat import wsgi_get_bytes
  8. from ..datastructures import CombinedMultiDict
  9. from ..datastructures import EnvironHeaders
  10. from ..datastructures import ImmutableList
  11. from ..datastructures import ImmutableMultiDict
  12. from ..datastructures import ImmutableTypeConversionDict
  13. from ..datastructures import iter_multi_items
  14. from ..datastructures import MultiDict
  15. from ..formparser import default_stream_factory
  16. from ..formparser import FormDataParser
  17. from ..http import parse_cookie
  18. from ..http import parse_options_header
  19. from ..urls import url_decode
  20. from ..utils import cached_property
  21. from ..utils import environ_property
  22. from ..wsgi import get_content_length
  23. from ..wsgi import get_current_url
  24. from ..wsgi import get_host
  25. from ..wsgi import get_input_stream
  26. class BaseRequest(object):
  27. """Very basic request object. This does not implement advanced stuff like
  28. entity tag parsing or cache controls. The request object is created with
  29. the WSGI environment as first argument and will add itself to the WSGI
  30. environment as ``'werkzeug.request'`` unless it's created with
  31. `populate_request` set to False.
  32. There are a couple of mixins available that add additional functionality
  33. to the request object, there is also a class called `Request` which
  34. subclasses `BaseRequest` and all the important mixins.
  35. It's a good idea to create a custom subclass of the :class:`BaseRequest`
  36. and add missing functionality either via mixins or direct implementation.
  37. Here an example for such subclasses::
  38. from werkzeug.wrappers import BaseRequest, ETagRequestMixin
  39. class Request(BaseRequest, ETagRequestMixin):
  40. pass
  41. Request objects are **read only**. As of 0.5 modifications are not
  42. allowed in any place. Unlike the lower level parsing functions the
  43. request object will use immutable objects everywhere possible.
  44. Per default the request object will assume all the text data is `utf-8`
  45. encoded. Please refer to :doc:`the unicode chapter </unicode>` for more
  46. details about customizing the behavior.
  47. Per default the request object will be added to the WSGI
  48. environment as `werkzeug.request` to support the debugging system.
  49. If you don't want that, set `populate_request` to `False`.
  50. If `shallow` is `True` the environment is initialized as shallow
  51. object around the environ. Every operation that would modify the
  52. environ in any way (such as consuming form data) raises an exception
  53. unless the `shallow` attribute is explicitly set to `False`. This
  54. is useful for middlewares where you don't want to consume the form
  55. data by accident. A shallow request is not populated to the WSGI
  56. environment.
  57. .. versionchanged:: 0.5
  58. read-only mode was enforced by using immutables classes for all
  59. data.
  60. """
  61. #: the charset for the request, defaults to utf-8
  62. charset = "utf-8"
  63. #: the error handling procedure for errors, defaults to 'replace'
  64. encoding_errors = "replace"
  65. #: the maximum content length. This is forwarded to the form data
  66. #: parsing function (:func:`parse_form_data`). When set and the
  67. #: :attr:`form` or :attr:`files` attribute is accessed and the
  68. #: parsing fails because more than the specified value is transmitted
  69. #: a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
  70. #:
  71. #: Have a look at :ref:`dealing-with-request-data` for more details.
  72. #:
  73. #: .. versionadded:: 0.5
  74. max_content_length = None
  75. #: the maximum form field size. This is forwarded to the form data
  76. #: parsing function (:func:`parse_form_data`). When set and the
  77. #: :attr:`form` or :attr:`files` attribute is accessed and the
  78. #: data in memory for post data is longer than the specified value a
  79. #: :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
  80. #:
  81. #: Have a look at :ref:`dealing-with-request-data` for more details.
  82. #:
  83. #: .. versionadded:: 0.5
  84. max_form_memory_size = None
  85. #: the class to use for `args` and `form`. The default is an
  86. #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports
  87. #: multiple values per key. alternatively it makes sense to use an
  88. #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which
  89. #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict`
  90. #: which is the fastest but only remembers the last key. It is also
  91. #: possible to use mutable structures, but this is not recommended.
  92. #:
  93. #: .. versionadded:: 0.6
  94. parameter_storage_class = ImmutableMultiDict
  95. #: the type to be used for list values from the incoming WSGI environment.
  96. #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used
  97. #: (for example for :attr:`access_list`).
  98. #:
  99. #: .. versionadded:: 0.6
  100. list_storage_class = ImmutableList
  101. #: the type to be used for dict values from the incoming WSGI environment.
  102. #: By default an
  103. #: :class:`~werkzeug.datastructures.ImmutableTypeConversionDict` is used
  104. #: (for example for :attr:`cookies`).
  105. #:
  106. #: .. versionadded:: 0.6
  107. dict_storage_class = ImmutableTypeConversionDict
  108. #: The form data parser that shoud be used. Can be replaced to customize
  109. #: the form date parsing.
  110. form_data_parser_class = FormDataParser
  111. #: Optionally a list of hosts that is trusted by this request. By default
  112. #: all hosts are trusted which means that whatever the client sends the
  113. #: host is will be accepted.
  114. #:
  115. #: Because `Host` and `X-Forwarded-Host` headers can be set to any value by
  116. #: a malicious client, it is recommended to either set this property or
  117. #: implement similar validation in the proxy (if application is being run
  118. #: behind one).
  119. #:
  120. #: .. versionadded:: 0.9
  121. trusted_hosts = None
  122. #: Indicates whether the data descriptor should be allowed to read and
  123. #: buffer up the input stream. By default it's enabled.
  124. #:
  125. #: .. versionadded:: 0.9
  126. disable_data_descriptor = False
  127. def __init__(self, environ, populate_request=True, shallow=False):
  128. self.environ = environ
  129. if populate_request and not shallow:
  130. self.environ["werkzeug.request"] = self
  131. self.shallow = shallow
  132. def __repr__(self):
  133. # make sure the __repr__ even works if the request was created
  134. # from an invalid WSGI environment. If we display the request
  135. # in a debug session we don't want the repr to blow up.
  136. args = []
  137. try:
  138. args.append("'%s'" % to_native(self.url, self.url_charset))
  139. args.append("[%s]" % self.method)
  140. except Exception:
  141. args.append("(invalid WSGI environ)")
  142. return "<%s %s>" % (self.__class__.__name__, " ".join(args))
  143. @property
  144. def url_charset(self):
  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. @classmethod
  151. def from_values(cls, *args, **kwargs):
  152. """Create a new request object based on the values provided. If
  153. environ is given missing values are filled from there. This method is
  154. useful for small scripts when you need to simulate a request from an URL.
  155. Do not use this method for unittesting, there is a full featured client
  156. object (:class:`Client`) that allows to create multipart requests,
  157. support for cookies etc.
  158. This accepts the same options as the
  159. :class:`~werkzeug.test.EnvironBuilder`.
  160. .. versionchanged:: 0.5
  161. This method now accepts the same arguments as
  162. :class:`~werkzeug.test.EnvironBuilder`. Because of this the
  163. `environ` parameter is now called `environ_overrides`.
  164. :return: request object
  165. """
  166. from ..test import EnvironBuilder
  167. charset = kwargs.pop("charset", cls.charset)
  168. kwargs["charset"] = charset
  169. builder = EnvironBuilder(*args, **kwargs)
  170. try:
  171. return builder.get_request(cls)
  172. finally:
  173. builder.close()
  174. @classmethod
  175. def application(cls, f):
  176. """Decorate a function as responder that accepts the request as
  177. the last argument. This works like the :func:`responder`
  178. decorator but the function is passed the request object as the
  179. last argument and the request object will be closed
  180. automatically::
  181. @Request.application
  182. def my_wsgi_app(request):
  183. return Response('Hello World!')
  184. As of Werkzeug 0.14 HTTP exceptions are automatically caught and
  185. converted to responses instead of failing.
  186. :param f: the WSGI callable to decorate
  187. :return: a new WSGI callable
  188. """
  189. #: return a callable that wraps the -2nd argument with the request
  190. #: and calls the function with all the arguments up to that one and
  191. #: the request. The return value is then called with the latest
  192. #: two arguments. This makes it possible to use this decorator for
  193. #: both standalone WSGI functions as well as bound methods and
  194. #: partially applied functions.
  195. from ..exceptions import HTTPException
  196. def application(*args):
  197. request = cls(args[-2])
  198. with request:
  199. try:
  200. resp = f(*args[:-2] + (request,))
  201. except HTTPException as e:
  202. resp = e.get_response(args[-2])
  203. return resp(*args[-2:])
  204. return update_wrapper(application, f)
  205. def _get_file_stream(
  206. self, total_content_length, content_type, filename=None, content_length=None
  207. ):
  208. """Called to get a stream for the file upload.
  209. This must provide a file-like class with `read()`, `readline()`
  210. and `seek()` methods that is both writeable and readable.
  211. The default implementation returns a temporary file if the total
  212. content length is higher than 500KB. Because many browsers do not
  213. provide a content length for the files only the total content
  214. length matters.
  215. :param total_content_length: the total content length of all the
  216. data in the request combined. This value
  217. is guaranteed to be there.
  218. :param content_type: the mimetype of the uploaded file.
  219. :param filename: the filename of the uploaded file. May be `None`.
  220. :param content_length: the length of this file. This value is usually
  221. not provided because webbrowsers do not provide
  222. this value.
  223. """
  224. return default_stream_factory(
  225. total_content_length=total_content_length,
  226. filename=filename,
  227. content_type=content_type,
  228. content_length=content_length,
  229. )
  230. @property
  231. def want_form_data_parsed(self):
  232. """Returns True if the request method carries content. As of
  233. Werkzeug 0.9 this will be the case if a content type is transmitted.
  234. .. versionadded:: 0.8
  235. """
  236. return bool(self.environ.get("CONTENT_TYPE"))
  237. def make_form_data_parser(self):
  238. """Creates the form data parser. Instantiates the
  239. :attr:`form_data_parser_class` with some parameters.
  240. .. versionadded:: 0.8
  241. """
  242. return self.form_data_parser_class(
  243. self._get_file_stream,
  244. self.charset,
  245. self.encoding_errors,
  246. self.max_form_memory_size,
  247. self.max_content_length,
  248. self.parameter_storage_class,
  249. )
  250. def _load_form_data(self):
  251. """Method used internally to retrieve submitted data. After calling
  252. this sets `form` and `files` on the request object to multi dicts
  253. filled with the incoming form data. As a matter of fact the input
  254. stream will be empty afterwards. You can also call this method to
  255. force the parsing of the form data.
  256. .. versionadded:: 0.8
  257. """
  258. # abort early if we have already consumed the stream
  259. if "form" in self.__dict__:
  260. return
  261. _assert_not_shallow(self)
  262. if self.want_form_data_parsed:
  263. content_type = self.environ.get("CONTENT_TYPE", "")
  264. content_length = get_content_length(self.environ)
  265. mimetype, options = parse_options_header(content_type)
  266. parser = self.make_form_data_parser()
  267. data = parser.parse(
  268. self._get_stream_for_parsing(), mimetype, content_length, options
  269. )
  270. else:
  271. data = (
  272. self.stream,
  273. self.parameter_storage_class(),
  274. self.parameter_storage_class(),
  275. )
  276. # inject the values into the instance dict so that we bypass
  277. # our cached_property non-data descriptor.
  278. d = self.__dict__
  279. d["stream"], d["form"], d["files"] = data
  280. def _get_stream_for_parsing(self):
  281. """This is the same as accessing :attr:`stream` with the difference
  282. that if it finds cached data from calling :meth:`get_data` first it
  283. will create a new stream out of the cached data.
  284. .. versionadded:: 0.9.3
  285. """
  286. cached_data = getattr(self, "_cached_data", None)
  287. if cached_data is not None:
  288. return BytesIO(cached_data)
  289. return self.stream
  290. def close(self):
  291. """Closes associated resources of this request object. This
  292. closes all file handles explicitly. You can also use the request
  293. object in a with statement which will automatically close it.
  294. .. versionadded:: 0.9
  295. """
  296. files = self.__dict__.get("files")
  297. for _key, value in iter_multi_items(files or ()):
  298. value.close()
  299. def __enter__(self):
  300. return self
  301. def __exit__(self, exc_type, exc_value, tb):
  302. self.close()
  303. @cached_property
  304. def stream(self):
  305. """
  306. If the incoming form data was not encoded with a known mimetype
  307. the data is stored unmodified in this stream for consumption. Most
  308. of the time it is a better idea to use :attr:`data` which will give
  309. you that data as a string. The stream only returns the data once.
  310. Unlike :attr:`input_stream` this stream is properly guarded that you
  311. can't accidentally read past the length of the input. Werkzeug will
  312. internally always refer to this stream to read data which makes it
  313. possible to wrap this object with a stream that does filtering.
  314. .. versionchanged:: 0.9
  315. This stream is now always available but might be consumed by the
  316. form parser later on. Previously the stream was only set if no
  317. parsing happened.
  318. """
  319. _assert_not_shallow(self)
  320. return get_input_stream(self.environ)
  321. input_stream = environ_property(
  322. "wsgi.input",
  323. """The WSGI input stream.
  324. In general it's a bad idea to use this one because you can
  325. easily read past the boundary. Use the :attr:`stream`
  326. instead.""",
  327. )
  328. @cached_property
  329. def args(self):
  330. """The parsed URL parameters (the part in the URL after the question
  331. mark).
  332. By default an
  333. :class:`~werkzeug.datastructures.ImmutableMultiDict`
  334. is returned from this function. This can be changed by setting
  335. :attr:`parameter_storage_class` to a different type. This might
  336. be necessary if the order of the form data is important.
  337. """
  338. return url_decode(
  339. wsgi_get_bytes(self.environ.get("QUERY_STRING", "")),
  340. self.url_charset,
  341. errors=self.encoding_errors,
  342. cls=self.parameter_storage_class,
  343. )
  344. @cached_property
  345. def data(self):
  346. """
  347. Contains the incoming request data as string in case it came with
  348. a mimetype Werkzeug does not handle.
  349. """
  350. if self.disable_data_descriptor:
  351. raise AttributeError("data descriptor is disabled")
  352. # XXX: this should eventually be deprecated.
  353. # We trigger form data parsing first which means that the descriptor
  354. # will not cache the data that would otherwise be .form or .files
  355. # data. This restores the behavior that was there in Werkzeug
  356. # before 0.9. New code should use :meth:`get_data` explicitly as
  357. # this will make behavior explicit.
  358. return self.get_data(parse_form_data=True)
  359. def get_data(self, cache=True, as_text=False, parse_form_data=False):
  360. """This reads the buffered incoming data from the client into one
  361. bytestring. By default this is cached but that behavior can be
  362. changed by setting `cache` to `False`.
  363. Usually it's a bad idea to call this method without checking the
  364. content length first as a client could send dozens of megabytes or more
  365. to cause memory problems on the server.
  366. Note that if the form data was already parsed this method will not
  367. return anything as form data parsing does not cache the data like
  368. this method does. To implicitly invoke form data parsing function
  369. set `parse_form_data` to `True`. When this is done the return value
  370. of this method will be an empty string if the form parser handles
  371. the data. This generally is not necessary as if the whole data is
  372. cached (which is the default) the form parser will used the cached
  373. data to parse the form data. Please be generally aware of checking
  374. the content length first in any case before calling this method
  375. to avoid exhausting server memory.
  376. If `as_text` is set to `True` the return value will be a decoded
  377. unicode string.
  378. .. versionadded:: 0.9
  379. """
  380. rv = getattr(self, "_cached_data", None)
  381. if rv is None:
  382. if parse_form_data:
  383. self._load_form_data()
  384. rv = self.stream.read()
  385. if cache:
  386. self._cached_data = rv
  387. if as_text:
  388. rv = rv.decode(self.charset, self.encoding_errors)
  389. return rv
  390. @cached_property
  391. def form(self):
  392. """The form parameters. By default an
  393. :class:`~werkzeug.datastructures.ImmutableMultiDict`
  394. is returned from this function. This can be changed by setting
  395. :attr:`parameter_storage_class` to a different type. This might
  396. be necessary if the order of the form data is important.
  397. Please keep in mind that file uploads will not end up here, but instead
  398. in the :attr:`files` attribute.
  399. .. versionchanged:: 0.9
  400. Previous to Werkzeug 0.9 this would only contain form data for POST
  401. and PUT requests.
  402. """
  403. self._load_form_data()
  404. return self.form
  405. @cached_property
  406. def values(self):
  407. """A :class:`werkzeug.datastructures.CombinedMultiDict` that combines
  408. :attr:`args` and :attr:`form`."""
  409. args = []
  410. for d in self.args, self.form:
  411. if not isinstance(d, MultiDict):
  412. d = MultiDict(d)
  413. args.append(d)
  414. return CombinedMultiDict(args)
  415. @cached_property
  416. def files(self):
  417. """:class:`~werkzeug.datastructures.MultiDict` object containing
  418. all uploaded files. Each key in :attr:`files` is the name from the
  419. ``<input type="file" name="">``. Each value in :attr:`files` is a
  420. Werkzeug :class:`~werkzeug.datastructures.FileStorage` object.
  421. It basically behaves like a standard file object you know from Python,
  422. with the difference that it also has a
  423. :meth:`~werkzeug.datastructures.FileStorage.save` function that can
  424. store the file on the filesystem.
  425. Note that :attr:`files` will only contain data if the request method was
  426. POST, PUT or PATCH and the ``<form>`` that posted to the request had
  427. ``enctype="multipart/form-data"``. It will be empty otherwise.
  428. See the :class:`~werkzeug.datastructures.MultiDict` /
  429. :class:`~werkzeug.datastructures.FileStorage` documentation for
  430. more details about the used data structure.
  431. """
  432. self._load_form_data()
  433. return self.files
  434. @cached_property
  435. def cookies(self):
  436. """A :class:`dict` with the contents of all cookies transmitted with
  437. the request."""
  438. return parse_cookie(
  439. self.environ,
  440. self.charset,
  441. self.encoding_errors,
  442. cls=self.dict_storage_class,
  443. )
  444. @cached_property
  445. def headers(self):
  446. """The headers from the WSGI environ as immutable
  447. :class:`~werkzeug.datastructures.EnvironHeaders`.
  448. """
  449. return EnvironHeaders(self.environ)
  450. @cached_property
  451. def path(self):
  452. """Requested path as unicode. This works a bit like the regular path
  453. info in the WSGI environment but will always include a leading slash,
  454. even if the URL root is accessed.
  455. """
  456. raw_path = wsgi_decoding_dance(
  457. self.environ.get("PATH_INFO") or "", self.charset, self.encoding_errors
  458. )
  459. return "/" + raw_path.lstrip("/")
  460. @cached_property
  461. def full_path(self):
  462. """Requested path as unicode, including the query string."""
  463. return self.path + u"?" + to_unicode(self.query_string, self.url_charset)
  464. @cached_property
  465. def script_root(self):
  466. """The root path of the script without the trailing slash."""
  467. raw_path = wsgi_decoding_dance(
  468. self.environ.get("SCRIPT_NAME") or "", self.charset, self.encoding_errors
  469. )
  470. return raw_path.rstrip("/")
  471. @cached_property
  472. def url(self):
  473. """The reconstructed current URL as IRI.
  474. See also: :attr:`trusted_hosts`.
  475. """
  476. return get_current_url(self.environ, trusted_hosts=self.trusted_hosts)
  477. @cached_property
  478. def base_url(self):
  479. """Like :attr:`url` but without the querystring
  480. See also: :attr:`trusted_hosts`.
  481. """
  482. return get_current_url(
  483. self.environ, strip_querystring=True, trusted_hosts=self.trusted_hosts
  484. )
  485. @cached_property
  486. def url_root(self):
  487. """The full URL root (with hostname), this is the application
  488. root as IRI.
  489. See also: :attr:`trusted_hosts`.
  490. """
  491. return get_current_url(self.environ, True, trusted_hosts=self.trusted_hosts)
  492. @cached_property
  493. def host_url(self):
  494. """Just the host with scheme as IRI.
  495. See also: :attr:`trusted_hosts`.
  496. """
  497. return get_current_url(
  498. self.environ, host_only=True, trusted_hosts=self.trusted_hosts
  499. )
  500. @cached_property
  501. def host(self):
  502. """Just the host including the port if available.
  503. See also: :attr:`trusted_hosts`.
  504. """
  505. return get_host(self.environ, trusted_hosts=self.trusted_hosts)
  506. query_string = environ_property(
  507. "QUERY_STRING",
  508. "",
  509. read_only=True,
  510. load_func=wsgi_get_bytes,
  511. doc="The URL parameters as raw bytestring.",
  512. )
  513. method = environ_property(
  514. "REQUEST_METHOD",
  515. "GET",
  516. read_only=True,
  517. load_func=lambda x: x.upper(),
  518. doc="The request method. (For example ``'GET'`` or ``'POST'``).",
  519. )
  520. @cached_property
  521. def access_route(self):
  522. """If a forwarded header exists this is a list of all ip addresses
  523. from the client ip to the last proxy server.
  524. """
  525. if "HTTP_X_FORWARDED_FOR" in self.environ:
  526. addr = self.environ["HTTP_X_FORWARDED_FOR"].split(",")
  527. return self.list_storage_class([x.strip() for x in addr])
  528. elif "REMOTE_ADDR" in self.environ:
  529. return self.list_storage_class([self.environ["REMOTE_ADDR"]])
  530. return self.list_storage_class()
  531. @property
  532. def remote_addr(self):
  533. """The remote address of the client."""
  534. return self.environ.get("REMOTE_ADDR")
  535. remote_user = environ_property(
  536. "REMOTE_USER",
  537. doc="""If the server supports user authentication, and the
  538. script is protected, this attribute contains the username the
  539. user has authenticated as.""",
  540. )
  541. scheme = environ_property(
  542. "wsgi.url_scheme",
  543. doc="""
  544. URL scheme (http or https).
  545. .. versionadded:: 0.7""",
  546. )
  547. @property
  548. def is_xhr(self):
  549. """True if the request was triggered via a JavaScript XMLHttpRequest.
  550. This only works with libraries that support the ``X-Requested-With``
  551. header and set it to "XMLHttpRequest". Libraries that do that are
  552. prototype, jQuery and Mochikit and probably some more.
  553. .. deprecated:: 0.13
  554. ``X-Requested-With`` is not standard and is unreliable. You
  555. may be able to use :attr:`AcceptMixin.accept_mimetypes`
  556. instead.
  557. """
  558. warnings.warn(
  559. "'Request.is_xhr' is deprecated as of version 0.13 and will"
  560. " be removed in version 1.0. The 'X-Requested-With' header"
  561. " is not standard and is unreliable. You may be able to use"
  562. " 'accept_mimetypes' instead.",
  563. DeprecationWarning,
  564. stacklevel=2,
  565. )
  566. return self.environ.get("HTTP_X_REQUESTED_WITH", "").lower() == "xmlhttprequest"
  567. is_secure = property(
  568. lambda self: self.environ["wsgi.url_scheme"] == "https",
  569. doc="`True` if the request is secure.",
  570. )
  571. is_multithread = environ_property(
  572. "wsgi.multithread",
  573. doc="""boolean that is `True` if the application is served by a
  574. multithreaded WSGI server.""",
  575. )
  576. is_multiprocess = environ_property(
  577. "wsgi.multiprocess",
  578. doc="""boolean that is `True` if the application is served by a
  579. WSGI server that spawns multiple processes.""",
  580. )
  581. is_run_once = environ_property(
  582. "wsgi.run_once",
  583. doc="""boolean that is `True` if the application will be
  584. executed only once in a process lifetime. This is the case for
  585. CGI for example, but it's not guaranteed that the execution only
  586. happens one time.""",
  587. )
  588. def _assert_not_shallow(request):
  589. if request.shallow:
  590. raise RuntimeError(
  591. "A shallow request tried to consume form data. If you really"
  592. " want to do that, set `shallow` to False."
  593. )