@@ -847,6 +847,34 @@ def test_upgrade_header_non_ascii(parser: HttpRequestParser) -> None:
847847 assert not upgrade
848848
849849
850+ @pytest .mark .parametrize (
851+ ("connection" , "expected" ),
852+ [
853+ ("upgrade" , True ),
854+ ("upgrade, keep-alive" , True ), # other tokens alongside upgrade
855+ ("keep-alive, upgrade" , True ), # upgrade not first
856+ ("Upgrade, Keep-Alive" , True ), # case-insensitive
857+ ("keep-alive" , False ), # no upgrade token
858+ ("keep-alive, notupgrade" , False ), # substring is not a token
859+ ],
860+ )
861+ def test_response_upgrade_token_in_connection_list (
862+ response : HttpResponseParser , connection : str , expected : bool
863+ ) -> None :
864+ # RFC 9110 §7.6.1: Connection is a comma-separated token list, so the parser
865+ # must set msg.upgrade for a 101 response whenever "upgrade" appears as a
866+ # token, regardless of position, case, or neighbouring tokens.
867+ text = (
868+ b"HTTP/1.1 101 Switching Protocols\r \n "
869+ b"Upgrade: websocket\r \n "
870+ b"Connection: " + connection .encode () + b"\r \n \r \n "
871+ )
872+ messages , upgrade , tail = response .feed_data (text )
873+ msg = messages [0 ][0 ]
874+ assert msg .upgrade == expected
875+ assert upgrade == expected
876+
877+
850878def test_request_te_chunked_with_content_length (parser : HttpRequestParser ) -> None :
851879 text = (
852880 b"GET /test HTTP/1.1\r \n "
0 commit comments