Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-h2 for openSUSE:Factory checked in at 2025-08-27 21:34:08 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-h2 (Old) and /work/SRC/openSUSE:Factory/.python-h2.new.30751 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-h2" Wed Aug 27 21:34:08 2025 rev:18 rq:1301436 version:4.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-h2/python-h2.changes 2025-02-11 21:23:28.031380476 +0100 +++ /work/SRC/openSUSE:Factory/.python-h2.new.30751/python-h2.changes 2025-08-27 21:34:50.439086804 +0200 @@ -1,0 +2,18 @@ +Tue Aug 26 11:28:51 UTC 2025 - Nico Krapp <nico.kr...@suse.com> + +- Update to 4.3.0 (fixes CVE-2025-57804, bsc#1248737) + * API Changes (Backward Incompatible) + - Reject header names and values containing illegal characters, based on + RFC 9113, section 8.2.1. The main Python API is compatible, but some + previously valid requests/response headers might now be blocked. Use the + `validate_inbound_headers` config option if needed. Thanks to Sebastiano + Sartor (sebsrt) for the report. + * API Changes (Backward Compatible) + - h2 events now have tighter type bounds, e.g. `stream_id` is guaranteed to + not be `None` for most events now. This simplifies downstream type + checking. + - Various typing-related improvements. + * Bugfixes + - Fix error value when opening a new stream on too many open streams. + +------------------------------------------------------------------- Old: ---- h2-4.2.0.tar.gz New: ---- h2-4.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-h2.spec ++++++ --- /var/tmp/diff_new_pack.Xg5YTj/_old 2025-08-27 21:34:50.987109683 +0200 +++ /var/tmp/diff_new_pack.Xg5YTj/_new 2025-08-27 21:34:50.987109683 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-h2 # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2025 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,11 +16,9 @@ # -%{?!python_module:%define python_module() python3-%{**}} -%define skip_python2 1 %{?sle15_python_module_pythons} Name: python-h2 -Version: 4.2.0 +Version: 4.3.0 Release: 0 Summary: HTTP/2 State-Machine based protocol implementation License: MIT ++++++ h2-4.2.0.tar.gz -> h2-4.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/CHANGELOG.rst new/h2-4.3.0/CHANGELOG.rst --- old/h2-4.2.0/CHANGELOG.rst 2025-02-02 08:41:04.000000000 +0100 +++ new/h2-4.3.0/CHANGELOG.rst 2025-08-23 20:05:56.000000000 +0200 @@ -1,6 +1,26 @@ Release History =============== +4.3.0 (2025-08-23) +------------------ + +**API Changes (Backward Incompatible)** + +- Reject header names and values containing illegal characters, based on RFC 9113, section 8.2.1. + The main Python API is compatible, but some previously valid requests/response headers might now be blocked. + Use the `validate_inbound_headers` config option if needed. + Thanks to Sebastiano Sartor (sebsrt) for the report. + +**API Changes (Backward Compatible)** + +- h2 events now have tighter type bounds, e.g. `stream_id` is guaranteed to not be `None` for most events now. + This simplifies downstream type checking. +- Various typing-related improvements. + +**Bugfixes** + +- Fix error value when opening a new stream on too many open streams. + 4.2.0 (2025-02-01) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/MANIFEST.in new/h2-4.3.0/MANIFEST.in --- old/h2-4.2.0/MANIFEST.in 2025-01-22 22:47:11.000000000 +0100 +++ new/h2-4.3.0/MANIFEST.in 2025-04-30 23:04:28.000000000 +0200 @@ -11,3 +11,4 @@ include README.rst LICENSE CHANGELOG.rst pyproject.toml global-exclude *.pyc *.pyo *.swo *.swp *.map *.yml *.DS_Store +exclude .readthedocs.yaml diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/PKG-INFO new/h2-4.3.0/PKG-INFO --- old/h2-4.2.0/PKG-INFO 2025-02-02 08:41:38.774786700 +0100 +++ new/h2-4.3.0/PKG-INFO 2025-08-23 20:12:14.005698000 +0200 @@ -1,6 +1,6 @@ -Metadata-Version: 2.2 +Metadata-Version: 2.4 Name: h2 -Version: 4.2.0 +Version: 4.3.0 Summary: Pure-Python HTTP/2 protocol implementation Author-email: Cory Benfield <c...@lukasa.co.uk> Maintainer-email: Thomas Kriechbaumer <tho...@kriechbaumer.name> @@ -48,6 +48,7 @@ License-File: LICENSE Requires-Dist: hyperframe<7,>=6.1 Requires-Dist: hpack<5,>=4.1 +Dynamic: license-file ========================= h2: HTTP/2 Protocol Stack @@ -113,7 +114,7 @@ Before you contribute (either by opening an issue or filing a pull request), please `read the contribution guidelines`_. -.. _read the contribution guidelines: http://python-hyper.org/en/latest/contributing.html +.. _read the contribution guidelines: https://python-hyper.org/en/latest/contributing.html License ======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/README.rst new/h2-4.3.0/README.rst --- old/h2-4.2.0/README.rst 2021-06-06 17:01:49.000000000 +0200 +++ new/h2-4.3.0/README.rst 2025-04-30 23:04:28.000000000 +0200 @@ -62,7 +62,7 @@ Before you contribute (either by opening an issue or filing a pull request), please `read the contribution guidelines`_. -.. _read the contribution guidelines: http://python-hyper.org/en/latest/contributing.html +.. _read the contribution guidelines: https://python-hyper.org/en/latest/contributing.html License ======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/pyproject.toml new/h2-4.3.0/pyproject.toml --- old/h2-4.2.0/pyproject.toml 2025-02-01 11:53:05.000000000 +0100 +++ new/h2-4.3.0/pyproject.toml 2025-04-30 23:14:23.000000000 +0200 @@ -73,7 +73,7 @@ "check-manifest==0.50", "readme-renderer==44.0", "build>=1.2.2,<2", - "twine>=5.1.1,<6", + "twine>=6.1.0,<7", "wheel>=0.45.0,<1", ] @@ -183,9 +183,6 @@ """ [tool.tox.env_run_base] -pass_env = [ - "GITHUB_*", -] dependency_groups = ["testing"] commands = [ ["python", "-bb", "-m", "pytest", "--cov-report=xml", "--cov-report=term", "--cov=h2", { replace = "posargs", extend = true }] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/src/h2/__init__.py new/h2-4.3.0/src/h2/__init__.py --- old/h2-4.2.0/src/h2/__init__.py 2025-02-02 08:41:04.000000000 +0100 +++ new/h2-4.3.0/src/h2/__init__.py 2025-08-23 20:05:56.000000000 +0200 @@ -3,4 +3,4 @@ """ from __future__ import annotations -__version__ = "4.2.0" +__version__ = "4.3.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/src/h2/connection.py new/h2-4.3.0/src/h2/connection.py --- old/h2-4.2.0/src/h2/connection.py 2025-01-22 22:59:24.000000000 +0100 +++ new/h2-4.3.0/src/h2/connection.py 2025-02-22 17:40:39.000000000 +0100 @@ -793,8 +793,9 @@ # Check we can open the stream. if stream_id not in self.streams: max_open_streams = self.remote_settings.max_concurrent_streams - if (self.open_outbound_streams + 1) > max_open_streams: - msg = f"Max outbound streams is {max_open_streams}, {self.open_outbound_streams} open" + value = self.open_outbound_streams # take a copy due to the property accessor having side affects + if (value + 1) > max_open_streams: + msg = f"Max outbound streams is {max_open_streams}, {value} open" raise TooManyStreamsError(msg) self.state_machine.process_input(ConnectionInputs.SEND_HEADERS) @@ -1593,8 +1594,9 @@ # stream ID is valid. if frame.stream_id not in self.streams: max_open_streams = self.local_settings.max_concurrent_streams - if (self.open_inbound_streams + 1) > max_open_streams: - msg = f"Max outbound streams is {max_open_streams}, {self.open_outbound_streams} open" + value = self.open_inbound_streams # take a copy due to the property accessor having side affects + if (value + 1) > max_open_streams: + msg = f"Max inbound streams is {max_open_streams}, {value} open" raise TooManyStreamsError(msg) # Let's decode the headers. We handle headers as bytes internally up @@ -1806,9 +1808,7 @@ ) # FIXME: Should we split this into one event per active stream? - window_updated_event = WindowUpdated() - window_updated_event.stream_id = 0 - window_updated_event.delta = frame.window_increment + window_updated_event = WindowUpdated(stream_id=0, delta=frame.window_increment) stream_events = [window_updated_event] frames = [] @@ -1825,9 +1825,9 @@ evt: PingReceived | PingAckReceived if "ACK" in frame.flags: - evt = PingAckReceived() + evt = PingAckReceived(ping_data=frame.opaque_data) else: - evt = PingReceived() + evt = PingReceived(ping_data=frame.opaque_data) # automatically ACK the PING with the same 'opaque data' f = PingFrame(0) @@ -1835,7 +1835,6 @@ f.opaque_data = frame.opaque_data frames.append(f) - evt.ping_data = frame.opaque_data events.append(evt) return frames, events @@ -1974,8 +1973,7 @@ self.config.logger.debug( "Received unknown extension frame (ID %d)", frame.stream_id, ) - event = UnknownFrameReceived() - event.frame = frame + event = UnknownFrameReceived(frame=frame) return [], [event] def _local_settings_acked(self) -> dict[SettingCodes | int, ChangedSetting]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/src/h2/events.py new/h2-4.3.0/src/h2/events.py --- old/h2-4.2.0/src/h2/events.py 2025-01-22 22:59:24.000000000 +0100 +++ new/h2-4.3.0/src/h2/events.py 2025-02-10 21:12:26.000000000 +0100 @@ -11,24 +11,41 @@ from __future__ import annotations import binascii -from typing import TYPE_CHECKING +import sys +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any from .settings import ChangedSetting, SettingCodes, Settings, _setting_code_from_int if TYPE_CHECKING: # pragma: no cover - from hpack import HeaderTuple + from hpack.struct import Header from hyperframe.frame import Frame from .errors import ErrorCodes +if sys.version_info < (3, 10): # pragma: no cover + kw_only: dict[str, bool] = {} +else: # pragma: no cover + kw_only = {"kw_only": True} + + +_LAZY_INIT: Any = object() +""" +Some h2 events are instantiated by the state machine, but its attributes are +subsequently populated by H2Stream. To make this work with strict type annotations +on the events, they are temporarily set to this placeholder value. +This value should never be exposed to users. +""" + + class Event: """ Base class for h2 events. """ - +@dataclass(**kw_only) class RequestReceived(Event): """ The RequestReceived event is fired whenever all of a request's headers @@ -47,31 +64,35 @@ Added ``stream_ended`` and ``priority_updated`` properties. """ - def __init__(self) -> None: - #: The Stream ID for the stream this request was made on. - self.stream_id: int | None = None + stream_id: int + """The Stream ID for the stream this request was made on.""" - #: The request headers. - self.headers: list[HeaderTuple] | None = None + headers: list[Header] = _LAZY_INIT + """The request headers.""" - #: If this request also ended the stream, the associated - #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available - #: here. - #: - #: .. versionadded:: 2.4.0 - self.stream_ended: StreamEnded | None = None - - #: If this request also had associated priority information, the - #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` - #: event will be available here. - #: - #: .. versionadded:: 2.4.0 - self.priority_updated: PriorityUpdated | None = None + stream_ended: StreamEnded | None = None + """ + If this request also ended the stream, the associated + :class:`StreamEnded <h2.events.StreamEnded>` event will be available + here. + + .. versionadded:: 2.4.0 + """ + + priority_updated: PriorityUpdated | None = None + """ + If this request also had associated priority information, the + associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` + event will be available here. + + .. versionadded:: 2.4.0 + """ def __repr__(self) -> str: return f"<RequestReceived stream_id:{self.stream_id}, headers:{self.headers}>" +@dataclass(**kw_only) class ResponseReceived(Event): """ The ResponseReceived event is fired whenever response headers are received. @@ -86,31 +107,35 @@ Added ``stream_ended`` and ``priority_updated`` properties. """ - def __init__(self) -> None: - #: The Stream ID for the stream this response was made on. - self.stream_id: int | None = None + stream_id: int + """The Stream ID for the stream this response was made on.""" - #: The response headers. - self.headers: list[HeaderTuple] | None = None + headers: list[Header] = _LAZY_INIT + """The response headers.""" - #: If this response also ended the stream, the associated - #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available - #: here. - #: - #: .. versionadded:: 2.4.0 - self.stream_ended: StreamEnded | None = None - - #: If this response also had associated priority information, the - #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` - #: event will be available here. - #: - #: .. versionadded:: 2.4.0 - self.priority_updated: PriorityUpdated | None = None + stream_ended: StreamEnded | None = None + """ + If this response also ended the stream, the associated + :class:`StreamEnded <h2.events.StreamEnded>` event will be available + here. + + .. versionadded:: 2.4.0 + """ + + priority_updated: PriorityUpdated | None = None + """ + If this response also had associated priority information, the + associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` + event will be available here. + + .. versionadded:: 2.4.0 + """ def __repr__(self) -> str: return f"<ResponseReceived stream_id:{self.stream_id}, headers:{self.headers}>" +@dataclass(**kw_only) class TrailersReceived(Event): """ The TrailersReceived event is fired whenever trailers are received on a @@ -128,25 +153,28 @@ Added ``stream_ended`` and ``priority_updated`` properties. """ - def __init__(self) -> None: - #: The Stream ID for the stream on which these trailers were received. - self.stream_id: int | None = None + stream_id: int + """The Stream ID for the stream on which these trailers were received.""" - #: The trailers themselves. - self.headers: list[HeaderTuple] | None = None + headers: list[Header] = _LAZY_INIT + """The trailers themselves.""" - #: Trailers always end streams. This property has the associated - #: :class:`StreamEnded <h2.events.StreamEnded>` in it. - #: - #: .. versionadded:: 2.4.0 - self.stream_ended: StreamEnded | None = None - - #: If the trailers also set associated priority information, the - #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` - #: event will be available here. - #: - #: .. versionadded:: 2.4.0 - self.priority_updated: PriorityUpdated | None = None + stream_ended: StreamEnded | None = None + """ + Trailers always end streams. This property has the associated + :class:`StreamEnded <h2.events.StreamEnded>` in it. + + .. versionadded:: 2.4.0 + """ + + priority_updated: PriorityUpdated | None = None + """ + If the trailers also set associated priority information, the + associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` + event will be available here. + + .. versionadded:: 2.4.0 + """ def __repr__(self) -> str: return f"<TrailersReceived stream_id:{self.stream_id}, headers:{self.headers}>" @@ -207,7 +235,7 @@ """ - +@dataclass(**kw_only) class InformationalResponseReceived(Event): """ The InformationalResponseReceived event is fired when an informational @@ -231,25 +259,26 @@ Added ``priority_updated`` property. """ - def __init__(self) -> None: - #: The Stream ID for the stream this informational response was made - #: on. - self.stream_id: int | None = None + stream_id: int + """The Stream ID for the stream this informational response was made on.""" - #: The headers for this informational response. - self.headers: list[HeaderTuple] | None = None + headers: list[Header] = _LAZY_INIT + """The headers for this informational response.""" + + priority_updated: PriorityUpdated | None = None + """ + If this response also had associated priority information, the + associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` + event will be available here. - #: If this response also had associated priority information, the - #: associated :class:`PriorityUpdated <h2.events.PriorityUpdated>` - #: event will be available here. - #: - #: .. versionadded:: 2.4.0 - self.priority_updated: PriorityUpdated | None = None + .. versionadded:: 2.4.0 + """ def __repr__(self) -> str: return f"<InformationalResponseReceived stream_id:{self.stream_id}, headers:{self.headers}>" +@dataclass(**kw_only) class DataReceived(Event): """ The DataReceived event is fired whenever data is received on a stream from @@ -260,25 +289,28 @@ Added ``stream_ended`` property. """ - def __init__(self) -> None: - #: The Stream ID for the stream this data was received on. - self.stream_id: int | None = None + stream_id: int + """The Stream ID for the stream this data was received on.""" + + data: bytes = _LAZY_INIT + """The data itself.""" - #: The data itself. - self.data: bytes | None = None + flow_controlled_length: int = _LAZY_INIT + """ + The amount of data received that counts against the flow control + window. Note that padding counts against the flow control window, so + when adjusting flow control you should always use this field rather + than ``len(data)``. + """ + + stream_ended: StreamEnded | None = None + """ + If this data chunk also completed the stream, the associated + :class:`StreamEnded <h2.events.StreamEnded>` event will be available + here. - #: The amount of data received that counts against the flow control - #: window. Note that padding counts against the flow control window, so - #: when adjusting flow control you should always use this field rather - #: than ``len(data)``. - self.flow_controlled_length: int | None = None - - #: If this data chunk also completed the stream, the associated - #: :class:`StreamEnded <h2.events.StreamEnded>` event will be available - #: here. - #: - #: .. versionadded:: 2.4.0 - self.stream_ended: StreamEnded | None = None + .. versionadded:: 2.4.0 + """ def __repr__(self) -> str: return ( @@ -292,6 +324,7 @@ ) +@dataclass(**kw_only) class WindowUpdated(Event): """ The WindowUpdated event is fired whenever a flow control window changes @@ -301,13 +334,16 @@ the connection), and the delta in the window size. """ - def __init__(self) -> None: - #: The Stream ID of the stream whose flow control window was changed. - #: May be ``0`` if the connection window was changed. - self.stream_id: int | None = None + stream_id: int + """ + The Stream ID of the stream whose flow control window was changed. + May be ``0`` if the connection window was changed. + """ - #: The window delta. - self.delta: int | None = None + delta: int = _LAZY_INIT + """ + The window delta. + """ def __repr__(self) -> str: return f"<WindowUpdated stream_id:{self.stream_id}, delta:{self.delta}>" @@ -367,6 +403,7 @@ ) +@dataclass(**kw_only) class PingReceived(Event): """ The PingReceived event is fired whenever a PING is received. It contains @@ -376,14 +413,14 @@ .. versionadded:: 3.1.0 """ - def __init__(self) -> None: - #: The data included on the ping. - self.ping_data: bytes | None = None + ping_data: bytes + """The data included on the ping.""" def __repr__(self) -> str: return f"<PingReceived ping_data:{_bytes_representation(self.ping_data)}>" +@dataclass(**kw_only) class PingAckReceived(Event): """ The PingAckReceived event is fired whenever a PING acknowledgment is @@ -396,14 +433,14 @@ Removed deprecated but equivalent ``PingAcknowledged``. """ - def __init__(self) -> None: - #: The data included on the ping. - self.ping_data: bytes | None = None + ping_data: bytes + """The data included on the ping.""" def __repr__(self) -> str: return f"<PingAckReceived ping_data:{_bytes_representation(self.ping_data)}>" +@dataclass(**kw_only) class StreamEnded(Event): """ The StreamEnded event is fired whenever a stream is ended by a remote @@ -411,14 +448,14 @@ locally, but no further data or headers should be expected on that stream. """ - def __init__(self) -> None: - #: The Stream ID of the stream that was closed. - self.stream_id: int | None = None + stream_id: int + """The Stream ID of the stream that was closed.""" def __repr__(self) -> str: return f"<StreamEnded stream_id:{self.stream_id}>" +@dataclass(**kw_only) class StreamReset(Event): """ The StreamReset event is fired in two situations. The first is when the @@ -430,16 +467,20 @@ This event is now fired when h2 automatically resets a stream. """ - def __init__(self) -> None: - #: The Stream ID of the stream that was reset. - self.stream_id: int | None = None + stream_id: int + """ + The Stream ID of the stream that was reset. + """ - #: The error code given. Either one of :class:`ErrorCodes - #: <h2.errors.ErrorCodes>` or ``int`` - self.error_code: ErrorCodes | None = None + error_code: ErrorCodes | int = _LAZY_INIT + """ + The error code given. + """ - #: Whether the remote peer sent a RST_STREAM or we did. - self.remote_reset = True + remote_reset: bool = True + """ + Whether the remote peer sent a RST_STREAM or we did. + """ def __repr__(self) -> str: return f"<StreamReset stream_id:{self.stream_id}, error_code:{self.error_code!s}, remote_reset:{self.remote_reset}>" @@ -460,7 +501,7 @@ self.parent_stream_id: int | None = None #: The request headers, sent by the remote party in the push. - self.headers: list[HeaderTuple] | None = None + self.headers: list[Header] | None = None def __repr__(self) -> str: return ( @@ -601,6 +642,7 @@ ) +@dataclass(**kw_only) class UnknownFrameReceived(Event): """ The UnknownFrameReceived event is fired when the remote peer sends a frame @@ -616,9 +658,7 @@ .. versionadded:: 2.7.0 """ - def __init__(self) -> None: - #: The hyperframe Frame object that encapsulates the received frame. - self.frame: Frame | None = None + frame: Frame def __repr__(self) -> str: return "<UnknownFrameReceived>" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/src/h2/frame_buffer.py new/h2-4.3.0/src/h2/frame_buffer.py --- old/h2-4.2.0/src/h2/frame_buffer.py 2025-01-22 22:59:24.000000000 +0100 +++ new/h2-4.3.0/src/h2/frame_buffer.py 2025-04-30 23:02:48.000000000 +0200 @@ -30,7 +30,7 @@ """ def __init__(self, server: bool = False) -> None: - self.data = b"" + self._data = bytearray() self.max_frame_size = 0 self._preamble = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" if server else b"" self._preamble_len = len(self._preamble) @@ -54,7 +54,7 @@ self._preamble_len -= of_which_preamble self._preamble = self._preamble[of_which_preamble:] - self.data += data + self._data += data def _validate_frame_length(self, length: int) -> None: """ @@ -119,18 +119,18 @@ def __next__(self) -> Frame: # First, check that we have enough data to successfully parse the # next frame header. If not, bail. Otherwise, parse it. - if len(self.data) < 9: + if len(self._data) < 9: raise StopIteration try: - f, length = Frame.parse_frame_header(memoryview(self.data[:9])) + f, length = Frame.parse_frame_header(memoryview(self._data[:9])) except (InvalidDataError, InvalidFrameError) as err: # pragma: no cover msg = f"Received frame with invalid header: {err!s}" raise ProtocolError(msg) from err # Next, check that we have enough length to parse the frame body. If # not, bail, leaving the frame header data in the buffer for next time. - if len(self.data) < length + 9: + if len(self._data) < length + 9: raise StopIteration # Confirm the frame has an appropriate length. @@ -138,7 +138,7 @@ # Try to parse the frame body try: - f.parse_body(memoryview(self.data[9:9+length])) + f.parse_body(memoryview(self._data[9:9+length])) except InvalidDataError as err: msg = "Received frame with non-compliant data" raise ProtocolError(msg) from err @@ -148,7 +148,7 @@ # At this point, as we know we'll use or discard the entire frame, we # can update the data. - self.data = self.data[9+length:] + self._data = self._data[9+length:] # Pass the frame through the header buffer. new_frame = self._update_header_buffer(f) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/src/h2/stream.py new/h2-4.3.0/src/h2/stream.py --- old/h2-4.2.0/src/h2/stream.py 2025-01-22 22:59:24.000000000 +0100 +++ new/h2-4.3.0/src/h2/stream.py 2025-04-30 23:14:23.000000000 +0200 @@ -7,7 +7,7 @@ from __future__ import annotations from enum import Enum, IntEnum -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Union, cast from hpack import HeaderTuple from hyperframe.frame import AltSvcFrame, ContinuationFrame, DataFrame, Frame, HeadersFrame, PushPromiseFrame, RstStreamFrame, WindowUpdateFrame @@ -46,7 +46,7 @@ from .windows import WindowManager if TYPE_CHECKING: # pragma: no cover - from collections.abc import Generator, Iterable + from collections.abc import Callable, Generator, Iterable from hpack.hpack import Encoder from hpack.struct import Header, HeaderWeaklyTyped @@ -131,7 +131,7 @@ # How the stream was closed. One of StreamClosedBy. self.stream_closed_by: StreamClosedBy | None = None - def process_input(self, input_: StreamInputs) -> Any: + def process_input(self, input_: StreamInputs) -> list[Event]: """ Process a specific input in the state machine. """ @@ -195,8 +195,7 @@ self.client = False self.headers_received = True - event = RequestReceived() - event.stream_id = self.stream_id + event = RequestReceived(stream_id=self.stream_id) return [event] def response_received(self, previous_state: StreamState) -> list[Event]: @@ -208,11 +207,11 @@ if not self.headers_received: assert self.client is True self.headers_received = True - event = ResponseReceived() + event = ResponseReceived(stream_id=self.stream_id) else: assert not self.trailers_received self.trailers_received = True - event = TrailersReceived() + event = TrailersReceived(stream_id=self.stream_id) event.stream_id = self.stream_id return [event] @@ -224,25 +223,21 @@ if not self.headers_received: msg = "cannot receive data before headers" raise ProtocolError(msg) - event = DataReceived() - event.stream_id = self.stream_id + event = DataReceived(stream_id=self.stream_id) return [event] def window_updated(self, previous_state: StreamState) -> list[Event]: """ Fires when a window update frame is received. """ - event = WindowUpdated() - event.stream_id = self.stream_id - return [event] + return [WindowUpdated(stream_id=self.stream_id)] def stream_half_closed(self, previous_state: StreamState) -> list[Event]: """ Fires when an END_STREAM flag is received in the OPEN state, transitioning this stream to a HALF_CLOSED_REMOTE state. """ - event = StreamEnded() - event.stream_id = self.stream_id + event = StreamEnded(stream_id=self.stream_id) return [event] def stream_ended(self, previous_state: StreamState) -> list[Event]: @@ -250,8 +245,7 @@ Fires when a stream is cleanly ended. """ self.stream_closed_by = StreamClosedBy.RECV_END_STREAM - event = StreamEnded() - event.stream_id = self.stream_id + event = StreamEnded(stream_id=self.stream_id) return [event] def stream_reset(self, previous_state: StreamState) -> list[Event]: @@ -259,9 +253,7 @@ Fired when a stream is forcefully reset. """ self.stream_closed_by = StreamClosedBy.RECV_RST_STREAM - event = StreamReset() - event.stream_id = self.stream_id - return [event] + return [StreamReset(stream_id=self.stream_id)] def send_new_pushed_stream(self, previous_state: StreamState) -> list[Event]: """ @@ -315,21 +307,23 @@ event.parent_stream_id = self.stream_id return [event] - def send_end_stream(self, previous_state: StreamState) -> None: + def send_end_stream(self, previous_state: StreamState) -> list[Event]: """ Called when an attempt is made to send END_STREAM in the HALF_CLOSED_REMOTE state. """ self.stream_closed_by = StreamClosedBy.SEND_END_STREAM + return [] - def send_reset_stream(self, previous_state: StreamState) -> None: + def send_reset_stream(self, previous_state: StreamState) -> list[Event]: """ Called when an attempt is made to send RST_STREAM in a non-closed stream state. """ self.stream_closed_by = StreamClosedBy.SEND_RST_STREAM + return [] - def reset_stream_on_error(self, previous_state: StreamState) -> None: + def reset_stream_on_error(self, previous_state: StreamState) -> list[Event]: """ Called when we need to forcefully emit another RST_STREAM frame on behalf of the state machine. @@ -342,15 +336,16 @@ self.stream_closed_by = StreamClosedBy.SEND_RST_STREAM error = StreamClosedError(self.stream_id) - - event = StreamReset() - event.stream_id = self.stream_id - event.error_code = ErrorCodes.STREAM_CLOSED - event.remote_reset = False - error._events = [event] + error._events = [ + StreamReset( + stream_id=self.stream_id, + error_code=ErrorCodes.STREAM_CLOSED, + remote_reset=False, + ), + ] raise error - def recv_on_closed_stream(self, previous_state: StreamState) -> None: + def recv_on_closed_stream(self, previous_state: StreamState) -> list[Event]: """ Called when an unexpected frame is received on an already-closed stream. @@ -362,7 +357,7 @@ """ raise StreamClosedError(self.stream_id) - def send_on_closed_stream(self, previous_state: StreamState) -> None: + def send_on_closed_stream(self, previous_state: StreamState) -> list[Event]: """ Called when an attempt is made to send data on an already-closed stream. @@ -374,7 +369,7 @@ """ raise StreamClosedError(self.stream_id) - def recv_push_on_closed_stream(self, previous_state: StreamState) -> None: + def recv_push_on_closed_stream(self, previous_state: StreamState) -> list[Event]: """ Called when a PUSH_PROMISE frame is received on a full stop stream. @@ -393,7 +388,7 @@ msg = "Attempted to push on closed stream." raise ProtocolError(msg) - def send_push_on_closed_stream(self, previous_state: StreamState) -> None: + def send_push_on_closed_stream(self, previous_state: StreamState) -> list[Event]: """ Called when an attempt is made to push on an already-closed stream. @@ -429,9 +424,7 @@ msg = "Informational response after final response" raise ProtocolError(msg) - event = InformationalResponseReceived() - event.stream_id = self.stream_id - return [event] + return [InformationalResponseReceived(stream_id=self.stream_id)] def recv_alt_svc(self, previous_state: StreamState) -> list[Event]: """ @@ -473,7 +466,7 @@ # the event and let it get populated. return [AlternativeServiceAvailable()] - def send_alt_svc(self, previous_state: StreamState) -> None: + def send_alt_svc(self, previous_state: StreamState) -> list[Event]: """ Called when sending an ALTSVC frame on this stream. @@ -489,6 +482,7 @@ if self.headers_sent: msg = "Cannot send ALTSVC after sending response headers." raise ProtocolError(msg) + return [] @@ -561,7 +555,10 @@ # (state, input) to tuples of (side_effect_function, end_state). This # map contains all allowed transitions: anything not in this map is # invalid and immediately causes a transition to ``closed``. -_transitions = { +_transitions: dict[ + tuple[StreamState, StreamInputs], + tuple[Callable[[H2StreamStateMachine, StreamState], list[Event]] | None, StreamState], +] = { # State: idle (StreamState.IDLE, StreamInputs.SEND_HEADERS): (H2StreamStateMachine.request_sent, StreamState.OPEN), @@ -1040,10 +1037,11 @@ events = self.state_machine.process_input( StreamInputs.RECV_PUSH_PROMISE, ) - events[0].pushed_stream_id = promised_stream_id + push_event = cast("PushedStreamReceived", events[0]) + push_event.pushed_stream_id = promised_stream_id hdr_validation_flags = self._build_hdr_validation_flags(events) - events[0].headers = self._process_received_headers( + push_event.headers = self._process_received_headers( headers, hdr_validation_flags, header_encoding, ) return [], events @@ -1077,22 +1075,30 @@ input_ = StreamInputs.RECV_HEADERS events = self.state_machine.process_input(input_) + headers_event = cast( + "Union[RequestReceived, ResponseReceived, TrailersReceived, InformationalResponseReceived]", + events[0], + ) if end_stream: es_events = self.state_machine.process_input( StreamInputs.RECV_END_STREAM, ) - events[0].stream_ended = es_events[0] + # We ensured it's not an information response at the beginning of the method. + cast( + "Union[RequestReceived, ResponseReceived, TrailersReceived]", + headers_event, + ).stream_ended = cast("StreamEnded", es_events[0]) events += es_events self._initialize_content_length(headers) - if isinstance(events[0], TrailersReceived) and not end_stream: + if isinstance(headers_event, TrailersReceived) and not end_stream: msg = "Trailers must have END_STREAM set" raise ProtocolError(msg) hdr_validation_flags = self._build_hdr_validation_flags(events) - events[0].headers = self._process_received_headers( + headers_event.headers = self._process_received_headers( headers, hdr_validation_flags, header_encoding, ) return [], events @@ -1106,6 +1112,7 @@ "set to %d", self, end_stream, flow_control_len, ) events = self.state_machine.process_input(StreamInputs.RECV_DATA) + data_event = cast("DataReceived", events[0]) self._inbound_window_manager.window_consumed(flow_control_len) self._track_content_length(len(data), end_stream) @@ -1113,11 +1120,11 @@ es_events = self.state_machine.process_input( StreamInputs.RECV_END_STREAM, ) - events[0].stream_ended = es_events[0] + data_event.stream_ended = cast("StreamEnded", es_events[0]) events.extend(es_events) - events[0].data = data - events[0].flow_controlled_length = flow_control_len + data_event.data = data + data_event.flow_controlled_length = flow_control_len return [], events def receive_window_update(self, increment: int) -> tuple[list[Frame], list[Event]]: @@ -1137,7 +1144,7 @@ # this should be treated as a *stream* error, not a *connection* error. # That means we need to catch the error and forcibly close the stream. if events: - events[0].delta = increment + cast("WindowUpdated", events[0]).delta = increment try: self.outbound_flow_control_window = guard_increment_window( self.outbound_flow_control_window, @@ -1146,13 +1153,14 @@ except FlowControlError: # Ok, this is bad. We're going to need to perform a local # reset. - event = StreamReset() - event.stream_id = self.stream_id - event.error_code = ErrorCodes.FLOW_CONTROL_ERROR - event.remote_reset = False - - events = [event] - frames = self.reset_stream(event.error_code) + events = [ + StreamReset( + stream_id=self.stream_id, + error_code=ErrorCodes.FLOW_CONTROL_ERROR, + remote_reset=False, + ), + ] + frames = self.reset_stream(ErrorCodes.FLOW_CONTROL_ERROR) return frames, events @@ -1220,7 +1228,7 @@ if events: # We don't fire an event if this stream is already closed. - events[0].error_code = _error_code_from_int(frame.error_code) + cast("StreamReset", events[0]).error_code = _error_code_from_int(frame.error_code) return [], events @@ -1322,7 +1330,7 @@ def _process_received_headers(self, headers: Iterable[Header], header_validation_flags: HeaderValidationFlags, - header_encoding: bool | str | None) -> Iterable[Header]: + header_encoding: bool | str | None) -> list[Header]: """ When headers have been received from the remote peer, run a processing pipeline on them to transform them into the appropriate form for diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/src/h2/utilities.py new/h2-4.3.0/src/h2/utilities.py --- old/h2-4.2.0/src/h2/utilities.py 2025-01-22 22:59:24.000000000 +0100 +++ new/h2-4.3.0/src/h2/utilities.py 2025-08-23 19:56:57.000000000 +0200 @@ -7,8 +7,6 @@ from __future__ import annotations import collections -import re -from string import whitespace from typing import TYPE_CHECKING, Any, NamedTuple from hpack.struct import HeaderTuple, NeverIndexedHeaderTuple @@ -20,7 +18,6 @@ from hpack.struct import Header, HeaderWeaklyTyped -UPPER_RE = re.compile(b"[A-Z]") SIGIL = ord(b":") INFORMATIONAL_START = ord(b"1") @@ -70,9 +67,6 @@ _CONNECT_REQUEST_ONLY_HEADERS = frozenset([b":protocol"]) -_WHITESPACE = frozenset(map(ord, whitespace)) - - def _secure_headers(headers: Iterable[Header], hdr_validation_flags: HeaderValidationFlags | None) -> Generator[Header, None, None]: """ @@ -201,13 +195,10 @@ # For example, we avoid tuple unpacking in loops because it represents a # fixed cost that we don't want to spend, instead indexing into the header # tuples. - headers = _reject_empty_header_names( + headers = _reject_illegal_characters( headers, hdr_validation_flags, ) - headers = _reject_uppercase_header_fields( - headers, hdr_validation_flags, - ) - headers = _reject_surrounding_whitespace( + headers = _reject_empty_header_names( headers, hdr_validation_flags, ) headers = _reject_te( @@ -225,53 +216,66 @@ return _check_path_header(headers, hdr_validation_flags) - -def _reject_empty_header_names(headers: Iterable[Header], +def _reject_illegal_characters(headers: Iterable[Header], hdr_validation_flags: HeaderValidationFlags) -> Generator[Header, None, None]: """ - Raises a ProtocolError if any header names are empty (length 0). - While hpack decodes such headers without errors, they are semantically - forbidden in HTTP, see RFC 7230, stating that they must be at least one - character long. + Raises a ProtocolError if any header names or values contain illegal characters. + See <https://www.rfc-editor.org/rfc/rfc9113.html#section-8.2.1>. """ for header in headers: - if len(header[0]) == 0: - msg = "Received header name with zero length." + # > A field name MUST NOT contain characters in the ranges 0x00-0x20, 0x41-0x5a, + # > or 0x7f-0xff (all ranges inclusive). + for c in header[0]: + if 0x41 <= c <= 0x5a: + msg = f"Received uppercase header name {header[0]!r}." + raise ProtocolError(msg) + if c <= 0x20 or c >= 0x7f: + msg = f"Illegal character '{chr(c)}' in header name: {header[0]!r}" + raise ProtocolError(msg) + + # > With the exception of pseudo-header fields (Section 8.3), which have a name + # > that starts with a single colon, field names MUST NOT include a colon (ASCII + # > COLON, 0x3a). + if header[0].find(b":", 1) != -1: + msg = f"Illegal character ':' in header name: {header[0]!r}" raise ProtocolError(msg) - yield header + # For compatibility with RFC 7230 header fields, we need to allow the field + # value to be an empty string. This is ludicrous, but technically allowed. + if field_value := header[1]: + + # > A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed + # > (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position. + for c in field_value: + if c == 0 or c == 0x0a or c == 0x0d: # noqa: PLR1714 + msg = f"Illegal character '{chr(c)}' in header value: {field_value!r}" + raise ProtocolError(msg) + + # > A field value MUST NOT start or end with an ASCII whitespace character + # > (ASCII SP or HTAB, 0x20 or 0x09). + if ( + field_value[0] == 0x20 or + field_value[0] == 0x09 or + field_value[-1] == 0x20 or + field_value[-1] == 0x09 + ): + msg = f"Received header value surrounded by whitespace {field_value!r}" + raise ProtocolError(msg) -def _reject_uppercase_header_fields(headers: Iterable[Header], - hdr_validation_flags: HeaderValidationFlags) -> Generator[Header, None, None]: - """ - Raises a ProtocolError if any uppercase character is found in a header - block. - """ - for header in headers: - if UPPER_RE.search(header[0]): - msg = f"Received uppercase header name {header[0]!r}." - raise ProtocolError(msg) yield header -def _reject_surrounding_whitespace(headers: Iterable[Header], - hdr_validation_flags: HeaderValidationFlags) -> Generator[Header, None, None]: +def _reject_empty_header_names(headers: Iterable[Header], + hdr_validation_flags: HeaderValidationFlags) -> Generator[Header, None, None]: """ - Raises a ProtocolError if any header name or value is surrounded by - whitespace characters. + Raises a ProtocolError if any header names are empty (length 0). + While hpack decodes such headers without errors, they are semantically + forbidden in HTTP, see RFC 7230, stating that they must be at least one + character long. """ - # For compatibility with RFC 7230 header fields, we need to allow the field - # value to be an empty string. This is ludicrous, but technically allowed. - # The field name may not be empty, though, so we can safely assume that it - # must have at least one character in it and throw exceptions if it - # doesn't. for header in headers: - if header[0][0] in _WHITESPACE or header[0][-1] in _WHITESPACE: - msg = f"Received header name surrounded by whitespace {header[0]!r}" - raise ProtocolError(msg) - if header[1] and ((header[1][0] in _WHITESPACE) or - (header[1][-1] in _WHITESPACE)): - msg = f"Received header value surrounded by whitespace {header[1]!r}" + if len(header[0]) == 0: + msg = "Received header name with zero length." raise ProtocolError(msg) yield header diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/src/h2.egg-info/PKG-INFO new/h2-4.3.0/src/h2.egg-info/PKG-INFO --- old/h2-4.2.0/src/h2.egg-info/PKG-INFO 2025-02-02 08:41:38.000000000 +0100 +++ new/h2-4.3.0/src/h2.egg-info/PKG-INFO 2025-08-23 20:12:13.000000000 +0200 @@ -1,6 +1,6 @@ -Metadata-Version: 2.2 +Metadata-Version: 2.4 Name: h2 -Version: 4.2.0 +Version: 4.3.0 Summary: Pure-Python HTTP/2 protocol implementation Author-email: Cory Benfield <c...@lukasa.co.uk> Maintainer-email: Thomas Kriechbaumer <tho...@kriechbaumer.name> @@ -48,6 +48,7 @@ License-File: LICENSE Requires-Dist: hyperframe<7,>=6.1 Requires-Dist: hpack<5,>=4.1 +Dynamic: license-file ========================= h2: HTTP/2 Protocol Stack @@ -113,7 +114,7 @@ Before you contribute (either by opening an issue or filing a pull request), please `read the contribution guidelines`_. -.. _read the contribution guidelines: http://python-hyper.org/en/latest/contributing.html +.. _read the contribution guidelines: https://python-hyper.org/en/latest/contributing.html License ======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/tests/test_events.py new/h2-4.3.0/tests/test_events.py --- old/h2-4.2.0/tests/test_events.py 2025-01-22 22:59:24.000000000 +0100 +++ new/h2-4.3.0/tests/test_events.py 2025-02-10 20:54:58.000000000 +0100 @@ -7,6 +7,7 @@ import inspect import sys +import hyperframe.frame import pytest from hypothesis import given from hypothesis.strategies import integers, lists, tuples @@ -114,9 +115,10 @@ """ RequestReceived has a useful debug representation. """ - e = h2.events.RequestReceived() - e.stream_id = 5 - e.headers = self.example_request_headers + e = h2.events.RequestReceived( + stream_id=5, + headers=self.example_request_headers + ) assert repr(e) == ( "<RequestReceived stream_id:5, headers:[" @@ -130,9 +132,10 @@ """ ResponseReceived has a useful debug representation. """ - e = h2.events.ResponseReceived() - e.stream_id = 500 - e.headers = self.example_response_headers + e = h2.events.ResponseReceived( + stream_id=500, + headers=self.example_response_headers, + ) assert repr(e) == ( "<ResponseReceived stream_id:500, headers:[" @@ -144,9 +147,7 @@ """ TrailersReceived has a useful debug representation. """ - e = h2.events.TrailersReceived() - e.stream_id = 62 - e.headers = self.example_response_headers + e = h2.events.TrailersReceived(stream_id=62, headers=self.example_response_headers) assert repr(e) == ( "<TrailersReceived stream_id:62, headers:[" @@ -158,9 +159,10 @@ """ InformationalResponseReceived has a useful debug representation. """ - e = h2.events.InformationalResponseReceived() - e.stream_id = 62 - e.headers = self.example_informational_headers + e = h2.events.InformationalResponseReceived( + stream_id=62, + headers=self.example_informational_headers, + ) assert repr(e) == ( "<InformationalResponseReceived stream_id:62, headers:[" @@ -172,10 +174,11 @@ """ DataReceived has a useful debug representation. """ - e = h2.events.DataReceived() - e.stream_id = 888 - e.data = b"abcdefghijklmnopqrstuvwxyz" - e.flow_controlled_length = 88 + e = h2.events.DataReceived( + stream_id=888, + data=b"abcdefghijklmnopqrstuvwxyz", + flow_controlled_length=88, + ) assert repr(e) == ( "<DataReceived stream_id:888, flow_controlled_length:88, " @@ -186,9 +189,7 @@ """ WindowUpdated has a useful debug representation. """ - e = h2.events.WindowUpdated() - e.stream_id = 0 - e.delta = 2**16 + e = h2.events.WindowUpdated(stream_id=0, delta=2**16) assert repr(e) == "<WindowUpdated stream_id:0, delta:65536>" @@ -221,8 +222,7 @@ """ PingReceived has a useful debug representation. """ - e = h2.events.PingReceived() - e.ping_data = b"abcdefgh" + e = h2.events.PingReceived(ping_data=b"abcdefgh") assert repr(e) == "<PingReceived ping_data:6162636465666768>" @@ -230,8 +230,7 @@ """ PingAckReceived has a useful debug representation. """ - e = h2.events.PingAckReceived() - e.ping_data = b"abcdefgh" + e = h2.events.PingAckReceived(ping_data=b"abcdefgh") assert repr(e) == "<PingAckReceived ping_data:6162636465666768>" @@ -239,8 +238,7 @@ """ StreamEnded has a useful debug representation. """ - e = h2.events.StreamEnded() - e.stream_id = 99 + e = h2.events.StreamEnded(stream_id=99) assert repr(e) == "<StreamEnded stream_id:99>" @@ -248,10 +246,11 @@ """ StreamEnded has a useful debug representation. """ - e = h2.events.StreamReset() - e.stream_id = 919 - e.error_code = h2.errors.ErrorCodes.ENHANCE_YOUR_CALM - e.remote_reset = False + e = h2.events.StreamReset( + stream_id=919, + error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM, + remote_reset=False, + ) if sys.version_info >= (3, 11): assert repr(e) == ( @@ -363,7 +362,7 @@ """ UnknownFrameReceived has a useful debug representation. """ - e = h2.events.UnknownFrameReceived() + e = h2.events.UnknownFrameReceived(frame=hyperframe.frame.Frame(1)) assert repr(e) == "<UnknownFrameReceived>" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h2-4.2.0/tests/test_invalid_headers.py new/h2-4.3.0/tests/test_invalid_headers.py --- old/h2-4.2.0/tests/test_invalid_headers.py 2025-01-22 22:59:24.000000000 +0100 +++ new/h2-4.3.0/tests/test_invalid_headers.py 2025-08-23 18:49:56.000000000 +0200 @@ -48,6 +48,14 @@ [*base_request_headers, ("name ", "name with trailing space")], [*base_request_headers, ("name", " value with leading space")], [*base_request_headers, ("name", "value with trailing space ")], + [*base_request_headers, ("illegal:characters", "value")], + [*base_request_headers, ("illegal-\r-characters", "value")], + [*base_request_headers, ("illegal-\n-characters", "value")], + [*base_request_headers, ("illegal-\x00-characters", "value")], + [*base_request_headers, ("illegal-\x01-characters", "value")], + [*base_request_headers, ("illegal-characters", "some \r value")], + [*base_request_headers, ("illegal-characters", "some \n value")], + [*base_request_headers, ("illegal-characters", "some \x00 value")], [header for header in base_request_headers if header[0] != ":authority"], [(":protocol", "websocket"), *base_request_headers], @@ -665,7 +673,7 @@ def test_inbound_header_name_length_full_frame_decode(self, frame_factory) -> None: f = frame_factory.build_headers_frame([]) - f.data = b"\x00\x00\x05\x00\x00\x00\x00\x04" + f.data = b"\x00\x00\x01\x04" data = f.serialize() c = h2.connection.H2Connection(config=h2.config.H2Configuration(client_side=False))