123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695 |
- import warnings
- from functools import update_wrapper
- from io import BytesIO
- from .._compat import to_native
- from .._compat import to_unicode
- from .._compat import wsgi_decoding_dance
- from .._compat import wsgi_get_bytes
- from ..datastructures import CombinedMultiDict
- from ..datastructures import EnvironHeaders
- from ..datastructures import ImmutableList
- from ..datastructures import ImmutableMultiDict
- from ..datastructures import ImmutableTypeConversionDict
- from ..datastructures import iter_multi_items
- from ..datastructures import MultiDict
- from ..formparser import default_stream_factory
- from ..formparser import FormDataParser
- from ..http import parse_cookie
- from ..http import parse_options_header
- from ..urls import url_decode
- from ..utils import cached_property
- from ..utils import environ_property
- from ..wsgi import get_content_length
- from ..wsgi import get_current_url
- from ..wsgi import get_host
- from ..wsgi import get_input_stream
- class BaseRequest(object):
- """Very basic request object. This does not implement advanced stuff like
- entity tag parsing or cache controls. The request object is created with
- the WSGI environment as first argument and will add itself to the WSGI
- environment as ``'werkzeug.request'`` unless it's created with
- `populate_request` set to False.
- There are a couple of mixins available that add additional functionality
- to the request object, there is also a class called `Request` which
- subclasses `BaseRequest` and all the important mixins.
- It's a good idea to create a custom subclass of the :class:`BaseRequest`
- and add missing functionality either via mixins or direct implementation.
- Here an example for such subclasses::
- from werkzeug.wrappers import BaseRequest, ETagRequestMixin
- class Request(BaseRequest, ETagRequestMixin):
- pass
- Request objects are **read only**. As of 0.5 modifications are not
- allowed in any place. Unlike the lower level parsing functions the
- request object will use immutable objects everywhere possible.
- Per default the request object will assume all the text data is `utf-8`
- encoded. Please refer to :doc:`the unicode chapter </unicode>` for more
- details about customizing the behavior.
- Per default the request object will be added to the WSGI
- environment as `werkzeug.request` to support the debugging system.
- If you don't want that, set `populate_request` to `False`.
- If `shallow` is `True` the environment is initialized as shallow
- object around the environ. Every operation that would modify the
- environ in any way (such as consuming form data) raises an exception
- unless the `shallow` attribute is explicitly set to `False`. This
- is useful for middlewares where you don't want to consume the form
- data by accident. A shallow request is not populated to the WSGI
- environment.
- .. versionchanged:: 0.5
- read-only mode was enforced by using immutables classes for all
- data.
- """
- #: the charset for the request, defaults to utf-8
- charset = "utf-8"
- #: the error handling procedure for errors, defaults to 'replace'
- encoding_errors = "replace"
- #: the maximum content length. This is forwarded to the form data
- #: parsing function (:func:`parse_form_data`). When set and the
- #: :attr:`form` or :attr:`files` attribute is accessed and the
- #: parsing fails because more than the specified value is transmitted
- #: a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
- #:
- #: Have a look at :ref:`dealing-with-request-data` for more details.
- #:
- #: .. versionadded:: 0.5
- max_content_length = None
- #: the maximum form field size. This is forwarded to the form data
- #: parsing function (:func:`parse_form_data`). When set and the
- #: :attr:`form` or :attr:`files` attribute is accessed and the
- #: data in memory for post data is longer than the specified value a
- #: :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
- #:
- #: Have a look at :ref:`dealing-with-request-data` for more details.
- #:
- #: .. versionadded:: 0.5
- max_form_memory_size = None
- #: the class to use for `args` and `form`. The default is an
- #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports
- #: multiple values per key. alternatively it makes sense to use an
- #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which
- #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict`
- #: which is the fastest but only remembers the last key. It is also
- #: possible to use mutable structures, but this is not recommended.
- #:
- #: .. versionadded:: 0.6
- parameter_storage_class = ImmutableMultiDict
- #: the type to be used for list values from the incoming WSGI environment.
- #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used
- #: (for example for :attr:`access_list`).
- #:
- #: .. versionadded:: 0.6
- list_storage_class = ImmutableList
- #: the type to be used for dict values from the incoming WSGI environment.
- #: By default an
- #: :class:`~werkzeug.datastructures.ImmutableTypeConversionDict` is used
- #: (for example for :attr:`cookies`).
- #:
- #: .. versionadded:: 0.6
- dict_storage_class = ImmutableTypeConversionDict
- #: The form data parser that shoud be used. Can be replaced to customize
- #: the form date parsing.
- form_data_parser_class = FormDataParser
- #: Optionally a list of hosts that is trusted by this request. By default
- #: all hosts are trusted which means that whatever the client sends the
- #: host is will be accepted.
- #:
- #: Because `Host` and `X-Forwarded-Host` headers can be set to any value by
- #: a malicious client, it is recommended to either set this property or
- #: implement similar validation in the proxy (if application is being run
- #: behind one).
- #:
- #: .. versionadded:: 0.9
- trusted_hosts = None
- #: Indicates whether the data descriptor should be allowed to read and
- #: buffer up the input stream. By default it's enabled.
- #:
- #: .. versionadded:: 0.9
- disable_data_descriptor = False
- def __init__(self, environ, populate_request=True, shallow=False):
- self.environ = environ
- if populate_request and not shallow:
- self.environ["werkzeug.request"] = self
- self.shallow = shallow
- def __repr__(self):
- # make sure the __repr__ even works if the request was created
- # from an invalid WSGI environment. If we display the request
- # in a debug session we don't want the repr to blow up.
- args = []
- try:
- args.append("'%s'" % to_native(self.url, self.url_charset))
- args.append("[%s]" % self.method)
- except Exception:
- args.append("(invalid WSGI environ)")
- return "<%s %s>" % (self.__class__.__name__, " ".join(args))
- @property
- def url_charset(self):
- """The charset that is assumed for URLs. Defaults to the value
- of :attr:`charset`.
- .. versionadded:: 0.6
- """
- return self.charset
- @classmethod
- def from_values(cls, *args, **kwargs):
- """Create a new request object based on the values provided. If
- environ is given missing values are filled from there. This method is
- useful for small scripts when you need to simulate a request from an URL.
- Do not use this method for unittesting, there is a full featured client
- object (:class:`Client`) that allows to create multipart requests,
- support for cookies etc.
- This accepts the same options as the
- :class:`~werkzeug.test.EnvironBuilder`.
- .. versionchanged:: 0.5
- This method now accepts the same arguments as
- :class:`~werkzeug.test.EnvironBuilder`. Because of this the
- `environ` parameter is now called `environ_overrides`.
- :return: request object
- """
- from ..test import EnvironBuilder
- charset = kwargs.pop("charset", cls.charset)
- kwargs["charset"] = charset
- builder = EnvironBuilder(*args, **kwargs)
- try:
- return builder.get_request(cls)
- finally:
- builder.close()
- @classmethod
- def application(cls, f):
- """Decorate a function as responder that accepts the request as
- the last argument. This works like the :func:`responder`
- decorator but the function is passed the request object as the
- last argument and the request object will be closed
- automatically::
- @Request.application
- def my_wsgi_app(request):
- return Response('Hello World!')
- As of Werkzeug 0.14 HTTP exceptions are automatically caught and
- converted to responses instead of failing.
- :param f: the WSGI callable to decorate
- :return: a new WSGI callable
- """
- #: return a callable that wraps the -2nd argument with the request
- #: and calls the function with all the arguments up to that one and
- #: the request. The return value is then called with the latest
- #: two arguments. This makes it possible to use this decorator for
- #: both standalone WSGI functions as well as bound methods and
- #: partially applied functions.
- from ..exceptions import HTTPException
- def application(*args):
- request = cls(args[-2])
- with request:
- try:
- resp = f(*args[:-2] + (request,))
- except HTTPException as e:
- resp = e.get_response(args[-2])
- return resp(*args[-2:])
- return update_wrapper(application, f)
- def _get_file_stream(
- self, total_content_length, content_type, filename=None, content_length=None
- ):
- """Called to get a stream for the file upload.
- This must provide a file-like class with `read()`, `readline()`
- and `seek()` methods that is both writeable and readable.
- The default implementation returns a temporary file if the total
- content length is higher than 500KB. Because many browsers do not
- provide a content length for the files only the total content
- length matters.
- :param total_content_length: the total content length of all the
- data in the request combined. This value
- is guaranteed to be there.
- :param content_type: the mimetype of the uploaded file.
- :param filename: the filename of the uploaded file. May be `None`.
- :param content_length: the length of this file. This value is usually
- not provided because webbrowsers do not provide
- this value.
- """
- return default_stream_factory(
- total_content_length=total_content_length,
- filename=filename,
- content_type=content_type,
- content_length=content_length,
- )
- @property
- def want_form_data_parsed(self):
- """Returns True if the request method carries content. As of
- Werkzeug 0.9 this will be the case if a content type is transmitted.
- .. versionadded:: 0.8
- """
- return bool(self.environ.get("CONTENT_TYPE"))
- def make_form_data_parser(self):
- """Creates the form data parser. Instantiates the
- :attr:`form_data_parser_class` with some parameters.
- .. versionadded:: 0.8
- """
- return self.form_data_parser_class(
- self._get_file_stream,
- self.charset,
- self.encoding_errors,
- self.max_form_memory_size,
- self.max_content_length,
- self.parameter_storage_class,
- )
- def _load_form_data(self):
- """Method used internally to retrieve submitted data. After calling
- this sets `form` and `files` on the request object to multi dicts
- filled with the incoming form data. As a matter of fact the input
- stream will be empty afterwards. You can also call this method to
- force the parsing of the form data.
- .. versionadded:: 0.8
- """
- # abort early if we have already consumed the stream
- if "form" in self.__dict__:
- return
- _assert_not_shallow(self)
- if self.want_form_data_parsed:
- content_type = self.environ.get("CONTENT_TYPE", "")
- content_length = get_content_length(self.environ)
- mimetype, options = parse_options_header(content_type)
- parser = self.make_form_data_parser()
- data = parser.parse(
- self._get_stream_for_parsing(), mimetype, content_length, options
- )
- else:
- data = (
- self.stream,
- self.parameter_storage_class(),
- self.parameter_storage_class(),
- )
- # inject the values into the instance dict so that we bypass
- # our cached_property non-data descriptor.
- d = self.__dict__
- d["stream"], d["form"], d["files"] = data
- def _get_stream_for_parsing(self):
- """This is the same as accessing :attr:`stream` with the difference
- that if it finds cached data from calling :meth:`get_data` first it
- will create a new stream out of the cached data.
- .. versionadded:: 0.9.3
- """
- cached_data = getattr(self, "_cached_data", None)
- if cached_data is not None:
- return BytesIO(cached_data)
- return self.stream
- def close(self):
- """Closes associated resources of this request object. This
- closes all file handles explicitly. You can also use the request
- object in a with statement which will automatically close it.
- .. versionadded:: 0.9
- """
- files = self.__dict__.get("files")
- for _key, value in iter_multi_items(files or ()):
- value.close()
- def __enter__(self):
- return self
- def __exit__(self, exc_type, exc_value, tb):
- self.close()
- @cached_property
- def stream(self):
- """
- If the incoming form data was not encoded with a known mimetype
- the data is stored unmodified in this stream for consumption. Most
- of the time it is a better idea to use :attr:`data` which will give
- you that data as a string. The stream only returns the data once.
- Unlike :attr:`input_stream` this stream is properly guarded that you
- can't accidentally read past the length of the input. Werkzeug will
- internally always refer to this stream to read data which makes it
- possible to wrap this object with a stream that does filtering.
- .. versionchanged:: 0.9
- This stream is now always available but might be consumed by the
- form parser later on. Previously the stream was only set if no
- parsing happened.
- """
- _assert_not_shallow(self)
- return get_input_stream(self.environ)
- input_stream = environ_property(
- "wsgi.input",
- """The WSGI input stream.
- In general it's a bad idea to use this one because you can
- easily read past the boundary. Use the :attr:`stream`
- instead.""",
- )
- @cached_property
- def args(self):
- """The parsed URL parameters (the part in the URL after the question
- mark).
- By default an
- :class:`~werkzeug.datastructures.ImmutableMultiDict`
- is returned from this function. This can be changed by setting
- :attr:`parameter_storage_class` to a different type. This might
- be necessary if the order of the form data is important.
- """
- return url_decode(
- wsgi_get_bytes(self.environ.get("QUERY_STRING", "")),
- self.url_charset,
- errors=self.encoding_errors,
- cls=self.parameter_storage_class,
- )
- @cached_property
- def data(self):
- """
- Contains the incoming request data as string in case it came with
- a mimetype Werkzeug does not handle.
- """
- if self.disable_data_descriptor:
- raise AttributeError("data descriptor is disabled")
- # XXX: this should eventually be deprecated.
- # We trigger form data parsing first which means that the descriptor
- # will not cache the data that would otherwise be .form or .files
- # data. This restores the behavior that was there in Werkzeug
- # before 0.9. New code should use :meth:`get_data` explicitly as
- # this will make behavior explicit.
- return self.get_data(parse_form_data=True)
- def get_data(self, cache=True, as_text=False, parse_form_data=False):
- """This reads the buffered incoming data from the client into one
- bytestring. By default this is cached but that behavior can be
- changed by setting `cache` to `False`.
- Usually it's a bad idea to call this method without checking the
- content length first as a client could send dozens of megabytes or more
- to cause memory problems on the server.
- Note that if the form data was already parsed this method will not
- return anything as form data parsing does not cache the data like
- this method does. To implicitly invoke form data parsing function
- set `parse_form_data` to `True`. When this is done the return value
- of this method will be an empty string if the form parser handles
- the data. This generally is not necessary as if the whole data is
- cached (which is the default) the form parser will used the cached
- data to parse the form data. Please be generally aware of checking
- the content length first in any case before calling this method
- to avoid exhausting server memory.
- If `as_text` is set to `True` the return value will be a decoded
- unicode string.
- .. versionadded:: 0.9
- """
- rv = getattr(self, "_cached_data", None)
- if rv is None:
- if parse_form_data:
- self._load_form_data()
- rv = self.stream.read()
- if cache:
- self._cached_data = rv
- if as_text:
- rv = rv.decode(self.charset, self.encoding_errors)
- return rv
- @cached_property
- def form(self):
- """The form parameters. By default an
- :class:`~werkzeug.datastructures.ImmutableMultiDict`
- is returned from this function. This can be changed by setting
- :attr:`parameter_storage_class` to a different type. This might
- be necessary if the order of the form data is important.
- Please keep in mind that file uploads will not end up here, but instead
- in the :attr:`files` attribute.
- .. versionchanged:: 0.9
- Previous to Werkzeug 0.9 this would only contain form data for POST
- and PUT requests.
- """
- self._load_form_data()
- return self.form
- @cached_property
- def values(self):
- """A :class:`werkzeug.datastructures.CombinedMultiDict` that combines
- :attr:`args` and :attr:`form`."""
- args = []
- for d in self.args, self.form:
- if not isinstance(d, MultiDict):
- d = MultiDict(d)
- args.append(d)
- return CombinedMultiDict(args)
- @cached_property
- def files(self):
- """:class:`~werkzeug.datastructures.MultiDict` object containing
- all uploaded files. Each key in :attr:`files` is the name from the
- ``<input type="file" name="">``. Each value in :attr:`files` is a
- Werkzeug :class:`~werkzeug.datastructures.FileStorage` object.
- It basically behaves like a standard file object you know from Python,
- with the difference that it also has a
- :meth:`~werkzeug.datastructures.FileStorage.save` function that can
- store the file on the filesystem.
- Note that :attr:`files` will only contain data if the request method was
- POST, PUT or PATCH and the ``<form>`` that posted to the request had
- ``enctype="multipart/form-data"``. It will be empty otherwise.
- See the :class:`~werkzeug.datastructures.MultiDict` /
- :class:`~werkzeug.datastructures.FileStorage` documentation for
- more details about the used data structure.
- """
- self._load_form_data()
- return self.files
- @cached_property
- def cookies(self):
- """A :class:`dict` with the contents of all cookies transmitted with
- the request."""
- return parse_cookie(
- self.environ,
- self.charset,
- self.encoding_errors,
- cls=self.dict_storage_class,
- )
- @cached_property
- def headers(self):
- """The headers from the WSGI environ as immutable
- :class:`~werkzeug.datastructures.EnvironHeaders`.
- """
- return EnvironHeaders(self.environ)
- @cached_property
- def path(self):
- """Requested path as unicode. This works a bit like the regular path
- info in the WSGI environment but will always include a leading slash,
- even if the URL root is accessed.
- """
- raw_path = wsgi_decoding_dance(
- self.environ.get("PATH_INFO") or "", self.charset, self.encoding_errors
- )
- return "/" + raw_path.lstrip("/")
- @cached_property
- def full_path(self):
- """Requested path as unicode, including the query string."""
- return self.path + u"?" + to_unicode(self.query_string, self.url_charset)
- @cached_property
- def script_root(self):
- """The root path of the script without the trailing slash."""
- raw_path = wsgi_decoding_dance(
- self.environ.get("SCRIPT_NAME") or "", self.charset, self.encoding_errors
- )
- return raw_path.rstrip("/")
- @cached_property
- def url(self):
- """The reconstructed current URL as IRI.
- See also: :attr:`trusted_hosts`.
- """
- return get_current_url(self.environ, trusted_hosts=self.trusted_hosts)
- @cached_property
- def base_url(self):
- """Like :attr:`url` but without the querystring
- See also: :attr:`trusted_hosts`.
- """
- return get_current_url(
- self.environ, strip_querystring=True, trusted_hosts=self.trusted_hosts
- )
- @cached_property
- def url_root(self):
- """The full URL root (with hostname), this is the application
- root as IRI.
- See also: :attr:`trusted_hosts`.
- """
- return get_current_url(self.environ, True, trusted_hosts=self.trusted_hosts)
- @cached_property
- def host_url(self):
- """Just the host with scheme as IRI.
- See also: :attr:`trusted_hosts`.
- """
- return get_current_url(
- self.environ, host_only=True, trusted_hosts=self.trusted_hosts
- )
- @cached_property
- def host(self):
- """Just the host including the port if available.
- See also: :attr:`trusted_hosts`.
- """
- return get_host(self.environ, trusted_hosts=self.trusted_hosts)
- query_string = environ_property(
- "QUERY_STRING",
- "",
- read_only=True,
- load_func=wsgi_get_bytes,
- doc="The URL parameters as raw bytestring.",
- )
- method = environ_property(
- "REQUEST_METHOD",
- "GET",
- read_only=True,
- load_func=lambda x: x.upper(),
- doc="The request method. (For example ``'GET'`` or ``'POST'``).",
- )
- @cached_property
- def access_route(self):
- """If a forwarded header exists this is a list of all ip addresses
- from the client ip to the last proxy server.
- """
- if "HTTP_X_FORWARDED_FOR" in self.environ:
- addr = self.environ["HTTP_X_FORWARDED_FOR"].split(",")
- return self.list_storage_class([x.strip() for x in addr])
- elif "REMOTE_ADDR" in self.environ:
- return self.list_storage_class([self.environ["REMOTE_ADDR"]])
- return self.list_storage_class()
- @property
- def remote_addr(self):
- """The remote address of the client."""
- return self.environ.get("REMOTE_ADDR")
- remote_user = environ_property(
- "REMOTE_USER",
- doc="""If the server supports user authentication, and the
- script is protected, this attribute contains the username the
- user has authenticated as.""",
- )
- scheme = environ_property(
- "wsgi.url_scheme",
- doc="""
- URL scheme (http or https).
- .. versionadded:: 0.7""",
- )
- @property
- def is_xhr(self):
- """True if the request was triggered via a JavaScript XMLHttpRequest.
- This only works with libraries that support the ``X-Requested-With``
- header and set it to "XMLHttpRequest". Libraries that do that are
- prototype, jQuery and Mochikit and probably some more.
- .. deprecated:: 0.13
- ``X-Requested-With`` is not standard and is unreliable. You
- may be able to use :attr:`AcceptMixin.accept_mimetypes`
- instead.
- """
- warnings.warn(
- "'Request.is_xhr' is deprecated as of version 0.13 and will"
- " be removed in version 1.0. The 'X-Requested-With' header"
- " is not standard and is unreliable. You may be able to use"
- " 'accept_mimetypes' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self.environ.get("HTTP_X_REQUESTED_WITH", "").lower() == "xmlhttprequest"
- is_secure = property(
- lambda self: self.environ["wsgi.url_scheme"] == "https",
- doc="`True` if the request is secure.",
- )
- is_multithread = environ_property(
- "wsgi.multithread",
- doc="""boolean that is `True` if the application is served by a
- multithreaded WSGI server.""",
- )
- is_multiprocess = environ_property(
- "wsgi.multiprocess",
- doc="""boolean that is `True` if the application is served by a
- WSGI server that spawns multiple processes.""",
- )
- is_run_once = environ_property(
- "wsgi.run_once",
- doc="""boolean that is `True` if the application will be
- executed only once in a process lifetime. This is the case for
- CGI for example, but it's not guaranteed that the execution only
- happens one time.""",
- )
- def _assert_not_shallow(request):
- if request.shallow:
- raise RuntimeError(
- "A shallow request tried to consume form data. If you really"
- " want to do that, set `shallow` to False."
- )
|