2000 Commits

Author SHA1 Message Date
Benoit Chesneau
0a736ea4a2 fix: keep _body_receiver alive across the keepalive smuggling gate
The smuggling guard added in #3614 reads self._body_receiver after
_handle_http_request returns to refuse keepalive on a body that did
not finish framing.  But _handle_http_request's finally cleared the
receiver before returning, so the gate always saw None and let
keepalive proceed unconditionally.  Move the clear into the
connection loop's per-iteration cleanup (it already had one there
for the same purpose).

Adds an end-to-end regression test that pipelines a partial-body POST
followed by a smuggled GET and asserts the second request is not
served and the transport closes.
2026-05-03 21:03:49 +02:00
Benoit Chesneau
fe3655b9d3 refactor: split BodyReceiver._closed into transport vs body-wait
_closed now means the client transport has gone away. Body-wait timeouts
flip a separate _body_wait_expired flag. Both still surface as
http.disconnect to the app, but downstream code can now distinguish 'the
socket is dead' from 'the body never finished framing in time' without
guessing which path set the flag.
2026-05-03 20:42:55 +02:00
Benoit Chesneau
0d35d2ae44 fix: warn once when ASGI app emits a body for a no-body response
A framework bug — say, returning bytes from a HEAD or 204 handler — is
now logged at WARNING level the first time it happens for a request
so the misbehavior is visible without spamming on multi-chunk streams.
2026-05-03 20:25:23 +02:00
Benoit Chesneau
2191832b8d fix: drop body framing on HEAD/204/304 even when framework set it
RFC 9110 forbids a body for HEAD requests and for 1xx/204/304 status
codes. PR #3614 stopped gunicorn from auto-applying chunked encoding
in those cases, but if the application explicitly emitted a
Content-Length or Transfer-Encoding header (and possibly body bytes),
gunicorn still passed them through. Now strip both headers, force
plain framing, and discard any body the app emits.
2026-05-03 20:17:00 +02:00
Benoit Chesneau
8e25cb2400 fix: tighten keepalive gate and scope finish_body byte cap
- ASGI keepalive gate now keys on receiver._complete only. _closed is
  overloaded across transport disconnect and receive timeout; treating
  either as 'message complete' would re-enable the smuggling vector
  the previous PR was meant to close.
- Parser.finish_body's 64 KiB byte cap now applies only when an explicit
  deadline is given. Default invocations (notably __next__, used by
  base_async / sync workers) regain the prior unbounded drain so a
  partial drain does not silently desync the next request.
2026-05-03 18:37:45 +02:00
Benoit Chesneau
6f9ed30d23 lint: use dict literal and hoist mock import 2026-05-03 18:23:45 +02:00
Benoit Chesneau
e90b1c2c1e fix: address six WSGI/ASGI parser and protocol findings
- WSGI fast parser now applies the same per-header policy as the Python
  parser (Expect, secure_scheme_headers, forwarded_allow_ips trust gate,
  forwarder_headers / header_map). Shared helpers extracted on Message.

- ASGI keepalive no longer resets the parser when the previous request
  body was not fully framed; the connection closes instead, preventing
  request smuggling on pipelined connections.

- BodyReceiver._wait_for_data timeout flips _closed and yields
  http.disconnect rather than synthesizing more_body=False. Timeout
  honors cfg.timeout.

- ASGI chunked encoding now skips HEAD, 204, and 304 (matches
  Response.is_chunked in the WSGI path) via a small helper.

- _setup_callback_parser passes proxy_protocol to PythonProtocol; auto
  falls back to the Python parser when proxy_protocol != off (the C
  parser does not implement PROXY framing). _effective_peername swaps
  the transport peer with the PROXY-supplied client address.

- Parser.finish_body accepts a deadline and a 64KiB byte cap; gthread
  passes a deadline and abandons keepalive on incomplete drain so a
  stalled client cannot tie up a worker thread.
2026-05-03 18:19:08 +02:00
Benoit Chesneau
2073e13dc8 fix: reject control characters in header field-value (RFC 9110 section 5.5)
field-vchar = VCHAR / obs-text; only SP and HTAB are permitted beyond
that. Previous validation only caught NUL/CR/LF, leaving BEL, DEL, FF,
and other C0/C1 controls accepted — a log/response injection risk. Now
rejected across the WSGI and ASGI Python parsers.
2026-04-19 12:07:16 +02:00
Benoit Chesneau
a9270e3f9a fix: reject forbidden trailer field-names (RFC 9110 section 6.5.1)
Host, Content-Length, Transfer-Encoding, Trailer, Authorization, and TE
are not allowed in trailer sections; accepting them enables smuggling
and routing confusion. Both WSGI and ASGI Python parsers now raise
InvalidHeaderName when any of these appears in a trailer.
2026-04-19 11:41:00 +02:00
Benoit Chesneau
882e636208 fix: reject authority-form request-target outside CONNECT (RFC 9112 section 3.2.3)
Detect authority-form as a request-target that is neither origin-form
(starts with "/"), absolute-form (contains "://"), nor asterisk; reject
it for any method other than CONNECT. Both WSGI and ASGI Python parsers.
2026-04-19 11:11:42 +02:00
Benoit Chesneau
82d33d4c71 fix: reject asterisk-form request-target outside OPTIONS (RFC 9112 section 3.2.4)
The Python WSGI and ASGI parsers both accepted `GET *` and similar; RFC
9112 restricts asterisk-form to OPTIONS. Both now raise InvalidRequestLine.

The fast (C) parser in gunicorn_h1c does not yet enforce this, so the
fixture is marked python_only via a new sidecar flag honored by the WSGI
and ASGI invalid-request harnesses.
2026-04-19 10:43:01 +02:00
Benoit Chesneau
5de593708f
Merge pull request #3590 from benoitc/fix/graceful-connection-close
fix: drain connection on close per RFC 9112 section 9.6
2026-04-19 09:57:16 +02:00
Benoit Chesneau
9d422c3ef0 fix: drain connection on close per RFC 9112 section 9.6
Avoids TCP RST truncating the response tail when unread request data
(body, pipelined bytes, trailers) sits in the kernel recv buffer at
close time. Half-closes write, linger-reads (bounded 2s / 64 KB),
then closes.
2026-04-19 09:41:07 +02:00
Ran
38ea12629f Pass only the header name to InvalidHeader exception
Per @pajod review: the invalid header value may carry sensitive
content, and raising it through the exception could leak it
across security boundaries (browsers/proxies handling response
splitting errors). Pass just the name instead.
2026-04-17 06:11:57 +08:00
ran
7ae6503dea fix: validate headers in early_hints callback to match process_headers
The early_hints callback constructs 103 Early Hints responses without
any header validation, while process_headers validates against TOKEN_RE
and HEADER_VALUE_RE for normal responses. This inconsistency means a
WSGI app passing unsanitized data to wsgi.early_hints could enable
HTTP response splitting via CRLF injection.

Apply the same TOKEN_RE/HEADER_VALUE_RE checks from process_headers to
the early_hints callback for defense-in-depth consistency.

Closes #3585
2026-04-13 17:21:24 +08:00
Benoit Chesneau
725c03e8ff Fix WebSocket binary send when text key is None
ASGI allows websocket.send messages to contain both text and bytes
keys where one is None. Check for truthy values instead of key existence.
2026-04-03 23:53:03 +02:00
Benoit Chesneau
65ba40b243 Update Docker setup to install gunicorn from local source
This allows testing local changes to gunicorn in the E2E test suite.
Previously containers were installing from GitHub master branch.

Also updates compatibility grid with latest test results (417/444, 93%).
2026-04-03 22:03:39 +02:00
Benoit Chesneau
cf92b2317a Fix duplicate Transfer-Encoding header for BlackSheep streaming
When frameworks like BlackSheep set Transfer-Encoding: chunked on
streaming responses, gunicorn was adding a second header without
checking if one already exists. This caused httpcore to reject the
response with "multiple Transfer-Encoding headers" error.

Fix checks for existing Transfer-Encoding header before adding one,
while still enabling chunked body encoding when the framework sets it.
2026-04-03 21:10:23 +02:00
Benoit Chesneau
51d350a212 Close transport after WebSocket close handshake completes 2026-04-03 16:34:18 +02:00
Benoit Chesneau
3fc9a2f002 Fix WebSocket close handshake to comply with RFC 6455
- Add _close_sent, _close_received, _close_event state variables
- Server now waits for client's close frame response before marking
  connection as closed (5s timeout)
- Update _read_frames loop to continue reading after sending close
- Fix tests to simulate client close frame response
2026-04-03 14:53:36 +02:00
Benoit Chesneau
47bd20a7cb Fix HTTP 100 Continue adding Transfer-Encoding: chunked
Skip adding Transfer-Encoding: chunked for 1xx informational
responses per RFC 9110 Section 15.2.
2026-04-03 13:57:49 +02:00
Benoit Chesneau
4e9db71aeb
Merge pull request #3568 from benleembruggen/fix/h2-stream-ended-body-complete
fix: HTTP/2 ASGI body duplication in async_connection.py
2026-04-03 01:51:02 +02:00
Benoit Chesneau
7953c2585b Fix ASGI disconnect handling for Django-style apps
BodyReceiver.receive() now blocks after body is finished until actual
disconnect, instead of returning http.disconnect immediately. This fixes
Django's listen_for_disconnect task thinking client disconnected early.

Adds regression tests for the fix.

Fixes #3484
2026-04-02 23:55:27 +02:00
Ben Leembruggen
bcb13b1e74 Fix _handle_stream_ended to set _body_complete in async HTTP/2 handler
_handle_stream_ended() in async_connection.py (used by the ASGI worker)
did not set stream._body_complete = True or signal stream._body_event.
This caused the receive() closure in protocol.py to never see the body
as complete via the streaming path, so on the next call the fast path
re-read the entire body from BytesIO, doubling it.

The sync handler in connection.py already had a partial fix from #3559
but was also missing _body_event signalling, which is needed to unblock
any pending read_body_chunk() await.

Fixes https://github.com/benoitc/gunicorn/discussions/3567
2026-04-01 11:53:06 +11:00
Benoit Chesneau
3e2167c346
Add InvalidChunkExtension mapping and fast parser support for ASGI tests (#3565)
* Add InvalidChunkExtension to treq_asgi.py and fast parser support

- Add InvalidChunkExtension import and exception mapping for proper test
  coverage of bare CR rejection in chunk extensions per RFC 9112 7.1.1
- Add fast parser (H1CProtocol) support to treq_asgi.py and the ASGI
  invalid request tests
- Fast parser now receives limit configuration (limit_request_line,
  limit_request_fields, limit_request_field_size)
- Handle gunicorn_h1c's multiple ParseError classes from different modules
- Skip tests where fast parser has different validation than Python parser

* Handle gunicorn_h1c limit exceptions in ASGI protocol

Add handling for gunicorn_h1c.LimitRequestLine and
gunicorn_h1c.LimitRequestHeaders exceptions, matching the behavior
of the Python parser exceptions with appropriate HTTP status codes:
- LimitRequestLine: 414 URI Too Long
- LimitRequestHeaders: 431 Request Header Fields Too Large

* Refactor data_received to fix too-many-return-statements lint
2026-03-31 03:07:56 +02:00
Benoit Chesneau
2a15fdb93a Fix pylint isinstance-second-argument-not-valid-type warning 2026-03-26 23:45:38 +01:00
Benoit Chesneau
8d08aaa2cb Fix --limit-request-line 0 to mean unlimited
Per documentation, limit_request_line=0 means unlimited. The code was
incorrectly treating 0 as "use default max" by checking <= 0 instead
of < 0.

For the fast C parser (gunicorn_h1c), which doesn't support 0 as
unlimited, pass a large value (1MB) instead. This applies to both
WSGI workers (http/message.py) and ASGI workers (asgi/protocol.py).

Fixes #3563
2026-03-26 23:42:14 +01:00
Benoit Chesneau
da8bd4850a Remove unused AsyncRequest class
AsyncRequest was the legacy pull-based async HTTP parser, now replaced
by the push-based CallbackRequest/PythonProtocol. Remove the unused
code and associated tests.
2026-03-26 16:08:35 +01:00
Benoit Chesneau
b00f125755 Integrate gunicorn_h1c 0.6.3 with InvalidChunkExtension support
Update to gunicorn_h1c >= 0.6.3 which adds InvalidChunkExtension
validation for rejecting chunk extensions with bare CR bytes per
RFC 9112.

Changes:
- Update pyproject.toml to require gunicorn_h1c >= 0.6.3
- Add InvalidChunkExtension exception to gunicorn/asgi/parser.py
- Handle InvalidChunkExtension from both Python and C parsers in protocol.py
- Add chunk extension validation tests
- Update treq.py badrequest class to support hex escapes
2026-03-26 15:46:51 +01:00
Benoit Chesneau
bdb2ebd5a4 Reject chunk extensions with bare CR bytes (RFC 9112)
Both WSGI and ASGI parsers now validate that chunk extensions
do not contain bare CR characters, which are not allowed per
RFC 9112.

Fixes #3556
2026-03-26 15:45:48 +01:00
Benoit Chesneau
d43acb8fe0 Update to gunicorn_h1c >= 0.6.2 for asgi_headers support
- Use asgi_headers property (lowercase names) from fast parser
- Bump version to 25.3.0
- Update changelog with all changes for this release
2026-03-26 15:45:11 +01:00
Benoit Chesneau
cbd27e82a2
Merge pull request #3559 from benleembruggen/fix/http2-asgi-body-duplication
fix: prevent HTTP/2 ASGI body duplication in receive()
2026-03-26 14:38:53 +01:00
Benoit Chesneau
997eec4f45 Fix pylint superfluous-parens warning 2026-03-26 14:22:44 +01:00
Benoit Chesneau
1f8e60c199 Add finish() method to ASGI callback parser for EOF handling
Handle chunked encoding edge case where connection closes before
final CRLF after zero-chunk. Skip WSGI-specific tests (casefold,
underscore headers) that don't apply to ASGI.
2026-03-26 12:13:50 +01:00
Benoit Chesneau
ffcebce4a7 Fix ASGI callback parser header validation
Add security checks to PythonProtocol per RFC 9110/9112:
- Reject duplicate Content-Length headers
- Reject CL + TE combinations
- Reject chunked in HTTP/1.0
- Reject stacked chunked encoding
- Validate Transfer-Encoding values
- Strict chunk size validation

Add PROXY protocol v1/v2 support to callback parser.

Add treq-based test infrastructure for ASGI parser.
2026-03-26 06:32:15 +01:00
Ben Leembruggen
8fba44cf02 fix: prevent HTTP/2 ASGI request body duplication
receive_data() stores every DATA frame in both _body_chunks (list)
and request_body (BytesIO). The receive() closure in
_handle_http2_request() has two read paths: a streaming path that
pops from _body_chunks, and a fast path that reads from BytesIO.

After the streaming path consumed the body, the fast path could
re-read the same data from BytesIO because body_received was never
set in the streaming return path. This caused the application to
receive a doubled request body (e.g. 18 bytes sent, 36 bytes
received), breaking JSON parsing with "Extra data" errors.

Fix: set body_received = True in the streaming path when
_body_complete is True, preventing the fast path from re-reading
already-consumed data.

Fixes #3558
2026-03-26 14:15:14 +11:00
Benoit Chesneau
6f601a0de9 Bump version to 25.2.0 2026-03-24 23:42:01 +01:00
Benoit Chesneau
385a9211e2
Fix uwsgi incomplete header error with async workers (#3554)
The _read_exact method was calling unreader.read() without a size
parameter, which only reads one chunk at a time. With gevent/gthread
workers, this could return incomplete data before the full header
arrived.

Use unreader.read(size) which has proper retry logic built-in to
read the exact number of bytes requested.

Fixes #3552
2026-03-24 23:17:51 +01:00
r266-tech
f8fca7a72f
fix: add __iter__ and __next__ to FileWrapper for PEP 3333 compliance (#3550)
* fix: add __iter__ and __next__ to FileWrapper for PEP 3333 compliance

The WSGI spec (PEP 3333) requires that wsgi.file_wrapper return an
iterable object. Gunicorn's FileWrapper only implemented __getitem__,
which technically makes it iterable via old-style iteration but breaks
code that explicitly relies on the iterator protocol (e.g., calling
iter() or using next()).

This adds __iter__ (returning self) and __next__ to make FileWrapper
a proper iterator, maintaining backward compatibility with existing
__getitem__-based usage.

Fixes #3396

* Fix lint: move imports to top of file

---------

Co-authored-by: contributor <noreply@users.noreply.github.com>
Co-authored-by: Benoit Chesneau <bchesneau@gmail.com>
2026-03-24 22:38:16 +01:00
Benoit Chesneau
0ad47db800
Use user-writable default path for control socket (#3551)
The previous default /run/gunicorn.ctl requires root permissions.
Now uses $XDG_RUNTIME_DIR/gunicorn.ctl if available, otherwise
$HOME/.gunicorn/gunicorn.ctl. This works on Linux, FreeBSD, OpenBSD,
and macOS without requiring elevated privileges.

- Add _get_default_control_socket() helper in config.py
- Create parent directory automatically with 0o700 permissions
- Update gunicornc CLI to use the same default path
- Add unit tests for path selection and directory creation
2026-03-23 20:08:03 +01:00
Benoit Chesneau
f9ca296d21 Fix WebSocket and body receiver issues in ASGI protocol
- Fix body receiver timeout handling to prevent infinite loops
- Add WebSocket data forwarding via callbacks instead of StreamReader
- Fix HTTP/2 stream race condition where DATA frames arrive before first read
- Update WebSocketProtocol constructor (removed reader parameter)
2026-03-23 13:38:47 +01:00
Benoit Chesneau
241c479701 Fix WebSocket race condition in callback-based _read_exact()
Add double-check after clearing _data_event to prevent deadlock when
data arrives between clear() and wait(). The race condition occurred
when:
1. Task A checks buffer, needs more data
2. Task A clears _data_event
3. Task B (feed_data) sets event
4. Task A awaits on cleared event - deadlock

The fix re-checks the buffer after clear() to catch data that arrived
in the race window.

Also adds tests for edge cases: race condition simulation, EOF during
wait, fragmented message reassembly, and control frames during
fragmentation.
2026-03-23 13:08:57 +01:00
Benoit Chesneau
ba1aaa5e33 Fix non-ASCII URL handling in ASGI worker
Percent-decode path to UTF-8 and preserve raw_path as original bytes
per ASGI spec. Fixes #3543
2026-03-22 16:35:28 +01:00
Benoit Chesneau
4ce6aa1f3e Fix limit handling and add default max limit tests
- LimitRequestLine now accepts optional max_size parameter
- Use default max limits when limit_request_line or limit_request_field_size is 0
- Add tests validating default max enforcement (8190 bytes)
- Handle alternate exceptions from fast parser in test_invalid_requests
2026-03-22 16:17:55 +01:00
Benoit Chesneau
03cc85ef48 Integrate gunicorn_h1c 0.4.1 exception types and limit parameters
Require gunicorn_h1c >= 0.4.1 for fast parser mode. Add new exception
types and limit parameters to PythonProtocol for parity with C parser.
Update tests to parametrize across both parser implementations.
2026-03-22 13:43:18 +01:00
Benoit Chesneau
86c0baf933 Fix lint issues in ASGI parser and protocol 2026-03-22 09:00:48 +01:00
Benoit Chesneau
e8150e27b2 Simplify ASGI parser to always use callback mode
Remove pull-based HttpParser path and always use callback-based parsing:

- Remove HttpParser, ParseResult, FastAsyncRequest classes from parser.py
- Remove BufferReader, _handle_connection_fast(), _parse_request_fast()
- Update _setup_callback_parser() to handle auto/fast/python modes
- Fix race condition when data arrives before _handle_connection starts
- Simplify http_parser config to auto/fast/python (remove callback modes)

Parser selection for ASGI:
- auto: H1CProtocol if available, else PythonProtocol
- fast: H1CProtocol required (error if unavailable)
- python: PythonProtocol only

Reduces code by ~1150 lines while maintaining performance.
2026-03-22 02:02:03 +01:00
Benoit Chesneau
87bfb7d190 Add RFC 7230 validation for chunked transfer-encoding
Validate after fast parser returns:
- Reject chunked with HTTP/1.0
- Reject chunked + Content-Length conflict
2026-03-22 00:18:25 +01:00
Benoit Chesneau
0ca0d0cb02 Fix body polling and HTTP/2 request streaming
- Replace 100ms polling with event-based waiting in BodyReceiver
- Stream HTTP/2 request bodies instead of buffering entire uploads
- Add timeout handling for disconnect detection
2026-03-22 00:01:17 +01:00
Benoit Chesneau
ea37eaaa6d Add streaming body support to HTTP2Stream
- Add _body_chunks, _body_event, _body_complete fields for streaming
- Modify receive_data() to populate chunks queue alongside BytesIO
- Add async read_body_chunk() method for streaming body reads

This enables HTTP/2 request body streaming instead of buffering
entire uploads, reducing memory usage for large file uploads.
2026-03-22 00:00:37 +01:00