From ce701319756abef4d09c585828a630b8fdef89fc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 8 Aug 2011 21:47:26 +0200 Subject: [PATCH] If JSON parsing fails it now issues a BadRequest exception. --- CHANGES | 5 ++++- docs/upgrading.rst | 7 +++++++ flask/wrappers.py | 19 ++++++++++++++++--- tests/flask_tests.py | 9 +++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index cc8198d2..c1d33869 100644 --- a/CHANGES +++ b/CHANGES @@ -14,7 +14,7 @@ Relase date to be decided, codename to be chosen. - Empty session cookies are now deleted properly automatically. - View functions can now opt out of getting the automatic OPTIONS implementation. -- HTTP exceptions and Bad Request Key Errors can now be trapped so that they +- HTTP exceptions and Bad Request errors can now be trapped so that they show up normally in the traceback. - Flask in debug mode is now detecting some common problems and tries to warn you about them. @@ -23,6 +23,9 @@ Relase date to be decided, codename to be chosen. feedback when users forget to import view code ahead of time. - Added the ability to register callbacks that are only triggered once at the beginning of the first request. (:meth:`Flask.before_first_request`) +- Malformed JSON data will now trigger a bad request HTTP exception instead + of a value error which usually would result in a 500 internal server + error if not handled. This is a backwards incompatible change. Version 0.7.3 ------------- diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 154b51a7..13b5be71 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -29,6 +29,13 @@ object. With that introduction we moved the implementation details for the session system into a new module called :mod:`flask.sessions`. If you used the previously undocumented session support we urge you to upgrade. +If invalid JSON data was submitted Flask will now raise a +:exc:`~werkzeug.exceptions.BadRequest` exception instead of letting the +default :exc:`ValueError` bubble up. This has the advantage that you no +longer have to handle that error to avoid an internal server error showing +up for the user. If you were catching this down explicitly in the past +as `ValueError` you will need to change this. + Version 0.7 ----------- diff --git a/flask/wrappers.py b/flask/wrappers.py index 73169799..2d3d53f7 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -10,6 +10,7 @@ """ from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase +from werkzeug.exceptions import BadRequest from werkzeug.utils import cached_property from .debughelpers import attach_enctype_error_multidict @@ -96,9 +97,21 @@ class Request(RequestBase): _assert_have_json() if self.mimetype == 'application/json': request_charset = self.mimetype_params.get('charset') - if request_charset is not None: - return json.loads(self.data, encoding=request_charset) - return json.loads(self.data) + try: + if request_charset is not None: + return json.loads(self.data, encoding=request_charset) + return json.loads(self.data) + except ValueError, e: + return self.on_json_loading_failed(e) + + def on_json_loading_failed(self, e): + """Called if decoding of the JSON data failed. The return value of + this method is used by :attr:`json` when an error ocurred. The + default implementation raises a :class:`~werkzeug.exceptions.BadRequest`. + + .. versionadded:: 0.8 + """ + raise BadRequest() def _load_form_data(self): RequestBase._load_form_data(self) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 5e592710..7c1f4b2a 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -984,6 +984,15 @@ class BasicFunctionalityTestCase(unittest.TestCase): class JSONTestCase(unittest.TestCase): + def test_json_bad_requests(self): + app = flask.Flask(__name__) + @app.route('/json', methods=['POST']) + def return_json(): + return unicode(flask.request.json) + c = app.test_client() + rv = c.post('/json', data='malformed', content_type='application/json') + self.assertEqual(rv.status_code, 400) + def test_json_body_encoding(self): app = flask.Flask(__name__) app.testing = True