From edfcde2734e1f8cda5e4b8ef55381a390c68aefd Mon Sep 17 00:00:00 2001 From: Rayan Salhab Date: Sun, 19 Jul 2026 21:36:40 +0300 Subject: [PATCH 1/2] Handle request body decode errors as HTTP 415 (#13137) --- CHANGES/13099.bugfix.rst | 1 + aiohttp/web_request.py | 4 ++-- tests/test_web_request.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 CHANGES/13099.bugfix.rst diff --git a/CHANGES/13099.bugfix.rst b/CHANGES/13099.bugfix.rst new file mode 100644 index 00000000000..3fc027b022a --- /dev/null +++ b/CHANGES/13099.bugfix.rst @@ -0,0 +1 @@ +Fixed ``BaseRequest.text()`` and ``application/x-www-form-urlencoded`` request parsing to return HTTP 415 when body bytes fail to decode with the default charset -- by :user:`cyphercodes`. diff --git a/aiohttp/web_request.py b/aiohttp/web_request.py index 642f4e47387..5d152dedcd2 100644 --- a/aiohttp/web_request.py +++ b/aiohttp/web_request.py @@ -665,7 +665,7 @@ async def text(self) -> str: encoding = self.charset or "utf-8" try: return bytes_body.decode(encoding) - except LookupError: + except (LookupError, UnicodeDecodeError): raise HTTPUnsupportedMediaType() async def json( @@ -790,7 +790,7 @@ async def post(self) -> "MultiDictProxy[str | bytes | FileField]": bytes_query = data.rstrip() try: query = bytes_query.decode(charset) - except LookupError: + except (LookupError, UnicodeDecodeError): raise HTTPUnsupportedMediaType() out.extend( parse_qsl(qs=query, keep_blank_values=True, encoding=charset) diff --git a/tests/test_web_request.py b/tests/test_web_request.py index 9dd919caf09..b518d2a513f 100644 --- a/tests/test_web_request.py +++ b/tests/test_web_request.py @@ -930,6 +930,22 @@ async def test_request_with_wrong_content_type_encoding(protocol: BaseProtocol) assert err.value.status_code == 415 +async def test_request_text_with_invalid_default_encoding( + protocol: BaseProtocol, +) -> None: + payload = StreamReader( + protocol, DEFAULT_CHUNK_SIZE, loop=asyncio.get_running_loop() + ) + payload.feed_data(b"\xff") + payload.feed_eof() + headers = {"Content-Type": "text/html"} + req = make_mocked_request("POST", "/", payload=payload, headers=headers) + + with pytest.raises(web.HTTPUnsupportedMediaType) as err: + await req.text() + assert err.value.status_code == 415 + + async def test_make_too_big_request_same_size_to_max(protocol: BaseProtocol) -> None: payload = StreamReader(protocol, 2**16, loop=asyncio.get_running_loop()) large_file = 1024**2 * b"x" @@ -977,6 +993,24 @@ async def test_multipart_formdata(protocol: BaseProtocol) -> None: assert dict(result) == {"a": "b", "c": "d"} +async def test_urlencoded_form_with_invalid_default_encoding( + protocol: BaseProtocol, +) -> None: + payload = StreamReader( + protocol, DEFAULT_CHUNK_SIZE, loop=asyncio.get_running_loop() + ) + payload.feed_data(b"a=1&b=\xff") + payload.feed_eof() + headers = { + "Content-Type": "application/x-www-form-urlencoded", + } + req = make_mocked_request("POST", "/", payload=payload, headers=headers) + + with pytest.raises(web.HTTPUnsupportedMediaType) as err: + await req.post() + assert err.value.status_code == 415 + + async def test_multipart_formdata_field_missing_name(protocol: BaseProtocol) -> None: # Ensure ValueError is raised when Content-Disposition has no name payload = StreamReader( From 1adc0cd753cd19c627a198495d4b6a974f81bedf Mon Sep 17 00:00:00 2001 From: MOHAMMED HANAN M T P <91409429+hanu-14@users.noreply.github.com> Date: Mon, 20 Jul 2026 04:26:14 +0530 Subject: [PATCH 2/2] Upgrade http:// to https:// in README.rst (#13169) --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index dfd14ae7c0d..0f8790be1f8 100644 --- a/README.rst +++ b/README.rst @@ -59,7 +59,7 @@ To get something from the web: async def main(): async with aiohttp.ClientSession() as session: - async with session.get('http://python.org') as response: + async with session.get('https://python.org') as response: print("Status:", response.status) print("Content-type:", response.headers['content-type']) @@ -134,11 +134,11 @@ External links ============== * `Third party libraries - `_ + `_ * `Built with aiohttp - `_ + `_ * `Powered by aiohttp - `_ + `_ Feel free to make a Pull Request for adding your link to these pages!