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 {...@@ -443,7 +443,7 @@ pub const Reader = struct {
443 },443 },
444 .none => {444 .none => {
445 if (content_length) |len| {445 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 };
447 reader.interface = .{447 reader.interface = .{
448 .buffer = transfer_buffer,448 .buffer = transfer_buffer,
449 .seek = 0,449 .seek = 0,
...@@ -509,27 +509,29 @@ pub const Reader = struct {...@@ -509,27 +509,29 @@ pub const Reader = struct {
509 limit: std.Io.Limit,509 limit: std.Io.Limit,
510 ) std.Io.Reader.StreamError!usize {510 ) std.Io.Reader.StreamError!usize {
511 const reader: *Reader = @alignCast(@fieldParentPtr("interface", io_r));511 const reader: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
512 if (reader.state == .ready) return error.EndOfStream;
512 const remaining_content_length = &reader.state.body_remaining_content_length;513 const remaining_content_length = &reader.state.body_remaining_content_length;
513 const remaining = remaining_content_length.*;514 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) {
515 reader.state = .ready;517 reader.state = .ready;
516 return error.EndOfStream;518 } else {
519 remaining_content_length.* = remaining - n;
517 }520 }
518 const n = try reader.in.stream(w, limit.min(.limited64(remaining)));
519 remaining_content_length.* = remaining - n;
520 return n;521 return n;
521 }522 }
522523
523 fn contentLengthDiscard(io_r: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {524 fn contentLengthDiscard(io_r: *std.Io.Reader, limit: std.Io.Limit) std.Io.Reader.Error!usize {
524 const reader: *Reader = @alignCast(@fieldParentPtr("interface", io_r));525 const reader: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
526 if (reader.state == .ready) return error.EndOfStream;
525 const remaining_content_length = &reader.state.body_remaining_content_length;527 const remaining_content_length = &reader.state.body_remaining_content_length;
526 const remaining = remaining_content_length.*;528 const remaining = remaining_content_length.*;
527 if (remaining == 0) {529 const n = try reader.in.discard(limit.min(.limited64(remaining)));
530 if (n == remaining) {
528 reader.state = .ready;531 reader.state = .ready;
529 return error.EndOfStream;532 } else {
533 remaining_content_length.* = remaining - n;
530 }534 }
531 const n = try reader.in.discard(limit.min(.limited64(remaining)));
532 remaining_content_length.* = remaining - n;
533 return n;535 return n;
534 }536 }
535537
lib/std/http/test.zig+22
...@@ -11,6 +11,28 @@ const expectEqual = std.testing.expectEqual;...@@ -11,6 +11,28 @@ const expectEqual = std.testing.expectEqual;
11const expectEqualStrings = std.testing.expectEqualStrings;11const expectEqualStrings = std.testing.expectEqualStrings;
12const expectError = std.testing.expectError;12const 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
14test "trailers" {36test "trailers" {
15 if (builtin.cpu.arch.isPowerPC64() and builtin.mode != .Debug) return error.SkipZigTest; // https://github.com/llvm/llvm-project/issues/17187937 if (builtin.cpu.arch.isPowerPC64() and builtin.mode != .Debug) return error.SkipZigTest; // https://github.com/llvm/llvm-project/issues/171879
16 if (builtin.os.tag == .openbsd) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/3080638 if (builtin.os.tag == .openbsd) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/30806