Skip to content

Commit c40145f

Browse files
committed
1 parent efc8fea commit c40145f

3 files changed

Lines changed: 40 additions & 0 deletions

File tree

‎CHANGELOG.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ dev
1111
- `Stream.end_stream()` now raises `NoSuchStreamError` or `StreamClosedError` exceptions, instead of a generic `KeyError`.
1212
- Duplicate ``content-length`` headers with different values now raise ``ProtocolError``.
1313
Previously, the first ``content-length`` header was accepted and later conflicting values were ignored.
14+
- Parse ``content-length`` headers according to RFC9110 grammar for numbers (1*DIGIT)
1415
- **backfill from v4.3.0** Convert emitted events into Python `dataclass`, which introduces new constructors with required arguments.
1516
Instantiating these events without arguments, as previously commonly used API pattern, will no longer work.
1617

‎src/h2/stream.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,11 @@ def _initialize_content_length(self, headers: Iterable[Header]) -> None:
13681368

13691369
for n, v in headers:
13701370
if n == b"content-length":
1371+
if not v.isdigit():
1372+
# https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length
1373+
# RFC grammar for content-length is 1*DIGIT, so any non-digit value is invalid.
1374+
msg = f"Invalid content-length header: {v!r}"
1375+
raise ProtocolError(msg)
13711376
try:
13721377
parsed_content_length = int(v, 10)
13731378
except ValueError as err:

‎tests/test_invalid_content_lengths.py‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,40 @@ def test_duplicate_matching_content_lengths(self, frame_factory, request_headers
8181
assert isinstance(events[2], h2.events.StreamEnded)
8282
assert c.data_to_send() == b""
8383

84+
@pytest.mark.parametrize(
85+
"value", [
86+
"15a", "15.0", "-15", "+15", " 15", "15 ", "1e2", "0xF", "15_000", "15,000", "NaN", "\x00", "0b1111", "0o17",
87+
("1" * 5000), # https://docs.python.org/3/library/stdtypes.html#int-max-str-digits
88+
],
89+
)
90+
def test_content_length_with_non_digit_value(self, frame_factory, value) -> None:
91+
"""
92+
Remote peers sending content-length with non-digit value causes Protocol
93+
Errors.
94+
"""
95+
c = h2.connection.H2Connection(config=self.server_config)
96+
c.initiate_connection()
97+
c.receive_data(frame_factory.preamble())
98+
c.clear_outbound_data_buffer()
99+
100+
headers = frame_factory.build_headers_frame(
101+
headers=[
102+
*self.example_request_headers_bytes_without_content_length,
103+
("content-length", value),
104+
],
105+
)
106+
with pytest.raises(
107+
h2.exceptions.ProtocolError,
108+
match=f"Invalid content-length header",
109+
):
110+
c.receive_data(headers.serialize())
111+
112+
expected_frame = frame_factory.build_goaway_frame(
113+
last_stream_id=1,
114+
error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR,
115+
)
116+
assert c.data_to_send() == expected_frame.serialize()
117+
84118
@pytest.mark.parametrize(
85119
"request_headers",
86120
[

0 commit comments

Comments
 (0)