authorgravatar for dev@luna.glLuna Schwalbe <dev@luna.gl> 2025-12-07 04:28:01+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-11 11:04:24-07:00
log0bbf0461d9800ce5eba28cd32ef10492b2f80639
tree228500b88381759742764348878cd1255f28d8d4
parent29225ae11b93404f2d823ae45bfe68ce882706d1

std.http: reliably update reader state

Content length based reading would only set the reader state to `ready` once it returned EOF, but wrapping readers (such as decompressors) may stop reading from the underlying source without receiving EOF. In such cases the http reader state would stay set to `body_remaining_content_length`, even though the entire body had been read. Fixes #30060 Co-authored-by: Andrew Kelley <andre@ziglang.org>

2 files changed, 33 insertions(+), 9 deletions(-)

lib/std/http.zig+11-9
......@@ -443,7 +443,7 @@ pub const Reader = struct {
443443 },
444444 .none => {
445445 if (content_length) |len| {
446 reader.state = .{ .body_remaining_content_length = len };
446 reader.state = if (len == 0) .ready else .{ .body_remaining_content_length = len };
447447 reader.interface = .{
448448 .buffer = transfer_buffer,
449449 .seek = 0,
......@@ -509,27 +509,29 @@ pub const Reader = struct {
509509 limit: std.Io.Limit,
510510 ) std.Io.Reader.StreamError!usize {
511511 const reader: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
512 if (reader.state == .ready) return error.EndOfStream;
512513 const remaining_content_length = &reader.state.body_remaining_content_length;
513514 const remaining = remaining_content_length.*;
514 if (remaining == 0) {
515 const n = try reader.in.stream(w, limit.min(.limited64(remaining)));
516 if (n == remaining) {
515517 reader.state = .ready;
516 return error.EndOfStream;
518 } else {
519 remaining_content_length.* = remaining - n;
517520 }
518 const n = try reader.in.stream(w, limit.min(.limited64(remaining)));
519 remaining_content_length.* = remaining - n;
520521 return n;
521522 }
522523
523524 fn contentLengthDiscard(io_r: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {
524525 const reader: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
526 if (reader.state == .ready) return error.EndOfStream;
525527 const remaining_content_length = &reader.state.body_remaining_content_length;
526528 const remaining = remaining_content_length.*;
527 if (remaining == 0) {
529 const n = try reader.in.discard(limit.min(.limited64(remaining)));
530 if (n == remaining) {
528531 reader.state = .ready;
529 return error.EndOfStream;
532 } else {
533 remaining_content_length.* = remaining - n;
530534 }
531 const n = try reader.in.discard(limit.min(.limited64(remaining)));
532 remaining_content_length.* = remaining - n;
533535 return n;
534536 }
535537
lib/std/http/test.zig+22
......@@ -11,6 +11,28 @@ const expectEqual = std.testing.expectEqual;
1111const expectEqualStrings = std.testing.expectEqualStrings;
1212const expectError = std.testing.expectError;
1313
14test "content length reader state update" {
15 var in = Io.Reader.fixed("HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nHello!\r\nHTTP/1.1 200 OK\r\n\r\n");
16 var reader: http.Reader = .{
17 .in = &in,
18 .interface = undefined,
19 .state = .ready,
20 .max_head_len = 1024,
21 };
22
23 _ = try reader.receiveHead();
24 var body: [6]u8 = undefined;
25 _ = try reader.bodyReader(&.{}, .none, body.len).readSliceAll(&body);
26 try expectEqual(.ready, reader.state);
27 _ = try reader.receiveHead();
28
29 in.seek = 0;
30 _ = try reader.receiveHead();
31 try reader.bodyReader(&.{}, .none, body.len).discardAll(body.len);
32 try expectEqual(.ready, reader.state);
33 _ = try reader.receiveHead();
34}
35
1436test "trailers" {
1537 if (builtin.cpu.arch.isPowerPC64() and builtin.mode != .Debug) return error.SkipZigTest; // https://github.com/llvm/llvm-project/issues/171879
1638 if (builtin.os.tag == .openbsd) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/30806