Skip to content

Check HTTP/2 response bodies against the content-length header - #505

Merged
ericmj merged 3 commits into
mainfrom
check-http2-content-length
Sep 24, 2026
Merged

ericmj merged 3 commits into
mainfrom
check-http2-content-length

Conversation

@ericmj

@ericmj ericmj commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

The content-length header 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-length headers 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 second content-length header is an error. The digit check is Mint.ParsingTools.only_digits?/1, which Mint.HTTP1.Parse now 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-length header says. 2xx responses to CONNECT are exempt from the comparison because they carry tunnel data.

content_length/1 only matches {"content-length", value}. A response with Content-Length never 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.

@coveralls

coveralls commented Sep 20, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 8519

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.1%) to 88.77%

Details

  • Coverage increased (+0.1%) from the base build.
  • Patch coverage: 2 uncovered changes across 1 file (60 of 62 lines covered, 96.77%).
  • 22 coverage regressions across 1 file.

Uncovered Changes

File Changed Covered %
lib/mint/http2.ex 58 56 96.55%
Total (3 files) 62 60 96.77%

Coverage Regressions

22 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
lib/mint/http2.ex 22 93.86%

Coverage Stats

Coverage Status
Relevant Lines: 1781
Covered Lines: 1581
Line Coverage: 88.77%
Coverage Strength: 551.03 hits per line

💛 - Coveralls

@ericmj
ericmj force-pushed the check-http2-content-length branch from 984629f to 3eef772 Compare September 20, 2026 20:57
@ericmj
ericmj changed the base branch from validate-http2-response-headers to main September 20, 2026 20:57
@ericmj
ericmj marked this pull request as ready for review September 20, 2026 21:08
@ericmj
ericmj force-pushed the check-http2-content-length branch from 3eef772 to 5ecb643 Compare September 20, 2026 21:27
@ericmj
ericmj marked this pull request as draft September 20, 2026 21:27
@ericmj
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
ericmj force-pushed the check-http2-content-length branch from 5ecb643 to f25cfff Compare September 21, 2026 08:42

@whatyouhide whatyouhide left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Left two small comments but logic looks great.

Comment thread lib/mint/http2.ex Outdated
Comment thread lib/mint/http2.ex Outdated
…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.
@ericmj
ericmj merged commit 02d9148 into main Sep 24, 2026
3 checks passed
@ericmj
ericmj deleted the check-http2-content-length branch September 24, 2026 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants