authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-04 20:44:12-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
log97c7d6e5b386ab3c0cb394cae02e116b7a7a0955
tree555b539f7ca5b6585b0e082e3bea7c020c13451a
parent2abbcb6d2a199c9d535a950db6098b8224d510aa

std.http: fix parseTrailers (transfer-encoding: chunked)

It needs to detect \r\n as end of stream rather than trying to check the next byte.

1 files changed, 103 insertions(+), 49 deletions(-)

lib/std/http.zig+103-49
...@@ -553,44 +553,64 @@ pub const Reader = struct {...@@ -553,44 +553,64 @@ pub const Reader = struct {
553 .body_remaining_chunk_len => |*x| x,553 .body_remaining_chunk_len => |*x| x,
554 else => unreachable,554 else => unreachable,
555 };555 };
556 return chunkedReadEndless(reader, bw, limit, chunk_len_ptr) catch |err| switch (err) {
557 error.ReadFailed => return error.ReadFailed,
558 error.WriteFailed => return error.WriteFailed,
559 error.EndOfStream => {
560 reader.body_err = error.HttpChunkTruncated;
561 return error.ReadFailed;
562 },
563 else => |e| {
564 reader.body_err = e;
565 return error.ReadFailed;
566 },
567 };
568 }
569
570 fn chunkedReadEndless(
571 reader: *Reader,
572 bw: *std.io.BufferedWriter,
573 limit: std.io.Reader.Limit,
574 chunk_len_ptr: *RemainingChunkLen,
575 ) (BodyError || std.io.Reader.RwError)!usize {
556 const in = reader.in;576 const in = reader.in;
557 len: switch (chunk_len_ptr.*) {577 len: switch (chunk_len_ptr.*) {
558 .head => {578 .head => {
559 var cp: ChunkParser = .init;579 var cp: ChunkParser = .init;
560 const i = cp.feed(in.bufferContents());580 const i = cp.feed(in.bufferContents());
561 switch (cp.state) {581 switch (cp.state) {
562 .invalid => return reader.failBody(error.HttpChunkInvalid),582 .invalid => return error.HttpChunkInvalid,
563 .data => {583 .data => {
564 if (i > max_chunk_header_len) return reader.failBody(error.HttpChunkInvalid);584 if (i > max_chunk_header_len) return error.HttpChunkInvalid;
565 in.toss(i);585 in.toss(i);
566 },586 },
567 else => {587 else => {
568 try endless(reader, in.fill(max_chunk_header_len));588 try in.fill(max_chunk_header_len);
569 const next_i = cp.feed(in.bufferContents()[i..]);589 const next_i = cp.feed(in.bufferContents()[i..]);
570 if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid);590 if (cp.state != .data) return error.HttpChunkInvalid;
571 const header_len = i + next_i;591 const header_len = i + next_i;
572 if (header_len > max_chunk_header_len) return reader.failBody(error.HttpChunkInvalid);592 if (header_len > max_chunk_header_len) return error.HttpChunkInvalid;
573 in.toss(header_len);593 in.toss(header_len);
574 },594 },
575 }595 }
576 if (cp.chunk_len == 0) return parseTrailers(reader, 0);596 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
577 const n = try endless(reader, in.read(bw, limit.min(.limited(cp.chunk_len))));597 const n = try in.read(bw, limit.min(.limited(cp.chunk_len)));
578 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);598 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
579 return n;599 return n;
580 },600 },
581 .n => {601 .n => {
582 if ((try in.peekByte()) != '\n') return reader.failBody(error.HttpChunkInvalid);602 if ((try in.peekByte()) != '\n') return error.HttpChunkInvalid;
583 in.toss(1);603 in.toss(1);
584 continue :len .head;604 continue :len .head;
585 },605 },
586 .rn => {606 .rn => {
587 const rn = try endless(reader, in.peekArray(2));607 const rn = try in.peekArray(2);
588 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);608 if (rn[0] != '\r' or rn[1] != '\n') return error.HttpChunkInvalid;
589 in.toss(2);609 in.toss(2);
590 continue :len .head;610 continue :len .head;
591 },611 },
592 else => |remaining_chunk_len| {612 else => |remaining_chunk_len| {
593 const n = try endless(reader, in.read(bw, limit.min(.limited(@intFromEnum(remaining_chunk_len) - 2))));613 const n = try in.read(bw, limit.min(.limited(@intFromEnum(remaining_chunk_len) - 2)));
594 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);614 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);
595 return n;615 return n;
596 },616 },
...@@ -604,6 +624,24 @@ pub const Reader = struct {...@@ -604,6 +624,24 @@ pub const Reader = struct {
604 .body_remaining_chunk_len => |*x| x,624 .body_remaining_chunk_len => |*x| x,
605 else => unreachable,625 else => unreachable,
606 };626 };
627 return chunkedReadVecEndless(reader, data, chunk_len_ptr) catch |err| switch (err) {
628 error.ReadFailed => return error.ReadFailed,
629 error.EndOfStream => {
630 reader.body_err = error.HttpChunkTruncated;
631 return error.ReadFailed;
632 },
633 else => |e| {
634 reader.body_err = e;
635 return error.ReadFailed;
636 },
637 };
638 }
639
640 fn chunkedReadVecEndless(
641 reader: *Reader,
642 data: []const []u8,
643 chunk_len_ptr: *RemainingChunkLen,
644 ) (BodyError || std.io.Reader.Error)!usize {
607 const in = reader.in;645 const in = reader.in;
608 var already_requested_more = false;646 var already_requested_more = false;
609 var amt_read: usize = 0;647 var amt_read: usize = 0;
...@@ -614,21 +652,21 @@ pub const Reader = struct {...@@ -614,21 +652,21 @@ pub const Reader = struct {
614 var cp: ChunkParser = .init;652 var cp: ChunkParser = .init;
615 const available_buffer = in.bufferContents();653 const available_buffer = in.bufferContents();
616 const i = cp.feed(available_buffer);654 const i = cp.feed(available_buffer);
617 if (cp.state == .invalid) return reader.failBody(error.HttpChunkInvalid);655 if (cp.state == .invalid) return error.HttpChunkInvalid;
618 if (i == available_buffer.len) {656 if (i == available_buffer.len) {
619 if (already_requested_more) {657 if (already_requested_more) {
620 chunk_len_ptr.* = .head;658 chunk_len_ptr.* = .head;
621 return amt_read;659 return amt_read;
622 }660 }
623 already_requested_more = true;661 already_requested_more = true;
624 try endless(reader, in.fill(max_chunk_header_len));662 try in.fill(max_chunk_header_len);
625 const next_i = cp.feed(in.bufferContents()[i..]);663 const next_i = cp.feed(in.bufferContents()[i..]);
626 if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid);664 if (cp.state != .data) return error.HttpChunkInvalid;
627 const header_len = i + next_i;665 const header_len = i + next_i;
628 if (header_len > max_chunk_header_len) return reader.failBody(error.HttpChunkInvalid);666 if (header_len > max_chunk_header_len) return error.HttpChunkInvalid;
629 in.toss(header_len);667 in.toss(header_len);
630 } else {668 } else {
631 if (i > max_chunk_header_len) return reader.failBody(error.HttpChunkInvalid);669 if (i > max_chunk_header_len) return error.HttpChunkInvalid;
632 in.toss(i);670 in.toss(i);
633 }671 }
634 if (cp.chunk_len == 0) return parseTrailers(reader, amt_read);672 if (cp.chunk_len == 0) return parseTrailers(reader, amt_read);
...@@ -636,13 +674,13 @@ pub const Reader = struct {...@@ -636,13 +674,13 @@ pub const Reader = struct {
636 },674 },
637 .n => {675 .n => {
638 if (in.bufferContents().len < 1) already_requested_more = true;676 if (in.bufferContents().len < 1) already_requested_more = true;
639 if ((try endless(reader, in.takeByte())) != '\n') return reader.failBody(error.HttpChunkInvalid);677 if ((try in.takeByte()) != '\n') return error.HttpChunkInvalid;
640 continue :len .head;678 continue :len .head;
641 },679 },
642 .rn => {680 .rn => {
643 if (in.bufferContents().len < 2) already_requested_more = true;681 if (in.bufferContents().len < 2) already_requested_more = true;
644 const rn = try endless(reader, in.takeArray(2));682 const rn = try in.takeArray(2);
645 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);683 if (rn[0] != '\r' or rn[1] != '\n') return error.HttpChunkInvalid;
646 continue :len .head;684 continue :len .head;
647 },685 },
648 else => |remaining_chunk_len| {686 else => |remaining_chunk_len| {
...@@ -657,12 +695,14 @@ pub const Reader = struct {...@@ -657,12 +695,14 @@ pub const Reader = struct {
657 chunk_len_ptr.* = next_chunk_len;695 chunk_len_ptr.* = next_chunk_len;
658 continue :data;696 continue :data;
659 }697 }
660 if (already_requested_more) {698 if (available_buffer.len - copy_len == 0) {
661 chunk_len_ptr.* = next_chunk_len;699 if (already_requested_more) {
662 return amt_read;700 chunk_len_ptr.* = next_chunk_len;
701 return amt_read;
702 }
703 already_requested_more = true;
704 try in.fillMore();
663 }705 }
664 already_requested_more = true;
665 try endless(reader, in.fillMore());
666 continue :len next_chunk_len;706 continue :len next_chunk_len;
667 },707 },
668 }708 }
...@@ -677,44 +717,62 @@ pub const Reader = struct {...@@ -677,44 +717,62 @@ pub const Reader = struct {
677 .body_remaining_chunk_len => |*x| x,717 .body_remaining_chunk_len => |*x| x,
678 else => unreachable,718 else => unreachable,
679 };719 };
720 return chunkedDiscardEndless(reader, limit, chunk_len_ptr) catch |err| switch (err) {
721 error.ReadFailed => return error.ReadFailed,
722 error.EndOfStream => {
723 reader.body_err = error.HttpChunkTruncated;
724 return error.ReadFailed;
725 },
726 else => |e| {
727 reader.body_err = e;
728 return error.ReadFailed;
729 },
730 };
731 }
732
733 fn chunkedDiscardEndless(
734 reader: *Reader,
735 limit: std.io.Reader.Limit,
736 chunk_len_ptr: *RemainingChunkLen,
737 ) (BodyError || std.io.Reader.Error)!usize {
680 const in = reader.in;738 const in = reader.in;
681 len: switch (chunk_len_ptr.*) {739 len: switch (chunk_len_ptr.*) {
682 .head => {740 .head => {
683 var cp: ChunkParser = .init;741 var cp: ChunkParser = .init;
684 const i = cp.feed(in.bufferContents());742 const i = cp.feed(in.bufferContents());
685 switch (cp.state) {743 switch (cp.state) {
686 .invalid => return reader.failBody(error.HttpChunkInvalid),744 .invalid => return error.HttpChunkInvalid,
687 .data => {745 .data => {
688 if (i > max_chunk_header_len) return reader.failBody(error.HttpChunkInvalid);746 if (i > max_chunk_header_len) return error.HttpChunkInvalid;
689 in.toss(i);747 in.toss(i);
690 },748 },
691 else => {749 else => {
692 try endless(reader, in.fill(max_chunk_header_len));750 try in.fill(max_chunk_header_len);
693 const next_i = cp.feed(in.bufferContents()[i..]);751 const next_i = cp.feed(in.bufferContents()[i..]);
694 if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid);752 if (cp.state != .data) return error.HttpChunkInvalid;
695 const header_len = i + next_i;753 const header_len = i + next_i;
696 if (header_len > max_chunk_header_len) return reader.failBody(error.HttpChunkInvalid);754 if (header_len > max_chunk_header_len) return error.HttpChunkInvalid;
697 in.toss(header_len);755 in.toss(header_len);
698 },756 },
699 }757 }
700 if (cp.chunk_len == 0) return parseTrailers(reader, 0);758 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
701 const n = try endless(reader, in.discard(limit.min(.limited(cp.chunk_len))));759 const n = try in.discard(limit.min(.limited(cp.chunk_len)));
702 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);760 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
703 return n;761 return n;
704 },762 },
705 .n => {763 .n => {
706 if ((try endless(reader, in.peekByte())) != '\n') return reader.failBody(error.HttpChunkInvalid);764 if ((try in.peekByte()) != '\n') return error.HttpChunkInvalid;
707 in.toss(1);765 in.toss(1);
708 continue :len .head;766 continue :len .head;
709 },767 },
710 .rn => {768 .rn => {
711 const rn = try endless(reader, in.peekArray(2));769 const rn = try in.peekArray(2);
712 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);770 if (rn[0] != '\r' or rn[1] != '\n') return error.HttpChunkInvalid;
713 in.toss(2);771 in.toss(2);
714 continue :len .head;772 continue :len .head;
715 },773 },
716 else => |remaining_chunk_len| {774 else => |remaining_chunk_len| {
717 const n = try endless(reader, in.discard(limit.min(.limited(remaining_chunk_len.int() - 2))));775 const n = try in.discard(limit.min(.limited(remaining_chunk_len.int() - 2)));
718 chunk_len_ptr.* = .init(remaining_chunk_len.int() - n);776 chunk_len_ptr.* = .init(remaining_chunk_len.int() - n);
719 return n;777 return n;
720 },778 },
...@@ -723,33 +781,29 @@ pub const Reader = struct {...@@ -723,33 +781,29 @@ pub const Reader = struct {
723781
724 /// Called when next bytes in the stream are trailers, or "\r\n" to indicate782 /// Called when next bytes in the stream are trailers, or "\r\n" to indicate
725 /// end of chunked body.783 /// end of chunked body.
726 fn parseTrailers(reader: *Reader, amt_read: usize) std.io.Reader.Error!usize {784 fn parseTrailers(reader: *Reader, amt_read: usize) (BodyError || std.io.Reader.Error)!usize {
727 const in = reader.in;785 const in = reader.in;
728 var hp: HeadParser = .{};786 const rn = try in.peekArray(2);
729 var trailers_len: usize = 0;787 if (rn[0] == '\r' and rn[1] == '\n') {
788 in.toss(2);
789 reader.state = .ready;
790 assert(reader.trailers.len == 0);
791 return amt_read;
792 }
793 var hp: HeadParser = .{ .state = .seen_rn };
794 var trailers_len: usize = 2;
730 while (true) {795 while (true) {
731 if (trailers_len >= in.buffer.len) return reader.failBody(error.HttpHeadersOversize);796 if (trailers_len >= in.buffer.len) return error.HttpHeadersOversize;
732 try in.fill(trailers_len + 1);797 try in.fill(trailers_len + 1);
733 trailers_len += hp.feed(in.bufferContents()[trailers_len..]);798 trailers_len += hp.feed(in.bufferContents()[trailers_len..]);
734 if (hp.state == .finished) {799 if (hp.state == .finished) {
735 reader.state = .ready;800 reader.state = .ready;
736 reader.trailers = in.bufferContents()[0..trailers_len];801 reader.trailers = in.bufferContents()[0..trailers_len];
802 in.toss(trailers_len);
737 return amt_read;803 return amt_read;
738 }804 }
739 }805 }
740 }806 }
741
742 fn failBody(r: *Reader, err: BodyError) error{ReadFailed} {
743 r.body_err = err;
744 return error.ReadFailed;
745 }
746
747 fn endless(r: *Reader, x: anytype) @TypeOf(x) {
748 return x catch |err| switch (err) {
749 error.EndOfStream => return failBody(r, error.HttpChunkTruncated),
750 else => return err,
751 };
752 }
753};807};
754808
755pub const Decompressor = struct {809pub const Decompressor = struct {