From aff5a623e8daeb1d03ef070425336ac4d7205861 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 13:24:18 -1000 Subject: [PATCH 1/9] Fix handling of unsupported upgrades with the pure-python http parser --- aiohttp/http_parser.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py index 28f8edcf09d..d3f99f5a1e2 100644 --- a/aiohttp/http_parser.py +++ b/aiohttp/http_parser.py @@ -225,6 +225,11 @@ def parse_headers( return (CIMultiDictProxy(headers), tuple(raw_headers)) +def is_supported_upgrade(headers: CIMultiDict[str]) -> bool: + """Check if the upgrade header is supported.""" + return headers.get(hdrs.UPGRADE, "").lower() in ("tcp", "websocket") + + class HttpParser(abc.ABC, Generic[_MsgT]): lax: ClassVar[bool] = False @@ -359,9 +364,8 @@ def get_content_length() -> Optional[int]: method and method_must_be_empty_body(method) ) if not empty_body and ( - (length is not None and length > 0) - or msg.chunked - and not msg.upgrade + ((length is not None and length > 0) or msg.chunked) + and not (msg.upgrade and is_supported_upgrade(msg.headers)) ): payload = StreamReader( self.protocol, From ec09619e47d087c3233ef1d3d87ff720c5da6ff4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 13:26:37 -1000 Subject: [PATCH 2/9] Fix handling of unsupported upgrades with the pure-python http parser --- aiohttp/http_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py index d3f99f5a1e2..d39c44dcebf 100644 --- a/aiohttp/http_parser.py +++ b/aiohttp/http_parser.py @@ -225,7 +225,7 @@ def parse_headers( return (CIMultiDictProxy(headers), tuple(raw_headers)) -def is_supported_upgrade(headers: CIMultiDict[str]) -> bool: +def is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool: """Check if the upgrade header is supported.""" return headers.get(hdrs.UPGRADE, "").lower() in ("tcp", "websocket") From 812c5d064b5528f453288effb55da421f53aa324 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 13:31:47 -1000 Subject: [PATCH 3/9] timeline --- CHANGES/8252.bugfix.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 CHANGES/8252.bugfix.rst diff --git a/CHANGES/8252.bugfix.rst b/CHANGES/8252.bugfix.rst new file mode 100644 index 00000000000..e932eb9c7ed --- /dev/null +++ b/CHANGES/8252.bugfix.rst @@ -0,0 +1,2 @@ +Fixed content not being read when an upgrade request was not supported with the pure Python implementation. +-- by :user:`bdraco`. From df9a71c81345712c516f7b8c4e1c3d5e6bb2ff41 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 13:33:32 -1000 Subject: [PATCH 4/9] tcp only needed on client side --- aiohttp/http_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py index d39c44dcebf..2c3fa90f6e4 100644 --- a/aiohttp/http_parser.py +++ b/aiohttp/http_parser.py @@ -227,7 +227,7 @@ def parse_headers( def is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool: """Check if the upgrade header is supported.""" - return headers.get(hdrs.UPGRADE, "").lower() in ("tcp", "websocket") + return headers.get(hdrs.UPGRADE, "").lower() == "websocket" class HttpParser(abc.ABC, Generic[_MsgT]): From 2edb01cb910886dfdcc11b348b24ed8e4b6b7e9f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 13:41:18 -1000 Subject: [PATCH 5/9] protect --- aiohttp/http_parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py index 2c3fa90f6e4..750acd44c7b 100644 --- a/aiohttp/http_parser.py +++ b/aiohttp/http_parser.py @@ -225,7 +225,7 @@ def parse_headers( return (CIMultiDictProxy(headers), tuple(raw_headers)) -def is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool: +def _is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool: """Check if the upgrade header is supported.""" return headers.get(hdrs.UPGRADE, "").lower() == "websocket" @@ -365,7 +365,7 @@ def get_content_length() -> Optional[int]: ) if not empty_body and ( ((length is not None and length > 0) or msg.chunked) - and not (msg.upgrade and is_supported_upgrade(msg.headers)) + and not (msg.upgrade and _is_supported_upgrade(msg.headers)) ): payload = StreamReader( self.protocol, From 56c05041020cdf0fb9afb9391223993e8a1273a0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 13:46:06 -1000 Subject: [PATCH 6/9] merge more --- aiohttp/http_parser.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py index 750acd44c7b..5817a1fdfe9 100644 --- a/aiohttp/http_parser.py +++ b/aiohttp/http_parser.py @@ -352,12 +352,14 @@ def get_content_length() -> Optional[int]: if SEC_WEBSOCKET_KEY1 in msg.headers: raise InvalidHeader(SEC_WEBSOCKET_KEY1) - self._upgraded = msg.upgrade - method = getattr(msg, "method", self.method) # code is only present on responses code = getattr(msg, "code", 0) + self._upgraded = msg.upgrade and _is_supported_upgrade( + msg.headers + ) + assert self.protocol is not None # calculate payload empty_body = status_code_must_be_empty_body(code) or bool( @@ -365,7 +367,7 @@ def get_content_length() -> Optional[int]: ) if not empty_body and ( ((length is not None and length > 0) or msg.chunked) - and not (msg.upgrade and _is_supported_upgrade(msg.headers)) + and not self._upgraded ): payload = StreamReader( self.protocol, From 1b677983dbc0fcecd2825fda01f0755822ede164 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 13:46:39 -1000 Subject: [PATCH 7/9] merge more --- aiohttp/http_parser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py index 5817a1fdfe9..b18140d898f 100644 --- a/aiohttp/http_parser.py +++ b/aiohttp/http_parser.py @@ -352,14 +352,14 @@ def get_content_length() -> Optional[int]: if SEC_WEBSOCKET_KEY1 in msg.headers: raise InvalidHeader(SEC_WEBSOCKET_KEY1) - method = getattr(msg, "method", self.method) - # code is only present on responses - code = getattr(msg, "code", 0) - self._upgraded = msg.upgrade and _is_supported_upgrade( msg.headers ) + method = getattr(msg, "method", self.method) + # code is only present on responses + code = getattr(msg, "code", 0) + assert self.protocol is not None # calculate payload empty_body = status_code_must_be_empty_body(code) or bool( From b806dd1c2f6de0bff77a013cf6386d409b439072 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 Mar 2024 21:40:12 -1000 Subject: [PATCH 8/9] test_upgrade_connection_not_released_after_read expects tcp is a valid upgrade --- aiohttp/http_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py index b18140d898f..59cca547d05 100644 --- a/aiohttp/http_parser.py +++ b/aiohttp/http_parser.py @@ -227,7 +227,7 @@ def parse_headers( def _is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool: """Check if the upgrade header is supported.""" - return headers.get(hdrs.UPGRADE, "").lower() == "websocket" + return headers.get(hdrs.UPGRADE, "").lower() in ("tcp", "websocket") class HttpParser(abc.ABC, Generic[_MsgT]): From 7d96da717a1548122a6933955ebcb0f1172d8f9d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 27 Mar 2024 21:16:26 -1000 Subject: [PATCH 9/9] Update aiohttp/http_parser.py Co-authored-by: Sam Bull --- aiohttp/http_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py index 59cca547d05..39798b604c7 100644 --- a/aiohttp/http_parser.py +++ b/aiohttp/http_parser.py @@ -227,7 +227,7 @@ def parse_headers( def _is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool: """Check if the upgrade header is supported.""" - return headers.get(hdrs.UPGRADE, "").lower() in ("tcp", "websocket") + return headers.get(hdrs.UPGRADE, "").lower() in {"tcp", "websocket"} class HttpParser(abc.ABC, Generic[_MsgT]):