A 304 with content-length is accepted or rejected by framing alone
維護者通常 4 天內回覆
還沒有人認領這個 Issue。
評估
- 難度
- 4/5
- 預估耗時
- 3-5 天
- 新手友好度
- 45/100
- Issue 類型
- 缺陷
- 描述清晰度
- 基本清楚
- 活躍度
- 活躍
- 技術堆疊
- python
- 領域
- networking
研究方向
Start in src/h2/stream.py at _initialize_content_length (line 1369), receive_headers (line 1113), send_headers (line 909), and remotely_pushed (line 1052), using the standalone probe to reproduce the reported cases. First resolve whether 204/304 should set the expected length to 0 or None, then cover the exemption and HEADERS-frame check together. Done means the 17 reported assertions pass with uv run --frozen pytest tests/ -q at 100% coverage.
由索引模型根據 Issue 內容生成。
描述
While working on #1328 I found two more holes in the same function, and they turn out to be two halves of one problem. I have not touched anything — the measurements are in a standalone probe, and I would rather agree on the direction first.
A 304 or 204 carrying content-length is accepted or rejected depending on framing
_initialize_content_length (src/h2/stream.py:1369) implements exactly one exemption, self.request_method == b"HEAD" (:1375). RFC 9113 § 8.1.1 names three messages that are "defined as having no content": 204, 304, and the response to HEAD, and it says a message defined to have no content "MAY have a non-zero content-length header field". RFC 9110 § 8.6 repeats it for 304 — that is how a client learns the size of the cached entity. H2Stream never stores :status, so the other two cannot be written as the code stands. Observable today, on master and on #1329 alike:
304 + content-length: 1234 + empty DATA with END_STREAM -> InvalidBodyLengthError: Expected 1234 bytes, received 0
304 + content-length: 1234 + END_STREAM on the HEADERS -> accepted ['ResponseReceived', 'StreamEnded']
204 + content-length: 1234 + empty DATA with END_STREAM -> InvalidBodyLengthError: Expected 1234 bytes, received 0
One message, two verdicts, decided purely by which frame carried END_STREAM. That is an enforcement that rejects something the RFC permits, and it is reachable from a plain H2Connection.receive_data on a legitimate response.
The same hole #1328 fixed, in the other branch of the same if
receive_headers (:1113) — the else branch, i.e. a HEADERS block that is not trailers, calls _initialize_content_length(headers) and nothing else. So a bodyless message that ends on the HEADERS is never policed at all:
POST content-length: 15 + END_STREAM on the HEADERS, no DATA -> accepted ['RequestReceived', 'StreamEnded']
200 content-length: 1234 + END_STREAM on the HEADERS -> accepted ['ResponseReceived', 'StreamEnded']
content-length: 15 with nothing sent is the same lie #1328 is about, and _actual_content_length stays 0.
The obvious one-line fix is not safe on its own. Adding if end_stream: self._track_content_length(0, end_stream=True) to that branch, measured:
before after that line
POST content-length: 15, no body accepted InvalidBodyLengthError(15, 0) <- intended
304 content-length: 1234, no body accepted InvalidBodyLengthError(1234, 0) <- new breakage
204 content-length: 1234, no body accepted InvalidBodyLengthError(1234, 0) <- new breakage
POST content-length: 0, no body accepted accepted <- control
GET no content-length, no body accepted accepted <- control
So the 204/304 exemption has to land first, or with it. That is why I am asking rather than sending a patch: the exemption is a design call. Setting self._expected_content_length = 0 for 204/304 mirrors the HEAD precedent and additionally makes DATA after a 204 an error, which RFC 9110 § 6.3 implies; setting it to None only skips the comparison. I lean to 0, but that is your call, and H2Stream would need to start recording :status.
Two smaller ones in the same file, and what I am not reporting
Two smaller ones in the same file, both one-line
send_headers(:909) assignsself.request_method = extract_method_header(bytes_headers)on every header block, including a trailers block, which has no:method— so outbound trailers overwrite a rememberedb"HEAD"withNone.H2Connection.send_headers(1, HEAD)then a trailers block, then a200+content-length: 1234+ empty DATA + END_STREAM, raises where it should not. Guarding onmethod is not Nonefixes it.remotely_pushed(:1052) recordsself._authoritybut never the method, so a pushed stream never gets the HEAD exemption at all. APUSH_PROMISEwith:method: HEADfollowed by200+content-length: 1234+ empty DATA + END_STREAM raises; the identical push with:method: GETraises the same way, which is the tell that the method is simply not consulted.
Not reported
- RST_STREAM or a local reset mid-body runs no length check, and should not: the message is abandoned and the stream is closed, so nothing later can contradict the sender. Measured, no error, and I read that as specified rather than a bug.
- A
content-lengthin outbound trailers is a real asymmetry — h2 will send a trailers block carryingcontent-length: 0, and feeding those exact bytes back into an h2 client raisesProtocolError: Received content-length header in trailer. But fixing it means changing the trailer field in four existing tests intests/test_basic_logic.py, so I would file that separately rather than widen this. - A 1xx carrying
content-lengthis accepted and then overwritten. Inconsistent with the new trailers rule, but the RFC puts that obligation on the sender and no wrong byte is ever compared, so I do not think it is worth an interop risk.
Verification
uv run --frozen pytest tests/ -q is 1670 passed at 100.00% coverage (466 statements, 98 branches in stream.py, 0 missed), on d2b43d0. Every scenario above is a runtime measurement against the real H2Connection, not a reading. I have the probe as a standalone script and the 17 assertions as a pytest file; say the word and I will open a PR with the exemption and the HEADERS-frame check together, at 100% coverage.
- 主要語言
- Python
- 星號
- 1k
- 分支
- 197
- PR 合併指標
- 30 天內沒有已合併 PR
環境準備
這個專案沒有提供開發容器、Dockerfile 或貢獻指南,環境需要你自己搭建:先看它的 README,通用步驟見我們的新手貢獻指南。
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
python-hyper/h2 的其他 Issue
-
content-length is not policed when a stream ends with a trailers section可能已有人在做 @feiiiiii5 於 30 天前認領。 未關閉
難度 3/5 1-2 天 新手友好度 76/100
python-hyper/h2#1328 ·
維護者通常 4 天內回覆
-
難度 5/5 一週以上 新手友好度 35/100
python-hyper/h2#1312 ·
維護者通常 4 天內回覆
-
難度 4/5 3-5 天 新手友好度 35/100
python-hyper/h2#1280 · 2 則留言 ·
維護者通常 4 天內回覆
-
難度 3/5 1-2 天 新手友好度 45/100
python-hyper/h2#1239 · 3 個 reaction ·
維護者通常 4 天內回覆
-
ProtocolError on receive_data after connection is closed可能已有人在做 @adarshx01 於 69 天前認領。 未關閉
難度 4/5 3-5 天 新手友好度 42/100
python-hyper/h2#1199 · 3 則留言 ·
維護者通常 4 天內回覆
相似的 Issue
-
難度 2/5 1-3 小時 新手友好度 68/100
Arekkazu/sgpmp-backend#744 ·
維護者通常 1 天內回覆
-
agent/reviewer
難度 2/5 1-3 小時 新手友好度 78/100
projectbluefin/utah-packages#634 ·
維護者通常 1 天內回覆
-
enhancement good first issue help wanted
難度 2/5 1-2 天 新手友好度 78/100
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 68/100
home-assistant/core#188613 ·
維護者通常 1 天內回覆
-
Website未關閉good first issue
難度 2/5 1-3 小時 新手友好度 91/100
TanbirRamim/open-source-radar#314 ·
維護者通常 1 天內回覆