Skip to content

gh-150499: http.server: enforce RFC 7230 framing rules for keep-alive connections#150500

Open
tonghuaroot wants to merge 1 commit into
python:mainfrom
tonghuaroot:gh-150499-httpserver-framing-validation
Open

gh-150499: http.server: enforce RFC 7230 framing rules for keep-alive connections#150500
tonghuaroot wants to merge 1 commit into
python:mainfrom
tonghuaroot:gh-150499-httpserver-framing-validation

Conversation

@tonghuaroot
Copy link
Copy Markdown

@tonghuaroot tonghuaroot commented May 27, 2026

Refs gh-150499 (paired with a follow-up PR for the §6.3 body-drain piece).

Summary

BaseHTTPRequestHandler in Lib/http/server.py does not enforce two
RFC 7230 §3.3.3 framing-validation rules, so a non-conforming request can
desynchronise the keep-alive loop. This PR fixes both in a single
class:

  1. RFC 7230 §3.3.3 rule 3 — any Transfer-Encoding header is now
    rejected with 400 Bad Request and Connection: 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.
  2. RFC 7230 §3.3.3 rule 4 — duplicate Content-Length headers
    with disagreeing values are now rejected with 400 Bad Request and
    Connection: close. Identical values are still accepted.

The §6.3 keep-alive body-drain piece is split into a separate
follow-up PR against the same issue.

Backwards compatibility

The module's class docstring already warns that http.server is not
intended for production use. The two checks tighten conformance to
RFC 7230 and only change behaviour for non-conforming clients:

  • Duplicate-equal Content-Length still works.
  • Transfer-Encoding was previously accepted and silently ignored,
    which is a request-smuggling primitive; clients sending TE must now
    either dechunk themselves or stop sending it.

Tests

Lib/test/test_httpservers.py adds RFC7230FramingTestCase with:

  • test_transfer_encoding_rejected — rule 3, plus assertion that the
    smuggled trailing request is not dispatched.
  • test_duplicate_content_length_rejected — rule 4.
  • test_duplicate_content_length_same_value_accepted — confirms the
    same-value case is unchanged.
  • test_keep_alive_post_pipeline — regression test for two pipelined
    POSTs with correct Content-Length on the same keep-alive
    connection.

All four tests pass against main with the patched module. Full
test_httpservers (106 tests) and test_wsgiref (38 tests) suites
also pass with no regression.

News entry

Misc/NEWS.d/next/Library/2026-05-27-00-43-54.gh-issue-150499.ykruLi.rst.

Backport

This PR targets main only; per CPython practice the backport policy
is left to the core dev review.

Comment thread Lib/test/test_httpservers.py Outdated
self.assertIn(b"POST body=ABCD", data)

def test_transfer_encoding_rejected(self):
# RFC 7230 section 3.3.3 rule 3 plus no chunked decoder.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test rule 3 before rule 4.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will swap, both in parse_request and in the test class. TE reframes the body as chunked-framed and invalidates CL, so the duplicate-CL check is dead work when both headers are present.

Comment thread Lib/http/server.py Outdated
return False

# RFC 7230 section 3.3.3 rule 3: this handler does not implement
# a chunked decoder, so any Transfer-Encoding is rejected.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check rule 3 before rule 4

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will swap, both in parse_request and in the test class. TE reframes the body as chunked-framed and invalidates CL, so the duplicate-CL check is dead work when both headers are present.

Comment thread Lib/http/server.py Outdated
if not self.raw_requestline:
self.close_connection = True
return
# Wrap rfile to track body consumption for RFC 7230 section 6.3.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be a separate PR I think.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. The §6.3 piece (_ReadCountingReader, _drain_request_body, the handle_one_request wrap) is now in #150508, a follow-up against the same issue. This PR is now scoped to §3.3.3 rules 3 and 4, the matching tests, and a trimmed news entry.

@bedevere-app
Copy link
Copy Markdown

bedevere-app Bot commented May 27, 2026

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@picnixz
Copy link
Copy Markdown
Member

picnixz commented May 27, 2026

How much was written by an AI?

Add two framing checks to BaseHTTPRequestHandler so the handler does
not desynchronise on a persistent connection:

* 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 3.3.3 rule 4: reject duplicate Content-Length
  headers whose values disagree, with 400 Bad Request and close.
  Identical values are still accepted.

The §6.3 keep-alive body-drain piece is split into a follow-up PR.

Add regression tests in Lib/test/test_httpservers.py covering the two
defects plus a pipelined keep-alive POST regression test.

Signed-off-by: tonghuaroot <tonghuaroot@gmail.com>
@tonghuaroot
Copy link
Copy Markdown
Author

Thanks.

Check rule 3 before rule 4 / Test rule 3 before rule 4

Will swap, both in parse_request and in the test class. TE reframes the body as chunked-framed and invalidates CL, so the duplicate-CL check is dead work when both headers are present.

This must be a separate PR I think.

Agreed. The §6.3 piece (_ReadCountingReader, _drain_request_body, the handle_one_request wrap) is now in #150508, a follow-up against the same issue. This PR is now scoped to §3.3.3 rules 3 and 4, the matching tests, and a trimmed news entry.

How much was written by an AI?

English isn't my first language. I drafted the code and the prose then leaned on the LLM to polish the English across the PR body, code comments, docstrings, and the news entry. The RFC reading, the defect classification, and the design choices are mine. Happy to walk through any specific block.

I have made the requested changes; please review again.

@bedevere-app
Copy link
Copy Markdown

bedevere-app Bot commented May 27, 2026

Thanks for making the requested changes!

@picnixz: please review the changes made to this pull request.

@bedevere-app bedevere-app Bot requested a review from picnixz May 27, 2026 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants