Check HTTP/2 response bodies against the content-length header - #505
Merged
Merged
Conversation
Coverage Report for CI Build 8519Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage increased (+0.1%) to 88.77%Details
Uncovered Changes
Coverage Regressions22 previously-covered lines in 1 file lost coverage.
Coverage Stats
💛 - Coveralls |
ericmj
force-pushed
the
check-http2-content-length
branch
from
September 20, 2026 20:57
984629f to
3eef772
Compare
ericmj
changed the base branch from
validate-http2-response-headers
to
main
September 20, 2026 20:57
ericmj
marked this pull request as ready for review
September 20, 2026 21:08
ericmj
force-pushed
the
check-http2-content-length
branch
from
September 20, 2026 21:27
3eef772 to
5ecb643
Compare
ericmj
marked this pull request as draft
September 20, 2026 21:27
ericmj
marked this pull request as ready for review
September 20, 2026 21:30
The content-length header of an HTTP/2 response wasn't compared with the
DATA frames, so a body that was cut short, or that kept going past the
declared length, was delivered as a complete response. Non-numeric and
conflicting content-length values were passed through as well.
Each stream now records the request method and the declared length and
counts the body bytes it receives. A body that exceeds the declared
length, or that ends (through END_STREAM on DATA, on the headers or on
trailers) at a different length, is a stream error with PROTOCOL_ERROR,
following RFC 9113 8.1.1. Identical duplicate content-length headers are
accepted; differing values fail with
:more_than_one_content_length_header and values that aren't digits fail
with {:invalid_content_length_header, value}.
Responses to HEAD and 204 and 304 responses must not have content,
whatever their content-length header says, so they're tracked with a
length of zero and a DATA frame on them is a stream error rather than
body data. 2xx responses to CONNECT carry tunnel data and are exempt
from the comparison.
ericmj
force-pushed
the
check-http2-content-length
branch
from
September 21, 2026 08:42
5ecb643 to
f25cfff
Compare
whatyouhide
approved these changes
Sep 24, 2026
whatyouhide
left a comment
Contributor
There was a problem hiding this comment.
Left two small comments but logic looks great.
…nt_length_headers HTTP/2 responses accept identical duplicate content-length headers and only fail when the values differ, so :more_than_one_content_length_header named a case that isn't an error. HTTP/1 keeps that name, since there a second content-length header is an error whatever its value.
Mint.HTTP1.Parse and Mint.HTTP2 each had a private function checking that a content-length value is only ASCII digits. Both now call Mint.ParsingTools.only_digits?/1.
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.
The
content-lengthheader of an HTTP/2 response was never compared with the DATA frames, so a body that was cut short, or that kept going past the declared length, was delivered as a complete response. Non-numeric and conflicting values were passed through as well.Each stream now records the request method and the declared length and counts the body bytes it receives. A body that goes past the declared length, or that ends at a different length through END_STREAM on DATA, on the headers or on trailers, is a stream error with PROTOCOL_ERROR. RFC 9113 8.1.1 makes it malformed: "A request or response is also malformed if the value of a content-length header field does not equal the sum of the DATA frame payload lengths that form the content, unless the message is defined as having no content", and malformed responses MUST be treated as a stream error of type PROTOCOL_ERROR.
Identical duplicate
content-lengthheaders are accepted, differing values fail with:disagreeing_content_length_headers, and values that aren't digits fail with{:invalid_content_length_header, value}. HTTP/1 keeps:more_than_one_content_length_header, since there any secondcontent-lengthheader is an error. The digit check isMint.ParsingTools.only_digits?/1, whichMint.HTTP1.Parsenow uses too instead of its own copy.Responses to HEAD, and 204 and 304 responses, are the cases RFC 9110 6.4.1 says never carry content. They're tracked with a length of zero rather than exempted, so a DATA frame on one is a stream error instead of body data, whatever the
content-lengthheader says. 2xx responses to CONNECT are exempt from the comparison because they carry tunnel data.content_length/1only matches{"content-length", value}. A response withContent-Lengthnever reaches it: header validation from #504 runs first and fails the stream with{:invalid_header_name, "Content-Length"}, since RFC 9113 8.2.1 prohibits uppercase field names.