| ... | ... | @@ -331,9 +331,12 @@ pub const Reader = struct { |
| 331 | 331 | /// making invalid API usage cause assertion failures rather than HTTP |
| 332 | 332 | /// protocol violations. |
| 333 | 333 | state: State, |
| 334 | | /// Number of bytes of HTTP trailers. These are at the end of a |
| 335 | | /// transfer-encoding: chunked message. |
| 336 | | trailers_len: usize = 0, |
| 334 | /// HTTP trailer bytes. These are at the end of a transfer-encoding: |
| 335 | /// chunked message. This data is available only after calling one of the |
| 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 | 340 | body_err: ?BodyError = null, |
| 338 | 341 | /// Stolen from `in`. |
| 339 | 342 | head_buffer: []u8 = &.{}, |
| ... | ... | @@ -344,7 +347,6 @@ pub const Reader = struct { |
| 344 | 347 | head = 0, |
| 345 | 348 | n = 1, |
| 346 | 349 | rn = 2, |
| 347 | | done = std.math.maxInt(u64), |
| 348 | 350 | _, |
| 349 | 351 | |
| 350 | 352 | pub fn init(integer: u64) RemainingChunkLen { |
| ... | ... | @@ -371,6 +373,7 @@ pub const Reader = struct { |
| 371 | 373 | |
| 372 | 374 | pub const BodyError = error{ |
| 373 | 375 | HttpChunkInvalid, |
| 376 | HttpChunkTruncated, |
| 374 | 377 | HttpHeadersOversize, |
| 375 | 378 | }; |
| 376 | 379 | |
| ... | ... | @@ -393,6 +396,7 @@ pub const Reader = struct { |
| 393 | 396 | /// Buffers the entire head into `head_buffer`, invalidating the previous |
| 394 | 397 | /// `head_buffer`, if any. |
| 395 | 398 | pub fn receiveHead(reader: *Reader) HeadError!void { |
| 399 | reader.trailers = &.{}; |
| 396 | 400 | const in = reader.in; |
| 397 | 401 | in.restitute(reader.head_buffer.len); |
| 398 | 402 | in.rebase(); |
| ... | ... | @@ -544,7 +548,11 @@ pub const Reader = struct { |
| 544 | 548 | limit: std.io.Reader.Limit, |
| 545 | 549 | ) std.io.Reader.RwError!usize { |
| 546 | 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 | 556 | const in = reader.in; |
| 549 | 557 | len: switch (chunk_len_ptr.*) { |
| 550 | 558 | .head => { |
| ... | ... | @@ -557,7 +565,7 @@ pub const Reader = struct { |
| 557 | 565 | in.toss(i); |
| 558 | 566 | }, |
| 559 | 567 | else => { |
| 560 | | try in.fill(max_chunk_header_len); |
| 568 | try endless(reader, in.fill(max_chunk_header_len)); |
| 561 | 569 | const next_i = cp.feed(in.bufferContents()[i..]); |
| 562 | 570 | if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid); |
| 563 | 571 | const header_len = i + next_i; |
| ... | ... | @@ -566,7 +574,7 @@ pub const Reader = struct { |
| 566 | 574 | }, |
| 567 | 575 | } |
| 568 | 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 | 578 | chunk_len_ptr.* = .init(cp.chunk_len + 2 - n); |
| 571 | 579 | return n; |
| 572 | 580 | }, |
| ... | ... | @@ -576,27 +584,31 @@ pub const Reader = struct { |
| 576 | 584 | continue :len .head; |
| 577 | 585 | }, |
| 578 | 586 | .rn => { |
| 579 | | const rn = try in.peekArray(2); |
| 587 | const rn = try endless(reader, in.peekArray(2)); |
| 580 | 588 | if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid); |
| 581 | 589 | in.toss(2); |
| 582 | 590 | continue :len .head; |
| 583 | 591 | }, |
| 584 | 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 | 594 | chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n); |
| 587 | 595 | return n; |
| 588 | 596 | }, |
| 589 | | .done => return error.EndOfStream, |
| 590 | 597 | } |
| 591 | 598 | } |
| 592 | 599 | |
| 593 | 600 | fn chunkedReadVec(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize { |
| 594 | 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 | 607 | const in = reader.in; |
| 597 | 608 | var already_requested_more = false; |
| 598 | 609 | var amt_read: usize = 0; |
| 599 | 610 | data: for (data) |d| { |
| 611 | var d_i: usize = 0; |
| 600 | 612 | len: switch (chunk_len_ptr.*) { |
| 601 | 613 | .head => { |
| 602 | 614 | var cp: ChunkParser = .init; |
| ... | ... | @@ -609,7 +621,7 @@ pub const Reader = struct { |
| 609 | 621 | return amt_read; |
| 610 | 622 | } |
| 611 | 623 | already_requested_more = true; |
| 612 | | try in.fill(max_chunk_header_len); |
| 624 | try endless(reader, in.fill(max_chunk_header_len)); |
| 613 | 625 | const next_i = cp.feed(in.bufferContents()[i..]); |
| 614 | 626 | if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid); |
| 615 | 627 | const header_len = i + next_i; |
| ... | ... | @@ -624,23 +636,24 @@ pub const Reader = struct { |
| 624 | 636 | }, |
| 625 | 637 | .n => { |
| 626 | 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 | 640 | continue :len .head; |
| 629 | 641 | }, |
| 630 | 642 | .rn => { |
| 631 | 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 | 645 | if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid); |
| 634 | 646 | continue :len .head; |
| 635 | 647 | }, |
| 636 | 648 | else => |remaining_chunk_len| { |
| 637 | 649 | const available_buffer = in.bufferContents(); |
| 638 | | const copy_len = @min(available_buffer.len, d.len, remaining_chunk_len.int() - 2); |
| 639 | | @memcpy(d[0..copy_len], available_buffer[0..copy_len]); |
| 650 | const copy_len = @min(available_buffer.len, d.len - d_i, remaining_chunk_len.int() - 2); |
| 651 | @memcpy(d[d_i..][0..copy_len], available_buffer[0..copy_len]); |
| 652 | d_i += copy_len; |
| 640 | 653 | amt_read += copy_len; |
| 641 | 654 | in.toss(copy_len); |
| 642 | 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 | 657 | chunk_len_ptr.* = next_chunk_len; |
| 645 | 658 | continue :data; |
| 646 | 659 | } |
| ... | ... | @@ -649,10 +662,9 @@ pub const Reader = struct { |
| 649 | 662 | return amt_read; |
| 650 | 663 | } |
| 651 | 664 | already_requested_more = true; |
| 652 | | try in.fill(3); |
| 665 | try endless(reader, in.fillMore()); |
| 653 | 666 | continue :len next_chunk_len; |
| 654 | 667 | }, |
| 655 | | .done => return error.EndOfStream, |
| 656 | 668 | } |
| 657 | 669 | } |
| 658 | 670 | return amt_read; |
| ... | ... | @@ -660,7 +672,11 @@ pub const Reader = struct { |
| 660 | 672 | |
| 661 | 673 | fn chunkedDiscard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize { |
| 662 | 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 | 680 | const in = reader.in; |
| 665 | 681 | len: switch (chunk_len_ptr.*) { |
| 666 | 682 | .head => { |
| ... | ... | @@ -673,7 +689,7 @@ pub const Reader = struct { |
| 673 | 689 | in.toss(i); |
| 674 | 690 | }, |
| 675 | 691 | else => { |
| 676 | | try in.fill(max_chunk_header_len); |
| 692 | try endless(reader, in.fill(max_chunk_header_len)); |
| 677 | 693 | const next_i = cp.feed(in.bufferContents()[i..]); |
| 678 | 694 | if (cp.state != .data) return reader.failBody(error.HttpChunkInvalid); |
| 679 | 695 | const header_len = i + next_i; |
| ... | ... | @@ -682,27 +698,26 @@ pub const Reader = struct { |
| 682 | 698 | }, |
| 683 | 699 | } |
| 684 | 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 | 702 | chunk_len_ptr.* = .init(cp.chunk_len + 2 - n); |
| 687 | 703 | return n; |
| 688 | 704 | }, |
| 689 | 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 | 707 | in.toss(1); |
| 692 | 708 | continue :len .head; |
| 693 | 709 | }, |
| 694 | 710 | .rn => { |
| 695 | | const rn = try in.peekArray(2); |
| 711 | const rn = try endless(reader, in.peekArray(2)); |
| 696 | 712 | if (rn[0] != '\r' or rn[1] != '\n') return reader.failBody(error.HttpChunkInvalid); |
| 697 | 713 | in.toss(2); |
| 698 | 714 | continue :len .head; |
| 699 | 715 | }, |
| 700 | 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 | 718 | chunk_len_ptr.* = .init(remaining_chunk_len.int() - n); |
| 703 | 719 | return n; |
| 704 | 720 | }, |
| 705 | | .done => return error.EndOfStream, |
| 706 | 721 | } |
| 707 | 722 | } |
| 708 | 723 | |
| ... | ... | @@ -717,9 +732,8 @@ pub const Reader = struct { |
| 717 | 732 | try in.fill(trailers_len + 1); |
| 718 | 733 | trailers_len += hp.feed(in.bufferContents()[trailers_len..]); |
| 719 | 734 | if (hp.state == .finished) { |
| 720 | | reader.state.body_remaining_chunk_len = .done; |
| 721 | 735 | reader.state = .ready; |
| 722 | | reader.trailers_len = trailers_len; |
| 736 | reader.trailers = in.bufferContents()[0..trailers_len]; |
| 723 | 737 | return amt_read; |
| 724 | 738 | } |
| 725 | 739 | } |
| ... | ... | @@ -729,6 +743,13 @@ pub const Reader = struct { |
| 729 | 743 | r.body_err = err; |
| 730 | 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 | }; |
| 733 | 754 | |
| 734 | 755 | pub const Decompressor = struct { |
| ... | ... | @@ -823,18 +844,23 @@ pub const BodyWriter = struct { |
| 823 | 844 | }; |
| 824 | 845 | |
| 825 | 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 | 847 | pub fn flush(w: *BodyWriter) WriteError!void { |
| 848 | const out = w.http_protocol_output; |
| 830 | 849 | switch (w.state) { |
| 831 | | .end, .none, .content_length => return w.http_protocol_output.flush(), |
| 850 | .end, .none, .content_length => return out.flush(), |
| 832 | 851 | .chunked => |*chunked| switch (chunked.*) { |
| 833 | | .offset => |*offset| { |
| 834 | | try w.http_protocol_output.flushLimit(.limited(offset.*)); |
| 835 | | offset.* = 0; |
| 852 | .offset => |offset| { |
| 853 | const chunk_len = out.end - offset - chunk_header_template.len; |
| 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 | 901 | w.state = .end; |
| 876 | 902 | }, |
| 877 | 903 | .none => {}, |
| 878 | | .chunked => return endChunked(w, .{}), |
| 904 | .chunked => return endChunkedUnflushed(w, .{}), |
| 879 | 905 | } |
| 880 | 906 | } |
| 881 | 907 | |
| ... | ... | @@ -883,6 +909,21 @@ pub const BodyWriter = struct { |
| 883 | 909 | trailers: []const Header = &.{}, |
| 884 | 910 | }; |
| 885 | 911 | |
| 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 | 927 | /// Writes the end-of-stream message and any optional trailers. |
| 887 | 928 | /// |
| 888 | 929 | /// Does not flush. |
| ... | ... | @@ -892,9 +933,10 @@ pub const BodyWriter = struct { |
| 892 | 933 | /// Respects the value of `elide` to omit all data after the headers. |
| 893 | 934 | /// |
| 894 | 935 | /// See also: |
| 895 | | /// * `end` |
| 936 | /// * `endChunked` |
| 896 | 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 | 940 | const chunked = &w.state.chunked; |
| 899 | 941 | if (w.elide) { |
| 900 | 942 | w.state = .end; |