Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

400 error on bad json content type #2355

Merged
merged 1 commit into from Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -98,6 +98,9 @@ Unreleased
- ``Response.autocorrect_location_header`` is disabled by default.
The ``Location`` header URL will remain relative, and exclude the
scheme and domain, by default. :issue:`2352`
- ``Request.get_json()`` will raise a 400 ``BadRequest`` error if the
``Content-Type`` header is not ``application/json``. This makes a
very common source of confusion more visible. :issue:`2339`


Version 2.0.3
Expand Down
43 changes: 32 additions & 11 deletions src/werkzeug/wrappers/request.py
Expand Up @@ -530,6 +530,12 @@ def json(self) -> t.Optional[t.Any]:
(:mimetype:`application/json`, see :attr:`is_json`).

Calls :meth:`get_json` with default arguments.

If the request content type is not ``application/json``, this
will raise a 400 Bad Request error.

.. versionchanged:: 2.1
Raise a 400 error if the content type is incorrect.
"""
return self.get_json()

Expand All @@ -543,23 +549,28 @@ def get_json(
"""Parse :attr:`data` as JSON.

If the mimetype does not indicate JSON
(:mimetype:`application/json`, see :attr:`is_json`), this
returns ``None``.

If parsing fails, :meth:`on_json_loading_failed` is called and
its return value is used as the return value.
(:mimetype:`application/json`, see :attr:`is_json`), or parsing
fails, :meth:`on_json_loading_failed` is called and
its return value is used as the return value. By default this
raises a 400 Bad Request error.

:param force: Ignore the mimetype and always try to parse JSON.
:param silent: Silence parsing errors and return ``None``
instead.
:param silent: Silence mimetype and parsing errors, and
return ``None`` instead.
:param cache: Store the parsed JSON to return for subsequent
calls.

.. versionchanged:: 2.1
Raise a 400 error if the content type is incorrect.
"""
if cache and self._cached_json[silent] is not Ellipsis:
return self._cached_json[silent]

if not (force or self.is_json):
return None
if not silent:
return self.on_json_loading_failed(None)
else:
return None

data = self.get_data(cache=cache)

Expand All @@ -584,10 +595,20 @@ def get_json(

return rv

def on_json_loading_failed(self, e: ValueError) -> t.Any:
"""Called if :meth:`get_json` parsing fails and isn't silenced.
def on_json_loading_failed(self, e: t.Optional[ValueError]) -> t.Any:
"""Called if :meth:`get_json` fails and isn't silenced.

If this method returns a value, it is used as the return value
for :meth:`get_json`. The default implementation raises
:exc:`~werkzeug.exceptions.BadRequest`.

:param e: If parsing failed, this is the exception. It will be
``None`` if the content type wasn't ``application/json``.
"""
raise BadRequest(f"Failed to decode JSON object: {e}")
if e is not None:
raise BadRequest(f"Failed to decode JSON object: {e}")

raise BadRequest(
"Did not attempt to load JSON data because the request"
" Content-Type was not 'application/json'."
)
10 changes: 7 additions & 3 deletions tests/test_wrappers.py
Expand Up @@ -1346,13 +1346,17 @@ def test_response(self):
)
assert response.json == value

def test_force(self):
def test_bad_content_type(self):
value = [1, 2, 3]
request = wrappers.Request.from_values(json=value, content_type="text/plain")
assert request.json is None

with pytest.raises(BadRequest):
request.get_json()

assert request.get_json(silent=True) is None
assert request.get_json(force=True) == value

def test_silent(self):
def test_bad_data(self):
request = wrappers.Request.from_values(
data=b'{"a":}', content_type="application/json"
)
Expand Down