diff --git a/src/h2/utilities.py b/src/h2/utilities.py index 9b492210..7709aa60 100644 --- a/src/h2/utilities.py +++ b/src/h2/utilities.py @@ -88,7 +88,7 @@ def _secure_headers(headers: Iterable[Header], """ for header in headers: assert isinstance(header[0], bytes) - if header[0] in _SECURE_HEADERS or (header[0] in b"cookie" and len(header[1]) < 20): + if header[0] in _SECURE_HEADERS or (header[0] == b"cookie" and len(header[1]) < 20): yield NeverIndexedHeaderTuple(header[0], header[1]) else: yield header @@ -347,7 +347,7 @@ def _reject_pseudo_header_fields(headers: Iterable[Header], msg = f"Received custom pseudo-header field {header[0]!r}" raise ProtocolError(msg) - if header[0] in b":method": + if header[0] == b":method": method = header[1] else: diff --git a/tests/test_header_indexing.py b/tests/test_header_indexing.py index f50f23d8..252083e6 100644 --- a/tests/test_header_indexing.py +++ b/tests/test_header_indexing.py @@ -418,6 +418,19 @@ class TestSecureHeaders: HeaderTuple(b"cookie", b"twenty byte cookie!!"), HeaderTuple(b"Cookie", b"twenty byte cookie!!"), ] + # Headers with names that are substrings of "cookie" but not equal to + # "cookie". Before the fix, the ``in`` operator was used instead of + # ``==``, which caused these to be incorrectly treated as sensitive. + non_cookie_substring_headers = [ + (b"cook", b"short"), + (b"okie", b"short"), + (b"oo", b"short"), + (b"cooki", b"short"), + (b"ookie", b"short"), + HeaderTuple(b"cook", b"short"), + HeaderTuple(b"okie", b"short"), + HeaderTuple(b"cooki", b"short"), + ] server_config = h2.config.H2Configuration(client_side=False) @@ -622,3 +635,27 @@ def test_long_cookie_headers_can_be_indexed_push(self, ) assert c.data_to_send() == expected_frame.serialize() + + @pytest.mark.parametrize( + "headers", [example_request_headers, bytes_example_request_headers], + ) + @pytest.mark.parametrize("header", non_cookie_substring_headers) + def test_non_cookie_substring_headers_can_be_indexed(self, + headers, + header, + frame_factory) -> None: + """ + Headers with names that are substrings of 'cookie' but not equal to + 'cookie' should not be treated as sensitive. + """ + send_headers = [*headers, header] + expected_headers = [*headers, HeaderTuple(header[0], header[1])] + + c = h2.connection.H2Connection() + c.initiate_connection() + + c.clear_outbound_data_buffer() + c.send_headers(1, send_headers) + + f = frame_factory.build_headers_frame(headers=expected_headers) + assert c.data_to_send() == f.serialize() \ No newline at end of file diff --git a/tests/test_utility_functions.py b/tests/test_utility_functions.py index 31634f40..364fe968 100644 --- a/tests/test_utility_functions.py +++ b/tests/test_utility_functions.py @@ -163,6 +163,19 @@ def test_extract_header_method(self) -> None: self.example_headers_with_bytes, ) == b"GET" + def test_extract_method_header_no_false_substring_match(self) -> None: + """ + Headers with names that are substrings of ':method' but not equal + to ':method' should not be matched. + """ + headers = [ + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":me", b"GET"), + ] + assert extract_method_header(headers) is None + def test_size_limit_dict_limit() -> None: dct = SizeLimitDict(size_limit=2)