Skip to content

Commit f873315

Browse files
net.http: bring H2Conn.read_response to response-validation parity with the mux path
The synchronous client validated far less of a server response than the multiplexed H2MuxConn.on_response_headers/on_response_data path, so a malformed server could hang the request or have a bogus response accepted. Close the gaps, matching the mux path (which already enforces each of these): - Trailers (a second HEADERS block) must carry END_STREAM (RFC 9113 §8.1). Previously a trailers frame without END_STREAM fell through and read_response looped in next_frame() forever. - :status must be present and in range; 101 is forbidden in HTTP/2 (§8.1.1, §8.3.1). Previously a sub-100 or >599 status was latched as a valid response, and 101 was treated as a 1xx interim, waiting forever for a "final" HEADERS. - DATA before the first response HEADERS is a protocol error (§8.1). Previously body bytes were delivered with status 0. The 1xx + END_STREAM rejection added in the previous commit is retained. Each guard fails the request (abandoning the connection) rather than hanging or returning a malformed response, consistent with the existing error returns in read_response. Surfaced while reviewing the 1xx fix for #27413. Co-Authored-By: WOZCODE <contact@withwoz.com>
1 parent 0c61bdd commit f873315

1 file changed

Lines changed: 36 additions & 10 deletions

File tree

vlib/net/http/h2_conn.v

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -200,19 +200,44 @@ fn (mut c H2Conn) read_response(stream_id u32, req H2ClientRequest) !H2ClientRes
200200
fragment := c.collect_header_block(frame.fragment, frame.end_headers, stream_id)!
201201
// Decode once — HPACK table state is advanced here.
202202
decoded := c.decoder.decode(fragment)!
203-
// First pass: find :status to detect 1xx informational responses
204-
// (RFC 7540 §8.1, RFC 7231 §6.2). Trailers have no :status (== 0).
203+
if got_headers {
204+
// A second HEADERS block carries trailers, which MUST end the
205+
// stream (RFC 9113 §8.1). Without END_STREAM read_response would
206+
// loop forever waiting for a stream end that never comes; the mux
207+
// path resets such a stream, so the synchronous client fails too.
208+
if !frame.end_stream {
209+
return error('h2: trailers HEADERS frame must carry END_STREAM')
210+
}
211+
for f in decoded {
212+
if !f.name.starts_with(':') {
213+
resp.headers << f
214+
}
215+
}
216+
break
217+
}
218+
// First (interim or final) response HEADERS. Find :status. A
219+
// response MUST carry a valid :status (RFC 9113 §8.3.1), and 101 is
220+
// forbidden in HTTP/2 (§8.1.1).
205221
mut status := 0
222+
mut status_seen := false
206223
for f in decoded {
207224
if f.name == ':status' {
208225
if f.value.len == 3 && all_digits(f.value) {
209226
status = f.value.int()
210227
} else {
211228
return error('h2: malformed :status value: ${f.value}')
212229
}
230+
status_seen = true
213231
break
214232
}
215233
}
234+
if !status_seen || status < 100 || status > 599 || status == 101 {
235+
// Missing / out-of-range / 101 status: fail rather than latch a
236+
// bogus status or (for 101 or a sub-200 interim) loop forever
237+
// waiting for a "final" HEADERS. Mirrors the mux path, which
238+
// resets the stream with PROTOCOL_ERROR.
239+
return error('h2: response with a missing or invalid :status: ${status}')
240+
}
216241
if status >= 100 && status < 200 {
217242
// 1xx informational: discard and continue waiting for the
218243
// final HEADERS block. Do not set got_headers here.
@@ -227,17 +252,12 @@ fn (mut c H2Conn) read_response(stream_id u32, req H2ClientRequest) !H2ClientRes
227252
}
228253
continue
229254
}
230-
// Second pass: populate the response. Skip pseudo-headers.
231-
if status > 0 {
232-
resp.status = status
233-
}
255+
// Final response (status >= 200): populate, skipping pseudo-headers.
256+
resp.status = status
234257
for f in decoded {
235258
if !f.name.starts_with(':') {
236259
resp.headers << f
237-
// Only update body_expected on the first (non-1xx) HEADERS
238-
// frame. A content-length field in a trailer must not
239-
// overwrite the value used for the completeness check below.
240-
if !got_headers && f.name == 'content-length' {
260+
if f.name == 'content-length' {
241261
if all_digits(f.value) {
242262
body_expected = f.value.u64()
243263
has_content_length = true
@@ -256,6 +276,12 @@ fn (mut c H2Conn) read_response(stream_id u32, req H2ClientRequest) !H2ClientRes
256276
if frame.stream_id != stream_id {
257277
continue
258278
}
279+
if !got_headers {
280+
// DATA before the response HEADERS is a protocol error
281+
// (RFC 9113 §8.1); the mux path rejects it. Fail rather than
282+
// deliver body bytes for a response that has no status yet.
283+
return error('h2: DATA frame before response HEADERS')
284+
}
259285
if frame.data.len > 0 {
260286
body_so_far += u64(frame.data.len)
261287
// Append the chunk to the response body unless the copy

0 commit comments

Comments
 (0)