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 {...@@ -577,21 +577,20 @@ pub const Reader = struct {
577 len: switch (chunk_len_ptr.*) {577 len: switch (chunk_len_ptr.*) {
578 .head => {578 .head => {
579 var cp: ChunkParser = .init;579 var cp: ChunkParser = .init;
580 const i = cp.feed(in.bufferContents());580 while (true) {
581 switch (cp.state) {581 const i = cp.feed(in.bufferContents());
582 .invalid => return error.HttpChunkInvalid,582 switch (cp.state) {
583 .data => {583 .invalid => return error.HttpChunkInvalid,
584 if (i > max_chunk_header_len) return error.HttpChunkInvalid;584 .data => {
585 in.toss(i);585 in.toss(i);
586 },586 break;
587 else => {587 },
588 try in.fill(max_chunk_header_len);588 else => {
589 const next_i = cp.feed(in.bufferContents()[i..]);589 in.toss(i);
590 if (cp.state != .data) return error.HttpChunkInvalid;590 try in.fillMore();
591 const header_len = i + next_i;591 continue;
592 if (header_len > max_chunk_header_len) return error.HttpChunkInvalid;592 },
593 in.toss(header_len);593 }
594 },
595 }594 }
596 if (cp.chunk_len == 0) return parseTrailers(reader, 0);595 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
597 const n = try in.read(bw, limit.min(.limited(cp.chunk_len)));596 const n = try in.read(bw, limit.min(.limited(cp.chunk_len)));
...@@ -650,24 +649,21 @@ pub const Reader = struct {...@@ -650,24 +649,21 @@ pub const Reader = struct {
650 len: switch (chunk_len_ptr.*) {649 len: switch (chunk_len_ptr.*) {
651 .head => {650 .head => {
652 var cp: ChunkParser = .init;651 var cp: ChunkParser = .init;
653 const available_buffer = in.bufferContents();652 while (true) {
654 const i = cp.feed(available_buffer);653 const i = cp.feed(in.bufferContents());
655 if (cp.state == .invalid) return error.HttpChunkInvalid;654 switch (cp.state) {
656 if (i == available_buffer.len) {655 .invalid => return error.HttpChunkInvalid,
657 if (already_requested_more) {656 .data => {
658 chunk_len_ptr.* = .head;657 in.toss(i);
659 return amt_read;658 break;
659 },
660 else => {
661 in.toss(i);
662 already_requested_more = true;
663 try in.fillMore();
664 continue;
665 },
660 }666 }
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);
671 }667 }
672 if (cp.chunk_len == 0) return parseTrailers(reader, amt_read);668 if (cp.chunk_len == 0) return parseTrailers(reader, amt_read);
673 continue :len .init(cp.chunk_len + 2);669 continue :len .init(cp.chunk_len + 2);
...@@ -739,21 +735,20 @@ pub const Reader = struct {...@@ -739,21 +735,20 @@ pub const Reader = struct {
739 len: switch (chunk_len_ptr.*) {735 len: switch (chunk_len_ptr.*) {
740 .head => {736 .head => {
741 var cp: ChunkParser = .init;737 var cp: ChunkParser = .init;
742 const i = cp.feed(in.bufferContents());738 while (true) {
743 switch (cp.state) {739 const i = cp.feed(in.bufferContents());
744 .invalid => return error.HttpChunkInvalid,740 switch (cp.state) {
745 .data => {741 .invalid => return error.HttpChunkInvalid,
746 if (i > max_chunk_header_len) return error.HttpChunkInvalid;742 .data => {
747 in.toss(i);743 in.toss(i);
748 },744 break;
749 else => {745 },
750 try in.fill(max_chunk_header_len);746 else => {
751 const next_i = cp.feed(in.bufferContents()[i..]);747 in.toss(i);
752 if (cp.state != .data) return error.HttpChunkInvalid;748 try in.fillMore();
753 const header_len = i + next_i;749 continue;
754 if (header_len > max_chunk_header_len) return error.HttpChunkInvalid;750 },
755 in.toss(header_len);751 }
756 },
757 }752 }
758 if (cp.chunk_len == 0) return parseTrailers(reader, 0);753 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
759 const n = try in.discard(limit.min(.limited(cp.chunk_len)));754 const n = try in.discard(limit.min(.limited(cp.chunk_len)));
...@@ -1010,16 +1005,14 @@ pub const BodyWriter = struct {...@@ -1010,16 +1005,14 @@ pub const BodyWriter = struct {
1010 else => unreachable, // An earlier write call indicated more data would follow.1005 else => unreachable, // An earlier write call indicated more data would follow.
1011 },1006 },
1012 }1007 }
1013 if (options.trailers.len > 0) {1008 try bw.writeAll("0\r\n");
1014 try bw.writeAll("0\r\n");1009 for (options.trailers) |trailer| {
1015 for (options.trailers) |trailer| {1010 try bw.writeAll(trailer.name);
1016 try bw.writeAll(trailer.name);1011 try bw.writeAll(": ");
1017 try bw.writeAll(": ");1012 try bw.writeAll(trailer.value);
1018 try bw.writeAll(trailer.value);
1019 try bw.writeAll("\r\n");
1020 }
1021 try bw.writeAll("\r\n");1013 try bw.writeAll("\r\n");
1022 }1014 }
1015 try bw.writeAll("\r\n");
1023 w.state = .end;1016 w.state = .end;
1024 }1017 }
10251018