authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-05-04 20:01:31-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
log2abbcb6d2a199c9d535a950db6098b8224d510aa
treeadf4002d01fa728cc9b53f5a48d455f9ceff9d96
parent6fe9c8f03626651b7cafe01ffedac17b33588b29

std.http: fix chunked transfer flushing and end-of-stream handling


4 files changed, 119 insertions(+), 53 deletions(-)

lib/std/http.zig+81-39
...@@ -331,9 +331,12 @@ pub const Reader = struct {...@@ -331,9 +331,12 @@ pub const Reader = struct {
331 /// making invalid API usage cause assertion failures rather than HTTP331 /// making invalid API usage cause assertion failures rather than HTTP
332 /// protocol violations.332 /// protocol violations.
333 state: State,333 state: State,
334 /// Number of bytes of HTTP trailers. These are at the end of a334 /// HTTP trailer bytes. These are at the end of a transfer-encoding:
335 /// transfer-encoding: chunked message.335 /// chunked message. This data is available only after calling one of the
336 trailers_len: usize = 0,336 /// "end" functions and points to data inside the buffer of `in`, and is
337 /// therefore invalidated on the next call to `receiveHead`, or any other
338 /// read from `in`.
339 trailers: []const u8 = &.{},
337 body_err: ?BodyError = null,340 body_err: ?BodyError = null,
338 /// Stolen from `in`.341 /// Stolen from `in`.
339 head_buffer: []u8 = &.{},342 head_buffer: []u8 = &.{},
...@@ -344,7 +347,6 @@ pub const Reader = struct {...@@ -344,7 +347,6 @@ pub const Reader = struct {
344 head = 0,347 head = 0,
345 n = 1,348 n = 1,
346 rn = 2,349 rn = 2,
347 done = std.math.maxInt(u64),
348 _,350 _,
349351
350 pub fn init(integer: u64) RemainingChunkLen {352 pub fn init(integer: u64) RemainingChunkLen {
...@@ -371,6 +373,7 @@ pub const Reader = struct {...@@ -371,6 +373,7 @@ pub const Reader = struct {
371373
372 pub const BodyError = error{374 pub const BodyError = error{
373 HttpChunkInvalid,375 HttpChunkInvalid,
376 HttpChunkTruncated,
374 HttpHeadersOversize,377 HttpHeadersOversize,
375 };378 };
376379
...@@ -393,6 +396,7 @@ pub const Reader = struct {...@@ -393,6 +396,7 @@ pub const Reader = struct {
393 /// Buffers the entire head into `head_buffer`, invalidating the previous396 /// Buffers the entire head into `head_buffer`, invalidating the previous
394 /// `head_buffer`, if any.397 /// `head_buffer`, if any.
395 pub fn receiveHead(reader: *Reader) HeadError!void {398 pub fn receiveHead(reader: *Reader) HeadError!void {
399 reader.trailers = &.{};
396 const in = reader.in;400 const in = reader.in;
397 in.restitute(reader.head_buffer.len);401 in.restitute(reader.head_buffer.len);
398 in.rebase();402 in.rebase();
...@@ -544,7 +548,11 @@ pub const Reader = struct {...@@ -544,7 +548,11 @@ pub const Reader = struct {
544 limit: std.io.Reader.Limit,548 limit: std.io.Reader.Limit,
545 ) std.io.Reader.RwError!usize {549 ) std.io.Reader.RwError!usize {
546 const reader: *Reader = @alignCast(@ptrCast(ctx));550 const reader: *Reader = @alignCast(@ptrCast(ctx));
547 const chunk_len_ptr = &reader.state.body_remaining_chunk_len;551 const chunk_len_ptr = switch (reader.state) {
552 .ready => return error.EndOfStream,
553 .body_remaining_chunk_len => |*x| x,
554 else => unreachable,
555 };
548 const in = reader.in;556 const in = reader.in;
549 len: switch (chunk_len_ptr.*) {557 len: switch (chunk_len_ptr.*) {
550 .head => {558 .head => {
...@@ -557,7 +565,7 @@ pub const Reader = struct {...@@ -557,7 +565,7 @@ pub const Reader = struct {
557 in.toss(i);565 in.toss(i);
558 },566 },
559 else => {567 else => {
560 try in.fill(max_chunk_header_len);568 try endless(reader, in.fill(max_chunk_header_len));
561 const next_i = cp.feed(in.bufferContents()[i..]);569 const next_i = cp.feed(in.bufferContents()[i..]);
562 if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid);570 if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid);
563 const header_len = i + next_i;571 const header_len = i + next_i;
...@@ -566,7 +574,7 @@ pub const Reader = struct {...@@ -566,7 +574,7 @@ pub const Reader = struct {
566 },574 },
567 }575 }
568 if (cp.chunk_len == 0) return parseTrailers(reader, 0);576 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
569 const n = try in.read(bw, limit.min(.limited(cp.chunk_len)));577 const n = try endless(reader, in.read(bw, limit.min(.limited(cp.chunk_len))));
570 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);578 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
571 return n;579 return n;
572 },580 },
...@@ -576,27 +584,31 @@ pub const Reader = struct {...@@ -576,27 +584,31 @@ pub const Reader = struct {
576 continue :len .head;584 continue :len .head;
577 },585 },
578 .rn => {586 .rn => {
579 const rn = try in.peekArray(2);587 const rn = try endless(reader, in.peekArray(2));
580 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);588 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);
581 in.toss(2);589 in.toss(2);
582 continue :len .head;590 continue :len .head;
583 },591 },
584 else => |remaining_chunk_len| {592 else => |remaining_chunk_len| {
585 const n = try in.read(bw, limit.min(.limited(@intFromEnum(remaining_chunk_len) - 2)));593 const n = try endless(reader, in.read(bw, limit.min(.limited(@intFromEnum(remaining_chunk_len) - 2))));
586 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);594 chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n);
587 return n;595 return n;
588 },596 },
589 .done => return error.EndOfStream,
590 }597 }
591 }598 }
592599
593 fn chunkedReadVec(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize {600 fn chunkedReadVec(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize {
594 const reader: *Reader = @alignCast(@ptrCast(ctx));601 const reader: *Reader = @alignCast(@ptrCast(ctx));
595 const chunk_len_ptr = &reader.state.body_remaining_chunk_len;602 const chunk_len_ptr = switch (reader.state) {
603 .ready => return error.EndOfStream,
604 .body_remaining_chunk_len => |*x| x,
605 else => unreachable,
606 };
596 const in = reader.in;607 const in = reader.in;
597 var already_requested_more = false;608 var already_requested_more = false;
598 var amt_read: usize = 0;609 var amt_read: usize = 0;
599 data: for (data) |d| {610 data: for (data) |d| {
611 var d_i: usize = 0;
600 len: switch (chunk_len_ptr.*) {612 len: switch (chunk_len_ptr.*) {
601 .head => {613 .head => {
602 var cp: ChunkParser = .init;614 var cp: ChunkParser = .init;
...@@ -609,7 +621,7 @@ pub const Reader = struct {...@@ -609,7 +621,7 @@ pub const Reader = struct {
609 return amt_read;621 return amt_read;
610 }622 }
611 already_requested_more = true;623 already_requested_more = true;
612 try in.fill(max_chunk_header_len);624 try endless(reader, in.fill(max_chunk_header_len));
613 const next_i = cp.feed(in.bufferContents()[i..]);625 const next_i = cp.feed(in.bufferContents()[i..]);
614 if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid);626 if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid);
615 const header_len = i + next_i;627 const header_len = i + next_i;
...@@ -624,23 +636,24 @@ pub const Reader = struct {...@@ -624,23 +636,24 @@ pub const Reader = struct {
624 },636 },
625 .n => {637 .n => {
626 if (in.bufferContents().len < 1) already_requested_more = true;638 if (in.bufferContents().len < 1) already_requested_more = true;
627 if ((try in.takeByte()) != '\n') return reader.failBody(error.HttpChunkInvalid);639 if ((try endless(reader, in.takeByte())) != '\n') return reader.failBody(error.HttpChunkInvalid);
628 continue :len .head;640 continue :len .head;
629 },641 },
630 .rn => {642 .rn => {
631 if (in.bufferContents().len < 2) already_requested_more = true;643 if (in.bufferContents().len < 2) already_requested_more = true;
632 const rn = try in.takeArray(2);644 const rn = try endless(reader, in.takeArray(2));
633 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);645 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);
634 continue :len .head;646 continue :len .head;
635 },647 },
636 else => |remaining_chunk_len| {648 else => |remaining_chunk_len| {
637 const available_buffer = in.bufferContents();649 const available_buffer = in.bufferContents();
638 const copy_len = @min(available_buffer.len, d.len, remaining_chunk_len.int() - 2);650 const copy_len = @min(available_buffer.len, d.len - d_i, remaining_chunk_len.int() - 2);
639 @memcpy(d[0..copy_len], available_buffer[0..copy_len]);651 @memcpy(d[d_i..][0..copy_len], available_buffer[0..copy_len]);
652 d_i += copy_len;
640 amt_read += copy_len;653 amt_read += copy_len;
641 in.toss(copy_len);654 in.toss(copy_len);
642 const next_chunk_len: RemainingChunkLen = .init(remaining_chunk_len.int() - copy_len);655 const next_chunk_len: RemainingChunkLen = .init(remaining_chunk_len.int() - copy_len);
643 if (copy_len == d.len) {656 if (d.len - d_i == 0) {
644 chunk_len_ptr.* = next_chunk_len;657 chunk_len_ptr.* = next_chunk_len;
645 continue :data;658 continue :data;
646 }659 }
...@@ -649,10 +662,9 @@ pub const Reader = struct {...@@ -649,10 +662,9 @@ pub const Reader = struct {
649 return amt_read;662 return amt_read;
650 }663 }
651 already_requested_more = true;664 already_requested_more = true;
652 try in.fill(3);665 try endless(reader, in.fillMore());
653 continue :len next_chunk_len;666 continue :len next_chunk_len;
654 },667 },
655 .done => return error.EndOfStream,
656 }668 }
657 }669 }
658 return amt_read;670 return amt_read;
...@@ -660,7 +672,11 @@ pub const Reader = struct {...@@ -660,7 +672,11 @@ pub const Reader = struct {
660672
661 fn chunkedDiscard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize {673 fn chunkedDiscard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize {
662 const reader: *Reader = @alignCast(@ptrCast(ctx));674 const reader: *Reader = @alignCast(@ptrCast(ctx));
663 const chunk_len_ptr = &reader.state.body_remaining_chunk_len;675 const chunk_len_ptr = switch (reader.state) {
676 .ready => return error.EndOfStream,
677 .body_remaining_chunk_len => |*x| x,
678 else => unreachable,
679 };
664 const in = reader.in;680 const in = reader.in;
665 len: switch (chunk_len_ptr.*) {681 len: switch (chunk_len_ptr.*) {
666 .head => {682 .head => {
...@@ -673,7 +689,7 @@ pub const Reader = struct {...@@ -673,7 +689,7 @@ pub const Reader = struct {
673 in.toss(i);689 in.toss(i);
674 },690 },
675 else => {691 else => {
676 try in.fill(max_chunk_header_len);692 try endless(reader, in.fill(max_chunk_header_len));
677 const next_i = cp.feed(in.bufferContents()[i..]);693 const next_i = cp.feed(in.bufferContents()[i..]);
678 if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid);694 if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid);
679 const header_len = i + next_i;695 const header_len = i + next_i;
...@@ -682,27 +698,26 @@ pub const Reader = struct {...@@ -682,27 +698,26 @@ pub const Reader = struct {
682 },698 },
683 }699 }
684 if (cp.chunk_len == 0) return parseTrailers(reader, 0);700 if (cp.chunk_len == 0) return parseTrailers(reader, 0);
685 const n = try in.discard(limit.min(.limited(cp.chunk_len)));701 const n = try endless(reader, in.discard(limit.min(.limited(cp.chunk_len))));
686 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);702 chunk_len_ptr.* = .init(cp.chunk_len + 2 - n);
687 return n;703 return n;
688 },704 },
689 .n => {705 .n => {
690 if ((try in.peekByte()) != '\n') return reader.failBody(error.HttpChunkInvalid);706 if ((try endless(reader, in.peekByte())) != '\n') return reader.failBody(error.HttpChunkInvalid);
691 in.toss(1);707 in.toss(1);
692 continue :len .head;708 continue :len .head;
693 },709 },
694 .rn => {710 .rn => {
695 const rn = try in.peekArray(2);711 const rn = try endless(reader, in.peekArray(2));
696 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);712 if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid);
697 in.toss(2);713 in.toss(2);
698 continue :len .head;714 continue :len .head;
699 },715 },
700 else => |remaining_chunk_len| {716 else => |remaining_chunk_len| {
701 const n = try in.discard(limit.min(.limited(remaining_chunk_len.int() - 2)));717 const n = try endless(reader, in.discard(limit.min(.limited(remaining_chunk_len.int() - 2))));
702 chunk_len_ptr.* = .init(remaining_chunk_len.int() - n);718 chunk_len_ptr.* = .init(remaining_chunk_len.int() - n);
703 return n;719 return n;
704 },720 },
705 .done => return error.EndOfStream,
706 }721 }
707 }722 }
708723
...@@ -717,9 +732,8 @@ pub const Reader = struct {...@@ -717,9 +732,8 @@ pub const Reader = struct {
717 try in.fill(trailers_len + 1);732 try in.fill(trailers_len + 1);
718 trailers_len += hp.feed(in.bufferContents()[trailers_len..]);733 trailers_len += hp.feed(in.bufferContents()[trailers_len..]);
719 if (hp.state == .finished) {734 if (hp.state == .finished) {
720 reader.state.body_remaining_chunk_len = .done;
721 reader.state = .ready;735 reader.state = .ready;
722 reader.trailers_len = trailers_len;736 reader.trailers = in.bufferContents()[0..trailers_len];
723 return amt_read;737 return amt_read;
724 }738 }
725 }739 }
...@@ -729,6 +743,13 @@ pub const Reader = struct {...@@ -729,6 +743,13 @@ pub const Reader = struct {
729 r.body_err = err;743 r.body_err = err;
730 return error.ReadFailed;744 return error.ReadFailed;
731 }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 }
732};753};
733754
734pub const Decompressor = struct {755pub const Decompressor = struct {
...@@ -823,18 +844,23 @@ pub const BodyWriter = struct {...@@ -823,18 +844,23 @@ pub const BodyWriter = struct {
823 };844 };
824845
825 /// Sends all buffered data across `BodyWriter.http_protocol_output`.846 /// Sends all buffered data across `BodyWriter.http_protocol_output`.
826 ///
827 /// Some buffered data will remain if transfer-encoding is chunked and the
828 /// BodyWriter is mid-chunk.
829 pub fn flush(w: *BodyWriter) WriteError!void {847 pub fn flush(w: *BodyWriter) WriteError!void {
848 const out = w.http_protocol_output;
830 switch (w.state) {849 switch (w.state) {
831 .end, .none, .content_length => return w.http_protocol_output.flush(),850 .end, .none, .content_length => return out.flush(),
832 .chunked => |*chunked| switch (chunked.*) {851 .chunked => |*chunked| switch (chunked.*) {
833 .offset => |*offset| {852 .offset => |offset| {
834 try w.http_protocol_output.flushLimit(.limited(offset.*));853 const chunk_len = out.end - offset - chunk_header_template.len;
835 offset.* = 0;854 if (chunk_len > 0) {
855 writeHex(out.buffer[offset..][0..chunk_len_digits], chunk_len);
856 chunked.* = .{ .chunk_len = 2 };
857 } else {
858 out.end = offset;
859 chunked.* = .{ .chunk_len = 0 };
860 }
861 try out.flush();
836 },862 },
837 .chunk_len => return w.http_protocol_output.flush(),863 .chunk_len => return out.flush(),
838 },864 },
839 }865 }
840 }866 }
...@@ -875,7 +901,7 @@ pub const BodyWriter = struct {...@@ -875,7 +901,7 @@ pub const BodyWriter = struct {
875 w.state = .end;901 w.state = .end;
876 },902 },
877 .none => {},903 .none => {},
878 .chunked => return endChunked(w, .{}),904 .chunked => return endChunkedUnflushed(w, .{}),
879 }905 }
880 }906 }
881907
...@@ -883,6 +909,21 @@ pub const BodyWriter = struct {...@@ -883,6 +909,21 @@ pub const BodyWriter = struct {
883 trailers: []const Header = &.{},909 trailers: []const Header = &.{},
884 };910 };
885911
912 /// Writes the end-of-stream message and any optional trailers, flushing
913 /// the underlying stream.
914 ///
915 /// Asserts that the BodyWriter is using transfer-encoding: chunked.
916 ///
917 /// Respects the value of `elide` to omit all data after the headers.
918 ///
919 /// See also:
920 /// * `endChunkedUnflushed`
921 /// * `end`
922 pub fn endChunked(w: *BodyWriter, options: EndChunkedOptions) WriteError!void {
923 try endChunkedUnflushed(w, options);
924 try w.http_protocol_output.flush();
925 }
926
886 /// Writes the end-of-stream message and any optional trailers.927 /// Writes the end-of-stream message and any optional trailers.
887 ///928 ///
888 /// Does not flush.929 /// Does not flush.
...@@ -892,9 +933,10 @@ pub const BodyWriter = struct {...@@ -892,9 +933,10 @@ pub const BodyWriter = struct {
892 /// Respects the value of `elide` to omit all data after the headers.933 /// Respects the value of `elide` to omit all data after the headers.
893 ///934 ///
894 /// See also:935 /// See also:
895 /// * `end`936 /// * `endChunked`
896 /// * `endUnflushed`937 /// * `endUnflushed`
897 pub fn endChunked(w: *BodyWriter, options: EndChunkedOptions) WriteError!void {938 /// * `end`
939 pub fn endChunkedUnflushed(w: *BodyWriter, options: EndChunkedOptions) WriteError!void {
898 const chunked = &w.state.chunked;940 const chunked = &w.state.chunked;
899 if (w.elide) {941 if (w.elide) {
900 w.state = .end;942 w.state = .end;
lib/std/http/Client.zig+11
...@@ -703,6 +703,16 @@ pub const Response = struct {...@@ -703,6 +703,16 @@ pub const Response = struct {
703 pub fn bodyErr(response: *const Response) ?http.Reader.BodyError {703 pub fn bodyErr(response: *const Response) ?http.Reader.BodyError {
704 return response.request.reader.body_err;704 return response.request.reader.body_err;
705 }705 }
706
707 pub fn iterateTrailers(response: *const Response) http.HeaderIterator {
708 const r = &response.request.reader;
709 assert(r.state == .ready);
710 return .{
711 .bytes = r.trailers,
712 .index = 0,
713 .is_trailer = true,
714 };
715 }
706};716};
707717
708pub const Request = struct {718pub const Request = struct {
...@@ -951,6 +961,7 @@ pub const Request = struct {...@@ -951,6 +961,7 @@ pub const Request = struct {
951 HttpRedirectLocationInvalid,961 HttpRedirectLocationInvalid,
952 HttpContentEncodingUnsupported,962 HttpContentEncodingUnsupported,
953 HttpChunkInvalid,963 HttpChunkInvalid,
964 HttpChunkTruncated,
954 HttpHeadersOversize,965 HttpHeadersOversize,
955 UnsupportedUriScheme,966 UnsupportedUriScheme,
956967
lib/std/http/test.zig+4-2
...@@ -72,20 +72,22 @@ test "trailers" {...@@ -72,20 +72,22 @@ test "trailers" {
7272
73 try expectEqualStrings("Hello, World!\n", body);73 try expectEqualStrings("Hello, World!\n", body);
7474
75 var it = response.head.iterateHeaders();
76 {75 {
76 var it = response.head.iterateHeaders();
77 const header = it.next().?;77 const header = it.next().?;
78 try expect(!it.is_trailer);78 try expect(!it.is_trailer);
79 try expectEqualStrings("transfer-encoding", header.name);79 try expectEqualStrings("transfer-encoding", header.name);
80 try expectEqualStrings("chunked", header.value);80 try expectEqualStrings("chunked", header.value);
81 try expectEqual(null, it.next());
81 }82 }
82 {83 {
84 var it = response.iterateTrailers();
83 const header = it.next().?;85 const header = it.next().?;
84 try expect(it.is_trailer);86 try expect(it.is_trailer);
85 try expectEqualStrings("X-Checksum", header.name);87 try expectEqualStrings("X-Checksum", header.name);
86 try expectEqualStrings("aaaa", header.value);88 try expectEqualStrings("aaaa", header.value);
89 try expectEqual(null, it.next());
87 }90 }
88 try expectEqual(null, it.next());
89 }91 }
9092
91 // connection has been kept alive93 // connection has been kept alive
lib/std/io/BufferedReader.zig+23-12
...@@ -357,37 +357,48 @@ pub fn discardRemaining(br: *BufferedReader) Reader.ShortError!usize {...@@ -357,37 +357,48 @@ pub fn discardRemaining(br: *BufferedReader) Reader.ShortError!usize {
357///357///
358/// See also:358/// See also:
359/// * `peek`359/// * `peek`
360/// * `readSliceShort`
360pub fn readSlice(br: *BufferedReader, buffer: []u8) Reader.Error!void {361pub fn readSlice(br: *BufferedReader, buffer: []u8) Reader.Error!void {
362 const n = try readSliceShort(br, buffer);
363 if (n != buffer.len) return error.EndOfStream;
364}
365
366/// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing
367/// the seek position.
368///
369/// Invalidates previously returned values from `peek`.
370///
371/// Returns the number of bytes read, which is less than `buffer.len` if and
372/// only if the stream reached the end.
373///
374/// See also:
375/// * `readSlice`
376pub fn readSliceShort(br: *BufferedReader, buffer: []u8) Reader.ShortError!usize {
361 const in_buffer = br.buffer[br.seek..br.end];377 const in_buffer = br.buffer[br.seek..br.end];
362 const copy_len = @min(buffer.len, in_buffer.len);378 const copy_len = @min(buffer.len, in_buffer.len);
363 @memcpy(buffer[0..copy_len], in_buffer[0..copy_len]);379 @memcpy(buffer[0..copy_len], in_buffer[0..copy_len]);
364 if (copy_len == buffer.len) {380 if (buffer.len - copy_len == 0) {
365 br.seek += copy_len;381 br.seek += copy_len;
366 return;382 return buffer.len;
367 }383 }
368 var i: usize = copy_len;384 var i: usize = copy_len;
369 br.end = 0;385 br.end = 0;
370 br.seek = 0;386 br.seek = 0;
371 while (true) {387 while (true) {
372 const remaining = buffer[i..];388 const remaining = buffer[i..];
373 const n = try br.unbuffered_reader.readVec(&.{ remaining, br.buffer });389 const n = br.unbuffered_reader.readVec(&.{ remaining, br.buffer }) catch |err| switch (err) {
390 error.EndOfStream => return i,
391 error.ReadFailed => return error.ReadFailed,
392 };
374 if (n < remaining.len) {393 if (n < remaining.len) {
375 i += n;394 i += n;
376 continue;395 continue;
377 }396 }
378 br.end = n - remaining.len;397 br.end = n - remaining.len;
379 return;398 return buffer.len;
380 }399 }
381}400}
382401
383/// Returns the number of bytes read, which is less than `buffer.len` if and
384/// only if the stream reached the end.
385pub fn readSliceShort(br: *BufferedReader, buffer: []u8) Reader.ShortError!usize {
386 _ = br;
387 _ = buffer;
388 @panic("TODO");
389}
390
391pub const ReadAllocError = Reader.Error || Allocator.Error;402pub const ReadAllocError = Reader.Error || Allocator.Error;
392403
393/// The function is inline to avoid the dead code in case `endian` is404/// The function is inline to avoid the dead code in case `endian` is