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

Fix too lax application/json prefix-only matching #6180

Merged
merged 9 commits into from
Nov 3, 2021
1 change: 1 addition & 0 deletions CHANGES/6180.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed matching the JSON media type to not accept arbitrary characters after ``application/json`` or the ``+json`` media type suffix.
6 changes: 3 additions & 3 deletions aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -1031,13 +1031,13 @@ async def json(
await self.read()

if content_type:
ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower()
if not is_expected_content_type(ctype, content_type):
if not is_expected_content_type(self.content_type, content_type):
raise ContentTypeError(
self.request_info,
self.history,
message=(
"Attempt to decode JSON with " "unexpected mimetype: %s" % ctype
"Attempt to decode JSON with "
"unexpected mimetype: %s" % self.content_type
),
headers=self.headers,
)
Expand Down
7 changes: 6 additions & 1 deletion aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def iscoroutinefunction(func: Any) -> bool:
return asyncio.iscoroutinefunction(func)


json_re = re.compile(r"(?:application/|[\w.-]+/[\w.+-]+?\+)json", re.IGNORECASE)
json_re = re.compile(r"(?:application/|[\w.-]+/[\w.+-]+?\+)json$", re.IGNORECASE)
asvetlov marked this conversation as resolved.
Show resolved Hide resolved


class BasicAuth(namedtuple("BasicAuth", ["login", "password", "encoding"])):
Expand Down Expand Up @@ -423,6 +423,11 @@ def content_disposition_header(
def is_expected_content_type(
response_content_type: str, expected_content_type: str
) -> bool:
"""Checks if received content type is processable as an expected one.


Both arguments should be given without parameters.
asvetlov marked this conversation as resolved.
Show resolved Hide resolved
"""
if expected_content_type == "application/json":
return json_re.match(response_content_type) is not None
return expected_content_type in response_content_type
Expand Down
6 changes: 3 additions & 3 deletions aiohttp/web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,11 +661,11 @@ async def json(
"""Return BODY as JSON."""
body = await self.text()
if content_type:
ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower()
if not is_expected_content_type(ctype, content_type):
if not is_expected_content_type(self.content_type, content_type):
raise HTTPBadRequest(
text=(
"Attempt to decode JSON with " "unexpected mimetype: %s" % ctype
"Attempt to decode JSON with "
"unexpected mimetype: %s" % self.content_type
)
)

Expand Down
8 changes: 8 additions & 0 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,14 @@ def test_is_expected_content_type_json_non_lowercase():
)


def test_is_expected_content_type_json_trailing_chars():
expected_ct = "application/json"
response_ct = "application/json-seq"
assert not is_expected_content_type(
response_content_type=response_ct, expected_content_type=expected_ct
)


def test_is_expected_content_type_non_json_match_exact():
expected_ct = "text/javascript"
response_ct = "text/javascript"
Expand Down