From 032c2c78b274f7cd4b204c40d46678eeea74f719 Mon Sep 17 00:00:00 2001 From: Daw-Ran Liou Date: Thu, 25 May 2017 10:36:26 -0700 Subject: [PATCH 1/2] Allow chunk request Pass request.max_content_length into get_input_stream to always return a LimitedStream if the CONTENT_LENGTH is not set in the environ variable. Client is expected to set the max_content_length to the request object. This patch should fix issue #1094 and #1096. --- werkzeug/wrappers.py | 2 +- werkzeug/wsgi.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/werkzeug/wrappers.py b/werkzeug/wrappers.py index 8676893ea..257826444 100644 --- a/werkzeug/wrappers.py +++ b/werkzeug/wrappers.py @@ -427,7 +427,7 @@ def stream(self): parsing happened. """ _assert_not_shallow(self) - return get_input_stream(self.environ) + return get_input_stream(self.environ, max_content_length=self.max_content_length) input_stream = environ_property('wsgi.input', """ The WSGI input stream. diff --git a/werkzeug/wsgi.py b/werkzeug/wsgi.py index e64d64a78..a7a446cbe 100644 --- a/werkzeug/wsgi.py +++ b/werkzeug/wsgi.py @@ -179,7 +179,7 @@ def get_content_length(environ): pass -def get_input_stream(environ, safe_fallback=True): +def get_input_stream(environ, safe_fallback=True, max_content_length=None): """Returns the input stream from the WSGI environment and wraps it in the most sensible way possible. The stream returned is not the raw WSGI stream in most cases but one that is safe to read from @@ -192,6 +192,8 @@ def get_input_stream(environ, safe_fallback=True): stream as safe fallback or just return the original WSGI input stream if it can't wrap it safely. The default is to return an empty string in those cases. + :param max_content_length: this limits the stream chunk size if the + CONTENT_LENGTH is not in the environ variable. """ stream = environ['wsgi.input'] content_length = get_content_length(environ) @@ -202,15 +204,19 @@ def get_input_stream(environ, safe_fallback=True): if environ.get('wsgi.input_terminated'): return stream - # If we don't have a content length we fall back to an empty stream + # If we don't have a content length nor the max_content_length + # we fall back to an empty stream # in case of a safe fallback, otherwise we return the stream unchanged. # The non-safe fallback is not recommended but might be useful in # some situations. - if content_length is None: + if content_length is None and max_content_length is None: return safe_fallback and _empty_stream or stream # Otherwise limit the stream to the content length - return LimitedStream(stream, content_length) + if max_content_length: + return LimitedStream(stream, min(content_length, max_content_length)) + else: + return LimitedStream(stream, content_length) def get_query_string(environ): From 5c6183fe84950296d3f0590bd5c6ca188331114d Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 9 Jun 2017 12:20:29 -0700 Subject: [PATCH 2/2] clean up docs, add changelog --- CHANGES | 17 +++++++++++------ werkzeug/wrappers.py | 11 +++++++++-- werkzeug/wsgi.py | 30 ++++++++++++++++-------------- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/CHANGES b/CHANGES index 3eb4609ea..e1e1c154d 100644 --- a/CHANGES +++ b/CHANGES @@ -4,11 +4,11 @@ Werkzeug Changelog Version 0.13 ------------ -Released on May 16 2017 +unreleased -- Raise `TypeError` when port is not an integer. -- Fully deprecate `werkzeug.script`. Use `click` - (http://click.pocoo.org) instead. +- Raise ``TypeError`` when port is not an integer. +- Fully deprecate ``werkzeug.script``. Use `Click `_ + instead. - ``response.age`` is parsed as a ``timedelta``. Previously, it was incorrectly treated as a ``datetime``. The header value is an integer number of seconds, not a date string. (``#414``) @@ -21,11 +21,16 @@ Released on May 16 2017 ``BaseResponse`` has a new attribute ``max_cookie_size`` and ``dump_cookie`` has a new argument ``max_size`` to configure this. (`#780`_, `#1109`_) - Fix a TypeError in ``werkzeug.contrib.lint.GuardedIterator.close``. -- `BaseResponse.calculate_content_length` now correctly works for unicode - responses on Python 3. It first encodes using `iter_encoded`. +- ``BaseResponse.calculate_content_length`` now correctly works for unicode + responses on Python 3. It first encodes using ``iter_encoded``. +- ``Request.stream`` is limited to ``Request.max_content_length`` if it is set. + Otherwise, keeps the previous behavior of returning empty streams when + ``Content-Length`` is not set. This allows chunk-encoded requests while + guarding against infinite streams. (`#1126`_) .. _`#780`: https://github.com/pallets/werkzeug/pull/780 .. _`#1109`: https://github.com/pallets/werkzeug/pull/1109 +.. _`#1126`: https://github.com/pallets/werkzeug/pull/1126 Version 0.12.2 diff --git a/werkzeug/wrappers.py b/werkzeug/wrappers.py index 257826444..0f23d98e5 100644 --- a/werkzeug/wrappers.py +++ b/werkzeug/wrappers.py @@ -336,7 +336,7 @@ def want_form_data_parsed(self): return bool(self.environ.get('CONTENT_TYPE')) def make_form_data_parser(self): - """Creates the form data parser. Instanciates the + """Creates the form data parser. Instantiates the :attr:`form_data_parser_class` with some parameters. .. versionadded:: 0.8 @@ -421,13 +421,20 @@ def stream(self): 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.13 + The stream will be limited to :attr:`max_content_length` if the + request does not specify a content length or it is greater than the + configured max. + .. 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, max_content_length=self.max_content_length) + return get_input_stream( + self.environ, max_content_length=self.max_content_length + ) input_stream = environ_property('wsgi.input', """ The WSGI input stream. diff --git a/werkzeug/wsgi.py b/werkzeug/wsgi.py index a7a446cbe..333b9822d 100644 --- a/werkzeug/wsgi.py +++ b/werkzeug/wsgi.py @@ -165,7 +165,7 @@ def get_host(environ, trusted_hosts=None): def get_content_length(environ): """Returns the content length from the WSGI environment as - integer. If it's not available `None` is returned. + integer. If it's not available ``None`` is returned. .. versionadded:: 0.9 @@ -185,15 +185,17 @@ def get_input_stream(environ, safe_fallback=True, max_content_length=None): raw WSGI stream in most cases but one that is safe to read from without taking into account the content length. + .. versionchanged:: 0.13 + Added the ``max_content_length`` parameter. + .. versionadded:: 0.9 :param environ: the WSGI environ to fetch the stream from. - :param safe: indicates whether the function should use an empty - stream as safe fallback or just return the original - WSGI input stream if it can't wrap it safely. The - default is to return an empty string in those cases. - :param max_content_length: this limits the stream chunk size if the - CONTENT_LENGTH is not in the environ variable. + :param safe_fallback: use an empty stream as a safe fallback when neither + the environ content length nor the max is set. Disabling this allows + infinite streams, which can be a denial-of-service risk. + :param max_content_length: if the environ does not set ``Content-Length`` + or it is greater than this value, the stream is limited to this length. """ stream = environ['wsgi.input'] content_length = get_content_length(environ) @@ -204,16 +206,16 @@ def get_input_stream(environ, safe_fallback=True, max_content_length=None): if environ.get('wsgi.input_terminated'): return stream - # If we don't have a content length nor the max_content_length - # we fall back to an empty stream - # in case of a safe fallback, otherwise we return the stream unchanged. - # The non-safe fallback is not recommended but might be useful in - # some situations. + # If the request doesn't specify a content length and there is no max + # length set, returning the stream is potentially dangerous because it + # could be infinite, maliciously or not. If safe_fallback is true, return + # an empty stream for safety instead. if content_length is None and max_content_length is None: return safe_fallback and _empty_stream or stream - # Otherwise limit the stream to the content length - if max_content_length: + # Otherwise limit the stream to the content length or max length, + # whichever is lower. + if max_content_length is not None: return LimitedStream(stream, min(content_length, max_content_length)) else: return LimitedStream(stream, content_length)