authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-05 13:42:50-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
logaf7721d20fc2330c0f4e7aa42df606254b06516f
treeb85f84dace797c7fc370ed1cb43f93b3c3d338cd
parent42cc1bd76e00094555fa5115005906848df4ca95

std.http.BodyWriter: fix end of transfer-encoding chunked

Looks like chunked transfers are supposed to always end with a length zero chunk. This bug exists on master branch as well.

1 files changed, 48 insertions(+), 55 deletions(-)

lib/std/http.zig+48-55
......@@ -577,21 +577,20 @@ pub const Reader = struct {
577577 len: switch (chunk_len_ptr.*) {
578578 .head => {
579579 var cp: ChunkParser = .init;
580 const i = cp.feed(in.bufferContents());
581 switch (cp.state) {
582 .invalid => return error.HttpChunkInvalid,
583 .data => {
584 if (i > max_chunk_header_len) return error.HttpChunkInvalid;
585 in.toss(i);
586 },
587 else => {
588 try in.fill(max_chunk_header_len);
589 const next_i = cp.feed(in.bufferContents()[i..]);
590 if (cp.state != .data) return error.HttpChunkInvalid;
591 const header_len = i + next_i;
592 if (header_len > max_chunk_header_len) return error.HttpChunkInvalid;
593 in.toss(header_len);
594 },
580 while (true) {
581 const i = cp.feed(in.bufferContents());
582 switch (cp.state) {
583 .invalid => return error.HttpChunkInvalid,
584 .data => {
585 in.toss(i);
586 break;
587 },
588 else => {
589 in.toss(i);
590 try in.fillMore();
591 continue;
592 },
593 }
595594 }
596595 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
597596 const n = try in.read(bw, limit.min(.limited(cp.chunk_len)));
......@@ -650,24 +649,21 @@ pub const Reader = struct {
650649 len: switch (chunk_len_ptr.*) {
651650 .head => {
652651 var cp: ChunkParser = .init;
653 const available_buffer = in.bufferContents();
654 const i = cp.feed(available_buffer);
655 if (cp.state == .invalid) return error.HttpChunkInvalid;
656 if (i == available_buffer.len) {
657 if (already_requested_more) {
658 chunk_len_ptr.* = .head;
659 return amt_read;
652 while (true) {
653 const i = cp.feed(in.bufferContents());
654 switch (cp.state) {
655 .invalid => return error.HttpChunkInvalid,
656 .data => {
657 in.toss(i);
658 break;
659 },
660 else => {
661 in.toss(i);
662 already_requested_more = true;
663 try in.fillMore();
664 continue;
665 },
660666 }
661 already_requested_more = true;
662 try in.fill(max_chunk_header_len);
663 const next_i = cp.feed(in.bufferContents()[i..]);
664 if (cp.state != .data) return error.HttpChunkInvalid;
665 const header_len = i + next_i;
666 if (header_len > max_chunk_header_len) return error.HttpChunkInvalid;
667 in.toss(header_len);
668 } else {
669 if (i > max_chunk_header_len) return error.HttpChunkInvalid;
670 in.toss(i);
671667 }
672668 if (cp.chunk_len == 0) return parseTrailers(reader, amt_read);
673669 continue :len .init(cp.chunk_len + 2);
......@@ -739,21 +735,20 @@ pub const Reader = struct {
739735 len: switch (chunk_len_ptr.*) {
740736 .head => {
741737 var cp: ChunkParser = .init;
742 const i = cp.feed(in.bufferContents());
743 switch (cp.state) {
744 .invalid => return error.HttpChunkInvalid,
745 .data => {
746 if (i > max_chunk_header_len) return error.HttpChunkInvalid;
747 in.toss(i);
748 },
749 else => {
750 try in.fill(max_chunk_header_len);
751 const next_i = cp.feed(in.bufferContents()[i..]);
752 if (cp.state != .data) return error.HttpChunkInvalid;
753 const header_len = i + next_i;
754 if (header_len > max_chunk_header_len) return error.HttpChunkInvalid;
755 in.toss(header_len);
756 },
738 while (true) {
739 const i = cp.feed(in.bufferContents());
740 switch (cp.state) {
741 .invalid => return error.HttpChunkInvalid,
742 .data => {
743 in.toss(i);
744 break;
745 },
746 else => {
747 in.toss(i);
748 try in.fillMore();
749 continue;
750 },
751 }
757752 }
758753 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
759754 const n = try in.discard(limit.min(.limited(cp.chunk_len)));
......@@ -1010,16 +1005,14 @@ pub const BodyWriter = struct {
10101005 else => unreachable, // An earlier write call indicated more data would follow.
10111006 },
10121007 }
1013 if (options.trailers.len > 0) {
1014 try bw.writeAll("0\r\n");
1015 for (options.trailers) |trailer| {
1016 try bw.writeAll(trailer.name);
1017 try bw.writeAll(": ");
1018 try bw.writeAll(trailer.value);
1019 try bw.writeAll("\r\n");
1020 }
1008 try bw.writeAll("0\r\n");
1009 for (options.trailers) |trailer| {
1010 try bw.writeAll(trailer.name);
1011 try bw.writeAll(": ");
1012 try bw.writeAll(trailer.value);
10211013 try bw.writeAll("\r\n");
10221014 }
1015 try bw.writeAll("\r\n");
10231016 w.state = .end;
10241017 }
10251018