diff --git a/AUTHORS.rst b/AUTHORS.rst index 363afc73..b1c8efd1 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -40,3 +40,4 @@ Contributors (chronological) * Xiaoyu Lee * Jonathan Angelo * @zhenhua32 +* Martin Roy diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d0380319..224bb848 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog --------- +5.5.2 (unreleased) +****************** + +Bug fixes: + +* Handle ``UnicodeDecodeError`` when parsing JSON payloads (:issue:`427`). + Thanks :user:`lindycoder` for the catch and patch. + 5.5.1 (2019-09-15) ****************** diff --git a/src/webargs/aiohttpparser.py b/src/webargs/aiohttpparser.py index b36428c4..9e76e05a 100644 --- a/src/webargs/aiohttpparser.py +++ b/src/webargs/aiohttpparser.py @@ -102,6 +102,9 @@ async def parse_json(self, req: Request, name: str, field: Field) -> typing.Any: return core.missing else: return self.handle_invalid_json_error(e, req) + except UnicodeDecodeError as e: + return self.handle_invalid_json_error(e, req) + self._cache["json"] = json_data return core.get_value(json_data, name, field, allow_many_nested=True) @@ -164,7 +167,11 @@ def handle_error( ) def handle_invalid_json_error( - self, error: json.JSONDecodeError, req: Request, *args, **kwargs + self, + error: typing.Union[json.JSONDecodeError, UnicodeDecodeError], + req: Request, + *args, + **kwargs ) -> "typing.NoReturn": error_class = exception_map[400] messages = {"json": ["Invalid JSON body."]} diff --git a/src/webargs/bottleparser.py b/src/webargs/bottleparser.py index a03fb11d..568dc658 100644 --- a/src/webargs/bottleparser.py +++ b/src/webargs/bottleparser.py @@ -47,6 +47,9 @@ def parse_json(self, req, name, field): return core.missing else: return self.handle_invalid_json_error(e, req) + except UnicodeDecodeError as e: + return self.handle_invalid_json_error(e, req) + if json_data is None: return core.missing return core.get_value(json_data, name, field, allow_many_nested=True) diff --git a/src/webargs/core.py b/src/webargs/core.py index b3eec6bc..fe2f39b6 100644 --- a/src/webargs/core.py +++ b/src/webargs/core.py @@ -112,7 +112,14 @@ def get_value(data, name, field, allow_many_nested=False): def parse_json(s, encoding="utf-8"): if isinstance(s, bytes): - s = s.decode(encoding) + try: + s = s.decode(encoding) + except UnicodeDecodeError as e: + raise json.JSONDecodeError( + "Bytes decoding error : {}".format(e.reason), + doc=str(e.object), + pos=e.start, + ) return json.loads(s) diff --git a/src/webargs/testing.py b/src/webargs/testing.py index 2d076842..922bc473 100644 --- a/src/webargs/testing.py +++ b/src/webargs/testing.py @@ -117,6 +117,18 @@ def test_parse_json_with_nonascii_chars(self, testapp): text = u"øˆƒ£ºº∆ƒˆ∆" assert testapp.post_json("/echo", {"name": text}).json == {"name": text} + # https://github.com/marshmallow-code/webargs/issues/427 + def test_parse_json_with_nonutf8_chars(self, testapp): + res = testapp.post( + "/echo", + b"\xfe", + headers={"Accept": "application/json", "Content-Type": "application/json"}, + expect_errors=True, + ) + + assert res.status_code == 400 + assert res.json == {"json": ["Invalid JSON body."]} + def test_validation_error_returns_422_response(self, testapp): res = testapp.post("/echo", {"name": "b"}, expect_errors=True) assert res.status_code == 422 diff --git a/tests/test_falconparser.py b/tests/test_falconparser.py index 7162a0db..d6092c72 100644 --- a/tests/test_falconparser.py +++ b/tests/test_falconparser.py @@ -16,6 +16,18 @@ def test_parse_files(self, testapp): def test_use_args_hook(self, testapp): assert testapp.get("/echo_use_args_hook?name=Fred").json == {"name": "Fred"} + # https://github.com/marshmallow-code/webargs/issues/427 + def test_parse_json_with_nonutf8_chars(self, testapp): + res = testapp.post( + "/echo", + b"\xfe", + headers={"Accept": "application/json", "Content-Type": "application/json"}, + expect_errors=True, + ) + + assert res.status_code == 400 + assert res.json["errors"] == {"json": ["Invalid JSON body."]} + # https://github.com/sloria/webargs/issues/329 def test_invalid_json(self, testapp): res = testapp.post(