| ... | @@ -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 HTTP | 331 | /// 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 a | 334 | /// 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 | _, |
| 349 | | 351 | |
| 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 { |
| 371 | | 373 | |
| 372 | pub const BodyError = error{ | 374 | pub const BodyError = error{ |
| 373 | HttpChunkInvalid, | 375 | HttpChunkInvalid, |
| | 376 | HttpChunkTruncated, |
| 374 | HttpHeadersOversize, | 377 | HttpHeadersOversize, |
| 375 | }; | 378 | }; |
| 376 | | 379 | |
| ... | @@ -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 previous | 396 | /// 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 | } |
| 592 | | 599 | |
| 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 { |
| 660 | | 672 | |
| 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 | } |
| 708 | | 723 | |
| ... | @@ -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 | }; |
| 733 | | 754 | |
| 734 | pub const Decompressor = struct { | 755 | pub const Decompressor = struct { |
| ... | @@ -823,18 +844,23 @@ pub const BodyWriter = struct { | ... | @@ -823,18 +844,23 @@ pub const BodyWriter = struct { |
| 823 | }; | 844 | }; |
| 824 | | 845 | |
| 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 | } |
| 881 | | 907 | |
| ... | @@ -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 | }; |
| 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 | /// 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; |