Repository navigation
gh-150751: Handle duplicate Content-Length headers in http.client - #159495
Open
danilablagorodniy37 wants to merge 1 commit into
Open
danilablagorodniy37 wants to merge 1 commit into
danilablagorodniy37 wants to merge 1 commit into
Conversation
HTTPResponse.begin() framed the body with the first Content-Length
header and ignored any others, so a response with two differing values
was read using the first one while the rest of the body stayed on the
connection and corrupted the next response on a keep-alive connection.
A comma-separated list of identical values ("7, 7"), which a proxy
produces when it combines repeated fields, was treated as malformed and
the body was read until the connection closed.
RFC 9112, section 6.3 says that an invalid Content-Length makes the
message framing invalid, unless it is a comma-separated list of
identical values, which is then used as that single value; repeated
fields are equivalent to such a list (RFC 9110, section 5.3). Collect
the distinct values: a single one is used as before, several are
treated like an invalid header (the body is read until close and the
connection is not reused), as Go and h11 do.
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.
HTTPResponse.begin()reads theContent-Lengthwithself.headers.get(), which returns the first field of that name, so a response carrying two differingContent-Lengthheaders is framed with the first value and the other headers are silently ignored. The rest of the body then stays on the socket, and on a keep-alive connection the next response is corrupted. A comma-separated list of identical values (Content-Length: 7, 7, which an intermediary produces when it combines repeated fields) fails the1*DIGITcheck instead and is treated as a missing header, so the body is read until the server closes the connection:RFC 9110, section 5.3 makes repeated field lines equivalent to one comma-separated list, and
Content-Length = 1*DIGIT, so such a list is an invalid value; RFC 9110, section 8.6 lets a recipient of "the same decimal value repeated as a comma-separated list" (Content-Length: 42, 42) "either reject the message as invalid or replace that invalid field value with a single instance of the decimal value". RFC 9112, section 6.3 then says that an invalidContent-Lengthmakes the message framing invalid and "the recipient MUST treat it as an unrecoverable error, unless the field value can be successfully parsed as a comma-separated list [...], all values in the list are valid, and all values in the list are the same (in which case, the message is processed with that single value used as the Content-Length field value)"; a user agent "MUST close the connection to the server and discard the received response". Other implementations follow this: Go'snet/httpde-duplicates identical values and fails withmessage cannot contain multiple Content-Length headersotherwise, h11 accepts identical values (including the list form) and raisesconflicting Content-Length headersfor differing ones, llhttp (Node.js, aiohttp's default parser) rejects any repeatedContent-Length, and aiohttp's pure-Python parser rejects it too in strict mode (Duplicate 'Content-Length' header found).This PR collects the distinct values from every
Content-Lengthfield (splitting on commas and stripping OWS, as GH-159156 already does for a single value). A single distinct value is used as before, so7+7and7, 7frame the body as 7 bytes. Several distinct values are handled the way gh-150752 handles a malformed value: the header is treated as invalid, the body is read until the connection closes andwill_closeis set, so the connection is never reused with unread body bytes on it. No new exception is introduced. The two new tests fail on currentmain(4 subtests: both orders of5/7are framed with the first value, and both list forms are treated as missing) and pass with the fix;test_httplib(156 tests) andtest_httpservers(103 tests) pass.This is a follow-up to gh-150752 and GH-159156 in the same lines of
begin(); backports to 3.14/3.15 would need gh-159020 and gh-159021 to land first.AI assistance (Claude Code) was used to investigate and draft this change; the diff, tests and references were reviewed by the author.