From 35c6a2abefa7affd6fef88ff5f402d1b18d5bf2e Mon Sep 17 00:00:00 2001 From: Benoit Chesneau Date: Sun, 3 May 2026 21:18:15 +0200 Subject: [PATCH] test: parametrize smuggling regression across python and fast parsers The previous test forced http_parser='python' to avoid a hard dependency on gunicorn_h1c. Now run the same scenario under both parser implementations so the smuggling guard is exercised on every supported request-line/header path. --- tests/test_asgi_keepalive_smuggling.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/test_asgi_keepalive_smuggling.py b/tests/test_asgi_keepalive_smuggling.py index ecd7ab70..b520ae51 100644 --- a/tests/test_asgi_keepalive_smuggling.py +++ b/tests/test_asgi_keepalive_smuggling.py @@ -17,6 +17,7 @@ gate can read it. See the commit that added this test for the fix. """ import asyncio +import sys import pytest @@ -83,12 +84,11 @@ class _Log: return False -def _build_worker(loop, app): +def _build_worker(loop, app, http_parser): cfg = Config() cfg.set('keepalive', 2) cfg.set('timeout', 30) - # Force the Python parser so the test does not depend on gunicorn_h1c. - cfg.set('http_parser', 'python') + cfg.set('http_parser', http_parser) class _W: pass @@ -104,14 +104,29 @@ def _build_worker(loop, app): return w +@pytest.fixture(params=["python", "fast"]) +def http_parser(request): + """Parametrize the smuggling test across both parser implementations.""" + if request.param == "fast": + if hasattr(sys, "pypy_version_info"): + pytest.skip("gunicorn_h1c not supported on PyPy") + gunicorn_h1c = pytest.importorskip("gunicorn_h1c") + if not hasattr(gunicorn_h1c.H1CProtocol, "asgi_headers"): + pytest.skip("gunicorn_h1c >= 0.6.2 required") + return request.param + + @pytest.mark.asyncio -async def test_keepalive_refused_when_first_body_is_partial(): +async def test_keepalive_refused_when_first_body_is_partial(http_parser): """Two pipelined requests on the same connection. The first POST advertises Content-Length: 100 but the client only sends 10 body bytes. The app returns 200 without consuming the body. The transport MUST close instead of serving a second response from the residual bytes (which would be the second request the attacker pipelined behind the short body). + + Run under both the Python parser and the C parser (gunicorn_h1c) so + the smuggling guard is verified end-to-end on every supported path. """ async def app(scope, receive, send): @@ -127,7 +142,7 @@ async def test_keepalive_refused_when_first_body_is_partial(): }) loop = asyncio.get_event_loop() - worker = _build_worker(loop, app) + worker = _build_worker(loop, app, http_parser) protocol = ASGIProtocol(worker) transport = _FakeTransport()