Skip to content

Reject a declared body that never arrives before stream end - #1342

Open
feiiiiii5 wants to merge 1 commit into
python-hyper:masterfrom
feiiiiii5:fix/content-length-end-stream
Open

feiiiiii5 wants to merge 1 commit into
python-hyper:masterfrom
feiiiiii5:fix/content-length-end-stream

Conversation

@feiiiiii5

Copy link
Copy Markdown

What is wrong

content-length was only enforced when a DATA frame arrived. If END_STREAM was on the HEADERS block, or on a trailers block, the zero-length body skipped _track_content_length(), so a message could declare content-length: 5 and send no body at all. A trailers block containing its own content-length also overwrote the expected length from the header block.

POST content-length: 5 + END_STREAM on trailers  -> accepted
200  content-length: 5 + END_STREAM on HEADERS   -> accepted

What changes

  • A trailers block no longer calls _initialize_content_length(); content-length belongs to the header block.
  • When END_STREAM arrives on a header or trailers block, the zero-length body is validated against the expected length. HEAD, 204 and 304 keep their zero-length exemption from _initialize_content_length().
  • A matching body followed by trailers is still accepted.

Tests

tests/test_content_length_end_stream.py covers a request with END_STREAM on trailers, the same request with the declared body then trailers, a response with END_STREAM on HEADERS, and a response with no content-length. The two rejection cases fail on the parent revision; the full h2 suite is 1666 passed. ruff check src/ and mypy --strict src/h2/stream.py pass.

A declared content-length was only checked when DATA frames arrived. END_STREAM on the header block, or on a trailers block, skipped the check, so a request or response could declare five bytes and send none. Validate the zero-length body on both paths; trailers also no longer reset the expected length.
@Kriechi

Kriechi commented Oct 31, 2026

Copy link
Copy Markdown
Member

How does this PR related to your other PR #1329?

@feiiiiii5

Copy link
Copy Markdown
Author

Thanks for asking — they do overlap on one path and I should have said so in the description.

So as written this branch is a superset on the trailers side and adds the header-block side. Two clean shapes, and it is your call:

  1. Keep Validate content-length when a stream ends with trailers #1329 as the trailers work and rebase this one onto it so it carries only the header-block hunk plus its test — one small diff to review, and the two together still cover every way a stream can end.
  2. Close this and fold the header-block case into Validate content-length when a stream ends with trailers #1329, which already has the harness and the test_invalid_content_lengths.py file.

I would lean to 1, since #1329 has already been reviewed on its own terms and would have to be re-read otherwise. I will not push anything here until you say which you prefer.

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.

2 participants