diff --git a/CHANGES b/CHANGES index 35f5b0a..98d920d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,18 @@ Werkzeug Changelog ================== + +Version 0.9.3 +------------- + +(bugfix release, released on July 25th 2013) + +- Restored beahvior of the ``data`` descriptor of the request class to pre 0.9 + behavior. This now also means that ``.data`` and ``.get_data()`` have + different behavior. New code should use ``.get_data()`` always. + + In addition to that there is now a flag for the ``.get_data()`` method that + controls what should happen with form data parsing and the form parser will + honor cached data. This makes dealing with custom form data more consistent. Version 0.9.2 ------------- diff --git a/PKG-INFO b/PKG-INFO index 1c88166..81c4341 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: Werkzeug -Version: 0.9.2 +Version: 0.9.3 Summary: The Swiss Army knife of Python web development Home-page: http://werkzeug.pocoo.org/ Author: Armin Ronacher diff --git a/Werkzeug.egg-info/PKG-INFO b/Werkzeug.egg-info/PKG-INFO index 1c88166..81c4341 100644 --- a/Werkzeug.egg-info/PKG-INFO +++ b/Werkzeug.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: Werkzeug -Version: 0.9.2 +Version: 0.9.3 Summary: The Swiss Army knife of Python web development Home-page: http://werkzeug.pocoo.org/ Author: Armin Ronacher diff --git a/setup.py b/setup.py index a8a2069..90a93c2 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ setup( name='Werkzeug', - version='0.9.2', + version='0.9.3', url='http://werkzeug.pocoo.org/', license='BSD', author='Armin Ronacher', diff --git a/werkzeug/__init__.py b/werkzeug/__init__.py index 927fd7c..02238c6 100644 --- a/werkzeug/__init__.py +++ b/werkzeug/__init__.py @@ -20,7 +20,7 @@ from werkzeug._compat import iteritems # the version. Usually set automatically by a script. -__version__ = '0.9.2' +__version__ = '0.9.3' # This import magic raises concerns quite often which is why the implementation diff --git a/werkzeug/testsuite/wrappers.py b/werkzeug/testsuite/wrappers.py index eb74692..87ed1bf 100644 --- a/werkzeug/testsuite/wrappers.py +++ b/werkzeug/testsuite/wrappers.py @@ -367,6 +367,44 @@ req.stream = LowercasingStream(req.stream) self.assert_equal(req.form['foo'], 'hello world') + def test_data_descriptor_triggers_parsing(self): + data = b'foo=Hello+World' + req = wrappers.Request.from_values('/', method='POST', data=data, + content_type='application/x-www-form-urlencoded') + + self.assert_equal(req.data, b'') + self.assert_equal(req.form['foo'], u'Hello World') + + def test_get_data_method_parsing_caching_behavior(self): + data = b'foo=Hello+World' + req = wrappers.Request.from_values('/', method='POST', data=data, + content_type='application/x-www-form-urlencoded') + + # get_data() caches, so form stays available + self.assert_equal(req.get_data(), data) + self.assert_equal(req.form['foo'], u'Hello World') + self.assert_equal(req.get_data(), data) + + # here we access the form data first, caching is bypassed + req = wrappers.Request.from_values('/', method='POST', data=data, + content_type='application/x-www-form-urlencoded') + self.assert_equal(req.form['foo'], u'Hello World') + self.assert_equal(req.get_data(), b'') + + # Another case is uncached get data which trashes everything + req = wrappers.Request.from_values('/', method='POST', data=data, + content_type='application/x-www-form-urlencoded') + self.assert_equal(req.get_data(cache=False), data) + self.assert_equal(req.get_data(cache=False), b'') + self.assert_equal(req.form, {}) + + # Or we can implicitly start the form parser which is similar to + # the old .data behavior + req = wrappers.Request.from_values('/', method='POST', data=data, + content_type='application/x-www-form-urlencoded') + self.assert_equal(req.get_data(parse_form_data=True), b'') + self.assert_equal(req.form['foo'], u'Hello World') + def test_etag_response_mixin(self): response = wrappers.Response('Hello World') self.assert_equal(response.get_etag(), (None, None)) diff --git a/werkzeug/wrappers.py b/werkzeug/wrappers.py index 3e89497..34a149d 100644 --- a/werkzeug/wrappers.py +++ b/werkzeug/wrappers.py @@ -45,7 +45,7 @@ from werkzeug._internal import _get_environ from werkzeug._compat import to_bytes, string_types, text_type, \ integer_types, wsgi_decoding_dance, wsgi_get_bytes, \ - to_unicode, to_native + to_unicode, to_native, BytesIO def _run_wsgi_app(*args): @@ -335,7 +335,8 @@ """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. + stream will be empty afterwards. You can also call this method to + force the parsing of the form data. .. versionadded:: 0.8 """ @@ -350,8 +351,8 @@ content_length = get_content_length(self.environ) mimetype, options = parse_options_header(content_type) parser = self.make_form_data_parser() - data = parser.parse(self.stream, mimetype, - content_length, options) + data = parser.parse(self._get_stream_for_parsing(), + mimetype, content_length, options) else: data = (self.stream, self.parameter_storage_class(), self.parameter_storage_class()) @@ -360,6 +361,18 @@ # 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 @@ -415,9 +428,15 @@ if self.disable_data_descriptor: raise AttributeError('data descriptor is disabled') # XXX: this should eventually be deprecated. - return self.get_data() - - def get_data(self, cache=True, as_text=False): + + # 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`. @@ -426,6 +445,17 @@ 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. @@ -433,6 +463,8 @@ """ 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