Repository: pallets/werkzeug

Issue:
json: Return 415 if wrong Content-Type
Amending to #2355:
When a request contains a wrong or no Content-Type, not 400 Bad Request should be returned but more precisely [415 Unsupported Media Type](https://httpwg.org/specs/rfc9110.html#status.415). This greatly helps tracking down the reason of malformed requests.

### Replicate:
Using Flask
```
from flask import Flask, request
app = Flask(__name__)
@app.post('/')
def test():
    return request.json
```
```
curl -i localhost:5000 -d '{"json": "test"}'
HTTP/1.1 400 BAD REQUEST
Server: Werkzeug/2.2.2 Python/3.10.6
Date: Thu, 24 Nov 2022 15:44:58 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 193
Connection: close

<!doctype html>
<html lang=en>
<title>400 Bad Request</title>
<h1>Bad Request</h1>
<p>Did not attempt to load JSON data because the request Content-Type was not &#39;application/json&#39;.</p>
```

### Expected behaviour
```
curl -i localhost:5000 -d '{"json": "test"}'
HTTP/1.1 415 UNSUPPORTED MEDIA TYPE
Server: Werkzeug/2.2.2 Python/3.10.6
Date: Thu, 24 Nov 2022 15:47:46 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 215
Connection: close

<!doctype html>
<html lang=en>
<title>415 Unsupported Media Type</title>
<h1>Unsupported Media Type</h1>
<p>Did not attempt to load JSON data because the request Content-Type was not &#39;application/json&#39;.</p>
```

Environment:

- Python version: 3.10.6
- Werkzeug version: 2.2.2



Discussion:

Resolving patch:
diff --git a/src/werkzeug/wrappers/request.py b/src/werkzeug/wrappers/request.py
--- a/src/werkzeug/wrappers/request.py
+++ b/src/werkzeug/wrappers/request.py
@@ -19,6 +19,7 @@
 from ..wsgi import _get_server
 from ..wsgi import get_input_stream
 from werkzeug.exceptions import BadRequest
+from werkzeug.exceptions import UnsupportedMediaType
 
 if t.TYPE_CHECKING:
     import typing_extensions as te
@@ -532,10 +533,10 @@ def json(self) -> t.Optional[t.Any]:
         Calls :meth:`get_json` with default arguments.
 
         If the request content type is not ``application/json``, this
-        will raise a 400 Bad Request error.
+        will raise a 415 Unsupported Media Type error.
 
-        .. versionchanged:: 2.1
-            Raise a 400 error if the content type is incorrect.
+        .. versionchanged:: 2.2.3
+            Raise a 415 error if the content type is incorrect.
         """
         return self.get_json()
 
@@ -564,7 +565,7 @@ def get_json(
         (: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.
+        raises a 415 Unsupported Media Type resp. 400 Bad Request error.
 
         :param force: Ignore the mimetype and always try to parse JSON.
         :param silent: Silence mimetype and parsing errors, and
@@ -572,8 +573,8 @@ def get_json(
         :param cache: Store the parsed JSON to return for subsequent
             calls.
 
-        .. versionchanged:: 2.1
-            Raise a 400 error if the content type is incorrect.
+        .. versionchanged:: 2.2.3
+            Raise a 415 error if the content type is incorrect.
         """
         if cache and self._cached_json[silent] is not Ellipsis:
             return self._cached_json[silent]
@@ -620,7 +621,7 @@ def on_json_loading_failed(self, e: t.Optional[ValueError]) -> t.Any:
         if e is not None:
             raise BadRequest(f"Failed to decode JSON object: {e}")
 
-        raise BadRequest(
+        raise UnsupportedMediaType(
             "Did not attempt to load JSON data because the request"
             " Content-Type was not 'application/json'."
         )


Test patch:
diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py
--- a/tests/test_wrappers.py
+++ b/tests/test_wrappers.py
@@ -23,6 +23,7 @@
 from werkzeug.exceptions import BadRequest
 from werkzeug.exceptions import RequestedRangeNotSatisfiable
 from werkzeug.exceptions import SecurityError
+from werkzeug.exceptions import UnsupportedMediaType
 from werkzeug.http import COEP
 from werkzeug.http import COOP
 from werkzeug.http import generate_etag
@@ -1350,7 +1351,7 @@ def test_bad_content_type(self):
         value = [1, 2, 3]
         request = wrappers.Request.from_values(json=value, content_type="text/plain")
 
-        with pytest.raises(BadRequest):
+        with pytest.raises(UnsupportedMediaType):
             request.get_json()
 
         assert request.get_json(silent=True) is None