Skip to content

Commit

Permalink
🐛 Check Content-Type request header before assuming JSON (#2118)
Browse files Browse the repository at this point in the history
Co-authored-by: Patrick Wang <patrickkwang@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
  • Loading branch information
3 people authored Jun 7, 2021
1 parent 90120dd commit fa7e3c9
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 12 deletions.
19 changes: 16 additions & 3 deletions fastapi/routing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import email.message
import enum
import inspect
import json
Expand Down Expand Up @@ -36,7 +37,7 @@
)
from pydantic import BaseModel
from pydantic.error_wrappers import ErrorWrapper, ValidationError
from pydantic.fields import ModelField
from pydantic.fields import ModelField, Undefined
from starlette import routing
from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException
Expand Down Expand Up @@ -174,14 +175,26 @@ def get_request_handler(

async def app(request: Request) -> Response:
try:
body = None
body: Any = None
if body_field:
if is_body_form:
body = await request.form()
else:
body_bytes = await request.body()
if body_bytes:
body = await request.json()
json_body: Any = Undefined
content_type_value = request.headers.get("content-type")
if content_type_value:
message = email.message.Message()
message["content-type"] = content_type_value
if message.get_content_maintype() == "application":
subtype = message.get_content_subtype()
if subtype == "json" or subtype.endswith("+json"):
json_body = await request.json()
if json_body != Undefined:
body = json_body
else:
body = body_bytes
except json.JSONDecodeError as e:
raise RequestValidationError([ErrorWrapper(e, ("body", e.pos))], body=e.doc)
except Exception as e:
Expand Down
84 changes: 75 additions & 9 deletions tests/test_tutorial/test_body/test_tutorial001.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,25 +173,91 @@ def test_post_body(path, body, expected_status, expected_response):


def test_post_broken_body():
response = client.post("/items/", data={"name": "Foo", "price": 50.5})
response = client.post(
"/items/",
headers={"content-type": "application/json"},
data="{some broken json}",
)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body", 1],
"msg": "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
"type": "value_error.jsondecode",
"ctx": {
"colno": 1,
"doc": "name=Foo&price=50.5",
"msg": "Expecting property name enclosed in double quotes",
"doc": "{some broken json}",
"pos": 1,
"lineno": 1,
"msg": "Expecting value",
"pos": 0,
"colno": 2,
},
"loc": ["body", 0],
"msg": "Expecting value: line 1 column 1 (char 0)",
"type": "value_error.jsondecode",
}
]
}


def test_post_form_for_json():
response = client.post("/items/", data={"name": "Foo", "price": 50.5})
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"loc": ["body"],
"msg": "value is not a valid dict",
"type": "type_error.dict",
}
]
}


def test_explicit_content_type():
response = client.post(
"/items/",
data='{"name": "Foo", "price": 50.5}',
headers={"Content-Type": "application/json"},
)
assert response.status_code == 200, response.text


def test_geo_json():
response = client.post(
"/items/",
data='{"name": "Foo", "price": 50.5}',
headers={"Content-Type": "application/geo+json"},
)
assert response.status_code == 200, response.text


def test_wrong_headers():
data = '{"name": "Foo", "price": 50.5}'
invalid_dict = {
"detail": [
{
"loc": ["body"],
"msg": "value is not a valid dict",
"type": "type_error.dict",
}
]
}

response = client.post("/items/", data=data, headers={"Content-Type": "text/plain"})
assert response.status_code == 422, response.text
assert response.json() == invalid_dict

response = client.post(
"/items/", data=data, headers={"Content-Type": "application/geo+json-seq"}
)
assert response.status_code == 422, response.text
assert response.json() == invalid_dict
response = client.post(
"/items/", data=data, headers={"Content-Type": "application/not-really-json"}
)
assert response.status_code == 422, response.text
assert response.json() == invalid_dict


def test_other_exceptions():
with patch("json.loads", side_effect=Exception):
response = client.post("/items/", json={"test": "test2"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "There was an error parsing the body"}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def test_gzip_request(compress):
if compress:
data = gzip.compress(data)
headers["Content-Encoding"] = "gzip"
headers["Content-Type"] = "application/json"
response = client.post("/sum", data=data, headers=headers)
assert response.json() == {"sum": n}

Expand Down

0 comments on commit fa7e3c9

Please sign in to comment.