From f69522cbfe6191bb60a926b5c313590f00349e9a Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 20 May 2026 01:30:56 +0100 Subject: [PATCH] Fix C parser not rejecting response body when none expected (#10587) --- CHANGES/10587.bugfix.rst | 1 + aiohttp/_http_parser.pyx | 27 ++++++++++++++------------- tests/test_http_parser.py | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 CHANGES/10587.bugfix.rst diff --git a/CHANGES/10587.bugfix.rst b/CHANGES/10587.bugfix.rst new file mode 100644 index 00000000000..f60e1d17c5f --- /dev/null +++ b/CHANGES/10587.bugfix.rst @@ -0,0 +1 @@ +Fixed the C parser failing to reject a response with a body when none was expected -- by :user:`Dreamsorcerer`. diff --git a/aiohttp/_http_parser.pyx b/aiohttp/_http_parser.pyx index cd58d7c242a..b1b642330bd 100644 --- a/aiohttp/_http_parser.pyx +++ b/aiohttp/_http_parser.pyx @@ -504,11 +504,14 @@ cdef class HttpParser: upgrade, chunked) if ( - ULLONG_MAX > self._cparser.content_length > 0 or chunked or - self._cparser.method == cparser.HTTP_CONNECT or - (self._cparser.status_code >= 199 and - self._cparser.content_length == 0 and - self._read_until_eof) + self._response_with_body + and ( + ULLONG_MAX > self._cparser.content_length > 0 or chunked or + self._cparser.method == cparser.HTTP_CONNECT or + (self._cparser.status_code >= 199 and + self._cparser.content_length == 0 and + self._read_until_eof) + ) ): payload = StreamReader( self.protocol, timer=self._timer, loop=self._loop, @@ -520,9 +523,6 @@ cdef class HttpParser: if encoding is not None and self._auto_decompress: self._payload = DeflateBuffer(payload, encoding, max_decompress_size=self._limit) - if not self._response_with_body: - payload = EMPTY_PAYLOAD - self._messages.append((msg, payload)) cdef _on_message_complete(self): @@ -849,8 +849,9 @@ cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1: else: if pyparser._upgraded or pyparser._cparser.method == cparser.HTTP_CONNECT: return 2 - else: - return 0 + if not pyparser._response_with_body: + return 1 + return 0 cdef int cb_on_body(cparser.llhttp_t* parser, @@ -924,7 +925,6 @@ cdef parser_error_from_errno(cparser.llhttp_t* parser, data, pointer): cparser.HPE_CB_MESSAGE_COMPLETE, cparser.HPE_CB_CHUNK_HEADER, cparser.HPE_CB_CHUNK_COMPLETE, - cparser.HPE_INVALID_CONSTANT, cparser.HPE_INVALID_HEADER_TOKEN, cparser.HPE_INVALID_CONTENT_LENGTH, cparser.HPE_INVALID_CHUNK_SIZE, @@ -934,8 +934,9 @@ cdef parser_error_from_errno(cparser.llhttp_t* parser, data, pointer): elif errno == cparser.HPE_INVALID_METHOD: return BadHttpMethod(error=err_msg) elif errno in {cparser.HPE_INVALID_STATUS, - cparser.HPE_INVALID_VERSION}: - return BadStatusLine(error=err_msg) + cparser.HPE_INVALID_VERSION, + cparser.HPE_INVALID_CONSTANT}: + return BadStatusLine(error=f"Bad status line:\n {err_msg}") elif errno == cparser.HPE_INVALID_URL: return InvalidURLError(err_msg) diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py index 147785def1c..bb9ef3393bb 100644 --- a/tests/test_http_parser.py +++ b/tests/test_http_parser.py @@ -2013,6 +2013,20 @@ def test_parse_payload_response_without_body( assert payload.is_eof() +async def test_parse_payload_response_with_invalid_body( + protocol: BaseProtocol, + response_cls: type[HttpResponseParser], +) -> None: + loop = asyncio.get_running_loop() + parser = response_cls(protocol, loop, 2**16, response_with_body=False) + text = ( + b"HTTP/1.1 200 Ok\r\nTransfer-Encoding: chunked\r\n\r\n" + b"7\r\nchunked\r\n0\r\n\r\n" + ) + with pytest.raises(http_exceptions.BadHttpMessage, match="status line"): + parser.feed_data(text)[0][0] + + def test_parse_length_payload(response: HttpResponseParser) -> None: text = b"HTTP/1.1 200 Ok\r\ncontent-length: 4\r\n\r\n" msg, payload = response.feed_data(text)[0][0]