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 {
553553 .body_remaining_chunk_len => |*x| x,
554554 else => unreachable,
555555 };
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 {
556576 const in = reader.in;
557577 len: switch (chunk_len_ptr.*) {
558578 .head => {
559579 var cp: ChunkParser = .init;
560580 const i = cp.feed(in.bufferContents());
561581 switch (cp.state) {
562 .invalid => return reader.failBody(error.HttpChunkInvalid),
582 .invalid => return error.HttpChunkInvalid,
563583 .data => {
564 if (i > max_chunk_header_len) return reader.failBody(error.HttpChunkInvalid);
584 if (i > max_chunk_header_len) return error.HttpChunkInvalid;
565585 in.toss(i);
566586 },
567587 else => {
568 try endless(reader, in.fill(max_chunk_header_len));
588 try in.fill(max_chunk_header_len);
569589 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;
571591 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;
573593 in.toss(header_len);
574594 },
575595 }
576596 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)));
578598 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
579599 return n;
580600 },
581601 .n => {
582 if ((try in.peekByte()) != '\n') return reader.failBody(error.HttpChunkInvalid);
602 if ((try in.peekByte()) != '\n') return error.HttpChunkInvalid;
583603 in.toss(1);
584604 continue :len .head;
585605 },
586606 .rn => {
587 const rn = try endless(reader, in.peekArray(2));
588 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);
607 const rn = try in.peekArray(2);
608 if (rn[0] != '\r' or rn[1] != '\n') return error.HttpChunkInvalid;
589609 in.toss(2);
590610 continue :len .head;
591611 },
592612 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)));
594614 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);
595615 return n;
596616 },
......@@ -604,6 +624,24 @@ pub const Reader = struct {
604624 .body_remaining_chunk_len => |*x| x,
605625 else => unreachable,
606626 };
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 {
607645 const in = reader.in;
608646 var already_requested_more = false;
609647 var amt_read: usize = 0;
......@@ -614,21 +652,21 @@ pub const Reader = struct {
614652 var cp: ChunkParser = .init;
615653 const available_buffer = in.bufferContents();
616654 const i = cp.feed(available_buffer);
617 if (cp.state == .invalid) return reader.failBody(error.HttpChunkInvalid);
655 if (cp.state == .invalid) return error.HttpChunkInvalid;
618656 if (i == available_buffer.len) {
619657 if (already_requested_more) {
620658 chunk_len_ptr.* = .head;
621659 return amt_read;
622660 }
623661 already_requested_more = true;
624 try endless(reader, in.fill(max_chunk_header_len));
662 try in.fill(max_chunk_header_len);
625663 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;
627665 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;
629667 in.toss(header_len);
630668 } else {
631 if (i > max_chunk_header_len) return reader.failBody(error.HttpChunkInvalid);
669 if (i > max_chunk_header_len) return error.HttpChunkInvalid;
632670 in.toss(i);
633671 }
634672 if (cp.chunk_len == 0) return parseTrailers(reader, amt_read);
......@@ -636,13 +674,13 @@ pub const Reader = struct {
636674 },
637675 .n => {
638676 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;
640678 continue :len .head;
641679 },
642680 .rn => {
643681 if (in.bufferContents().len < 2) already_requested_more = true;
644 const rn = try endless(reader, in.takeArray(2));
645 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);
682 const rn = try in.takeArray(2);
683 if (rn[0] != '\r' or rn[1] != '\n') return error.HttpChunkInvalid;
646684 continue :len .head;
647685 },
648686 else => |remaining_chunk_len| {
......@@ -657,12 +695,14 @@ pub const Reader = struct {
657695 chunk_len_ptr.* = next_chunk_len;
658696 continue :data;
659697 }
660 if (already_requested_more) {
661 chunk_len_ptr.* = next_chunk_len;
662 return amt_read;
698 if (available_buffer.len - copy_len == 0) {
699 if (already_requested_more) {
700 chunk_len_ptr.* = next_chunk_len;
701 return amt_read;
702 }
703 already_requested_more = true;
704 try in.fillMore();
663705 }
664 already_requested_more = true;
665 try endless(reader, in.fillMore());
666706 continue :len next_chunk_len;
667707 },
668708 }
......@@ -677,44 +717,62 @@ pub const Reader = struct {
677717 .body_remaining_chunk_len => |*x| x,
678718 else => unreachable,
679719 };
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 {
680738 const in = reader.in;
681739 len: switch (chunk_len_ptr.*) {
682740 .head => {
683741 var cp: ChunkParser = .init;
684742 const i = cp.feed(in.bufferContents());
685743 switch (cp.state) {
686 .invalid => return reader.failBody(error.HttpChunkInvalid),
744 .invalid => return error.HttpChunkInvalid,
687745 .data => {
688 if (i > max_chunk_header_len) return reader.failBody(error.HttpChunkInvalid);
746 if (i > max_chunk_header_len) return error.HttpChunkInvalid;
689747 in.toss(i);
690748 },
691749 else => {
692 try endless(reader, in.fill(max_chunk_header_len));
750 try in.fill(max_chunk_header_len);
693751 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;
695753 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;
697755 in.toss(header_len);
698756 },
699757 }
700758 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)));
702760 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
703761 return n;
704762 },
705763 .n => {
706 if ((try endless(reader, in.peekByte())) != '\n') return reader.failBody(error.HttpChunkInvalid);
764 if ((try in.peekByte()) != '\n') return error.HttpChunkInvalid;
707765 in.toss(1);
708766 continue :len .head;
709767 },
710768 .rn => {
711 const rn = try endless(reader, in.peekArray(2));
712 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);
769 const rn = try in.peekArray(2);
770 if (rn[0] != '\r' or rn[1] != '\n') return error.HttpChunkInvalid;
713771 in.toss(2);
714772 continue :len .head;
715773 },
716774 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)));
718776 chunk_len_ptr.* = .init(remaining_chunk_len.int() - n);
719777 return n;
720778 },
......@@ -723,33 +781,29 @@ pub const Reader = struct {
723781
724782 /// Called when next bytes in the stream are trailers, or "\r\n" to indicate
725783 /// 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 {
727785 const in = reader.in;
728 var hp: HeadParser = .{};
729 var trailers_len: usize = 0;
786 const rn = try in.peekArray(2);
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;
730795 while (true) {
731 if (trailers_len >= in.buffer.len) return reader.failBody(error.HttpHeadersOversize);
796 if (trailers_len >= in.buffer.len) return error.HttpHeadersOversize;
732797 try in.fill(trailers_len + 1);
733798 trailers_len += hp.feed(in.bufferContents()[trailers_len..]);
734799 if (hp.state == .finished) {
735800 reader.state = .ready;
736801 reader.trailers = in.bufferContents()[0..trailers_len];
802 in.toss(trailers_len);
737803 return amt_read;
738804 }
739805 }
740806 }
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 }
753807};
754808
755809pub const Decompressor = struct {