gh-150499: http.server: enforce RFC 7230 framing rules for keep-alive connections#150500
Open
tonghuaroot wants to merge 1 commit into
Open
gh-150499: http.server: enforce RFC 7230 framing rules for keep-alive connections#150500tonghuaroot wants to merge 1 commit into
tonghuaroot wants to merge 1 commit into
Conversation
Add three framing checks to BaseHTTPRequestHandler so the handler does not desynchronise on a persistent connection: * RFC 7230 section 3.3.3 rule 4: reject duplicate Content-Length headers whose values disagree, with 400 Bad Request and close. * RFC 7230 section 3.3.3 rule 3: reject any Transfer-Encoding header with 400 Bad Request and close, because http.server does not implement a chunked decoder. Once a decoder is added this check can be narrowed to anything other than 'identity'. * RFC 7230 section 6.3: wrap rfile with a small byte-counting reader for the duration of the request and, after the handler returns, drain any unread declared body up to a 1 MiB cap (or close the connection if the remainder is larger). Add regression tests in Lib/test/test_httpservers.py covering the three defects plus a pipelined keep-alive POST regression test that exercises the new drain path. Signed-off-by: tonghuaroot <tonghuaroot@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #150499.
Summary
BaseHTTPRequestHandlerinLib/http/server.pydoes not enforce threeRFC 7230 framing-validation rules, so a non-conforming request can
desynchronise the keep-alive loop. The issue collects the three
defects and the reproductions; this PR fixes all of them in a single
class:
Content-Lengthheaderswith disagreeing values are now rejected with
400 Bad RequestandConnection: close. Identical values are still accepted.Transfer-Encodingheader is nowrejected with
400 Bad RequestandConnection: close, becausehttp.serverdoes not implement a chunked decoder. Once a decoderis added this check can be narrowed to anything other than
identity.handle_one_requestnow wrapsself.rfilewith a small byte-counting reader for the duration of the request,
and after the handler returns it drains any unread declared body up
to a 1 MiB cap. If the remaining body exceeds that cap the
connection is closed instead. This prevents the next iteration of
the keep-alive loop from parsing leftover body bytes as a request
line.
The wrapper proxies
read,read1,readline,readinto, andreadinto1and falls back to__getattr__for everything else, soexisting handlers (including the
ServerHandlerchain inwsgiref.simple_server, which passesself.rfiledirectly to WSGIapplications) continue to work unchanged.
Backwards compatibility
The module's class docstring already warns that
http.serveris notintended for production use. The three checks tighten conformance to
RFC 7230 and only change behaviour for non-conforming clients:
Content-Lengthstill works.Transfer-Encodingwas previously accepted and silently ignored,which is a request-smuggling primitive; clients sending TE must now
either dechunk themselves or stop sending it.
full Content-Length body, and adds a bounded read for handlers that
do not (such as
SimpleHTTPRequestHandler'sdo_GETagainst arequest with a body).
Tests
Lib/test/test_httpservers.pyaddsRFC7230FramingTestCasewith:test_duplicate_content_length_rejected— defect A.test_duplicate_content_length_same_value_accepted— confirms thesame-value case is unchanged.
test_transfer_encoding_rejected— defect B, plus assertion thatthe smuggled trailing request is not dispatched.
test_body_drained_on_persistent_connection— defect C, assertsthat the leftover body is not parsed as the next request line.
test_keep_alive_post_pipeline— regression test for two pipelinedPOSTs with correct
Content-Lengthon the same keep-aliveconnection.
All five tests run against a single-threaded
HTTPServer(matchingthe existing
BaseTestCasesetup) and pass in ~6 seconds locally onPython 3.14.4 with the patched module.
News entry
Misc/NEWS.d/next/Library/2026-05-27-00-43-54.gh-issue-150499.ykruLi.rst.Backport
This PR targets
mainonly; per CPython practice the backport policyis left to the core dev review.