From 408b1f0517d84256f93a9201739ce1eca95de810 Mon Sep 17 00:00:00 2001 From: Benoit Chesneau Date: Sun, 19 Apr 2026 23:40:58 +0200 Subject: [PATCH 1/2] chore: require gunicorn_h1c >=0.6.4 and drop python_only markers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gunicorn_h1c 0.6.4 ships the RFC 9110/9112 hardening added in h1c #4, #6, and #7: control chars in header values, request-target form/method pairing, and forbidden trailer field-names. All the corresponding fixtures now pass against the C parser, so their python_only markers are removed. The CL list form fixture stays marked — the C parser does not yet reject Content-Length: "5, 5". --- pyproject.toml | 2 +- requirements_test.txt | 2 +- tests/requests/invalid/rfc9110_field_value_ctl_bel_01.py | 1 - tests/requests/invalid/rfc9110_field_value_ctl_del_01.py | 1 - tests/requests/invalid/rfc9110_trailer_forbidden_cl_01.py | 1 - tests/requests/invalid/rfc9110_trailer_forbidden_host_01.py | 2 -- tests/requests/invalid/rfc9110_trailer_forbidden_te_01.py | 1 - .../requests/invalid/rfc9112_target_asterisk_non_options_01.py | 2 -- .../requests/invalid/rfc9112_target_authority_non_connect_01.py | 2 -- tests/requests/invalid/rfc9112_target_relative_01.py | 2 -- 10 files changed, 2 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8638c570..3212111b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ tornado = ["tornado>=6.5.0"] gthread = [] setproctitle = ["setproctitle"] http2 = ["h2>=4.1.0"] -fast = ["gunicorn_h1c>=0.6.3"] +fast = ["gunicorn_h1c>=0.6.4"] testing = [ "gevent>=24.10.1", "eventlet>=0.40.3", diff --git a/requirements_test.txt b/requirements_test.txt index 4d26756a..40e28207 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -3,4 +3,4 @@ coverage pytest>=7.2.0 pytest-cov pytest-asyncio -gunicorn_h1c>=0.6.2 +gunicorn_h1c>=0.6.4 diff --git a/tests/requests/invalid/rfc9110_field_value_ctl_bel_01.py b/tests/requests/invalid/rfc9110_field_value_ctl_bel_01.py index 4203cfe9..abf559d9 100644 --- a/tests/requests/invalid/rfc9110_field_value_ctl_bel_01.py +++ b/tests/requests/invalid/rfc9110_field_value_ctl_bel_01.py @@ -7,4 +7,3 @@ # appear, to prevent log/response injection and parser confusion. from gunicorn.http.errors import InvalidHeader request = InvalidHeader -python_only = True diff --git a/tests/requests/invalid/rfc9110_field_value_ctl_del_01.py b/tests/requests/invalid/rfc9110_field_value_ctl_del_01.py index cd6cd1aa..f992575c 100644 --- a/tests/requests/invalid/rfc9110_field_value_ctl_del_01.py +++ b/tests/requests/invalid/rfc9110_field_value_ctl_del_01.py @@ -6,4 +6,3 @@ # it must not appear in a field-value. from gunicorn.http.errors import InvalidHeader request = InvalidHeader -python_only = True diff --git a/tests/requests/invalid/rfc9110_trailer_forbidden_cl_01.py b/tests/requests/invalid/rfc9110_trailer_forbidden_cl_01.py index b0f8ccae..05bdea9c 100644 --- a/tests/requests/invalid/rfc9110_trailer_forbidden_cl_01.py +++ b/tests/requests/invalid/rfc9110_trailer_forbidden_cl_01.py @@ -6,4 +6,3 @@ # smuggling vector; origin must reject. from gunicorn.http.errors import InvalidHeaderName request = InvalidHeaderName -python_only = True diff --git a/tests/requests/invalid/rfc9110_trailer_forbidden_host_01.py b/tests/requests/invalid/rfc9110_trailer_forbidden_host_01.py index fe897329..3d747dea 100644 --- a/tests/requests/invalid/rfc9110_trailer_forbidden_host_01.py +++ b/tests/requests/invalid/rfc9110_trailer_forbidden_host_01.py @@ -7,5 +7,3 @@ # Content-Length, Transfer-Encoding). Accepting them enables smuggling. from gunicorn.http.errors import InvalidHeaderName request = InvalidHeaderName -# The C parser (gunicorn_h1c) does not yet enforce this rule. -python_only = True diff --git a/tests/requests/invalid/rfc9110_trailer_forbidden_te_01.py b/tests/requests/invalid/rfc9110_trailer_forbidden_te_01.py index caa2f5ac..d2c4c471 100644 --- a/tests/requests/invalid/rfc9110_trailer_forbidden_te_01.py +++ b/tests/requests/invalid/rfc9110_trailer_forbidden_te_01.py @@ -6,4 +6,3 @@ # and must not be accepted. from gunicorn.http.errors import InvalidHeaderName request = InvalidHeaderName -python_only = True diff --git a/tests/requests/invalid/rfc9112_target_asterisk_non_options_01.py b/tests/requests/invalid/rfc9112_target_asterisk_non_options_01.py index cd5bfe62..a3616620 100644 --- a/tests/requests/invalid/rfc9112_target_asterisk_non_options_01.py +++ b/tests/requests/invalid/rfc9112_target_asterisk_non_options_01.py @@ -7,5 +7,3 @@ # rejected as an ill-formed request-line. from gunicorn.http.errors import InvalidRequestLine request = InvalidRequestLine -# The C parser (gunicorn_h1c) does not yet enforce this rule. -python_only = True diff --git a/tests/requests/invalid/rfc9112_target_authority_non_connect_01.py b/tests/requests/invalid/rfc9112_target_authority_non_connect_01.py index 2a91f311..85bfb56a 100644 --- a/tests/requests/invalid/rfc9112_target_authority_non_connect_01.py +++ b/tests/requests/invalid/rfc9112_target_authority_non_connect_01.py @@ -6,5 +6,3 @@ # the CONNECT method. Any other method carrying it must be rejected. from gunicorn.http.errors import InvalidRequestLine request = InvalidRequestLine -# The C parser (gunicorn_h1c) does not yet enforce this rule. -python_only = True diff --git a/tests/requests/invalid/rfc9112_target_relative_01.py b/tests/requests/invalid/rfc9112_target_relative_01.py index ea6bb9e2..8dd816d2 100644 --- a/tests/requests/invalid/rfc9112_target_relative_01.py +++ b/tests/requests/invalid/rfc9112_target_relative_01.py @@ -7,5 +7,3 @@ # like "foo/bar" matches none of these and must be rejected. from gunicorn.http.errors import InvalidRequestLine request = InvalidRequestLine -# The C parser (gunicorn_h1c) does not yet enforce this rule. -python_only = True From 377e8f81f94543d53cbfb4c84d232c26a0501322 Mon Sep 17 00:00:00 2001 From: Benoit Chesneau Date: Sun, 19 Apr 2026 23:48:18 +0200 Subject: [PATCH 2/2] test: skip fast parser on PyPy (gunicorn_h1c C extension is CPython-only) --- tests/conftest.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index c3824279..f1668590 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,6 +20,10 @@ if tests_dir not in sys.path: def http_parser(request): """Parametrize tests over http_parser implementations.""" if request.param == "fast": + # gunicorn_h1c ships as a CPython C extension; it is not reliable + # under PyPy (SIGSEGV observed in CI). Skip the fast parameter there. + if hasattr(sys, "pypy_version_info"): + pytest.skip("gunicorn_h1c not supported on PyPy") gunicorn_h1c = pytest.importorskip("gunicorn_h1c", reason="gunicorn_h1c required") # Require >= 0.6.2 for asgi_headers support if not hasattr(gunicorn_h1c.H1CProtocol, 'asgi_headers'):