| ... | ... | @@ -82,7 +82,7 @@ pub const HeadersParser = struct { |
| 82 | 82 | /// If the amount returned is less than `bytes.len`, you may assume that the parser is in a content state and the |
| 83 | 83 | /// first byte of content is located at `bytes[result]`. |
| 84 | 84 | pub fn findHeadersEnd(r: *HeadersParser, bytes: []const u8) u32 { |
| 85 | | const vector_len: comptime_int = comptime std.simd.suggestVectorSize(u8) orelse 8; |
| 85 | const vector_len: comptime_int = comptime std.math.max(std.simd.suggestVectorSize(u8) orelse 1, 8); |
| 86 | 86 | const len = @intCast(u32, bytes.len); |
| 87 | 87 | var index: u32 = 0; |
| 88 | 88 | |
| ... | ... | @@ -232,7 +232,7 @@ pub const HeadersParser = struct { |
| 232 | 232 | else => {}, |
| 233 | 233 | } |
| 234 | 234 | }, |
| 235 | | 4...vector_len - 1 => { |
| 235 | 4...vector_len => { |
| 236 | 236 | inline for (0..vector_len - 3) |i_usize| { |
| 237 | 237 | const i = @truncate(u32, i_usize); |
| 238 | 238 | |
| ... | ... | @@ -311,6 +311,7 @@ pub const HeadersParser = struct { |
| 311 | 311 | |
| 312 | 312 | switch (b16) { |
| 313 | 313 | int16("\r\n") => r.state = .seen_rn, |
| 314 | int16("\n\r") => r.state = .seen_rnr, |
| 314 | 315 | int16("\n\n") => r.state = .finished, |
| 315 | 316 | else => {}, |
| 316 | 317 | } |
| ... | ... | @@ -614,6 +615,86 @@ inline fn intShift(comptime T: type, x: anytype) T { |
| 614 | 615 | } |
| 615 | 616 | } |
| 616 | 617 | |
| 618 | /// A buffered (and peekable) Connection. |
| 619 | const MockBufferedConnection = struct { |
| 620 | pub const buffer_size = 0x2000; |
| 621 | |
| 622 | conn: std.io.FixedBufferStream([]const u8), |
| 623 | buf: [buffer_size]u8 = undefined, |
| 624 | start: u16 = 0, |
| 625 | end: u16 = 0, |
| 626 | |
| 627 | pub fn fill(bconn: *MockBufferedConnection) ReadError!void { |
| 628 | if (bconn.end != bconn.start) return; |
| 629 | |
| 630 | const nread = try bconn.conn.read(bconn.buf[0..]); |
| 631 | if (nread == 0) return error.EndOfStream; |
| 632 | bconn.start = 0; |
| 633 | bconn.end = @truncate(u16, nread); |
| 634 | } |
| 635 | |
| 636 | pub fn peek(bconn: *MockBufferedConnection) []const u8 { |
| 637 | return bconn.buf[bconn.start..bconn.end]; |
| 638 | } |
| 639 | |
| 640 | pub fn clear(bconn: *MockBufferedConnection, num: u16) void { |
| 641 | bconn.start += num; |
| 642 | } |
| 643 | |
| 644 | pub fn readAtLeast(bconn: *MockBufferedConnection, buffer: []u8, len: usize) ReadError!usize { |
| 645 | var out_index: u16 = 0; |
| 646 | while (out_index < len) { |
| 647 | const available = bconn.end - bconn.start; |
| 648 | const left = buffer.len - out_index; |
| 649 | |
| 650 | if (available > 0) { |
| 651 | const can_read = @truncate(u16, @min(available, left)); |
| 652 | |
| 653 | std.mem.copy(u8, buffer[out_index..], bconn.buf[bconn.start..][0..can_read]); |
| 654 | out_index += can_read; |
| 655 | bconn.start += can_read; |
| 656 | |
| 657 | continue; |
| 658 | } |
| 659 | |
| 660 | if (left > bconn.buf.len) { |
| 661 | // skip the buffer if the output is large enough |
| 662 | return bconn.conn.read(buffer[out_index..]); |
| 663 | } |
| 664 | |
| 665 | try bconn.fill(); |
| 666 | } |
| 667 | |
| 668 | return out_index; |
| 669 | } |
| 670 | |
| 671 | pub fn read(bconn: *MockBufferedConnection, buffer: []u8) ReadError!usize { |
| 672 | return bconn.readAtLeast(buffer, 1); |
| 673 | } |
| 674 | |
| 675 | pub const ReadError = std.io.FixedBufferStream([]const u8).ReadError || error{EndOfStream}; |
| 676 | pub const Reader = std.io.Reader(*MockBufferedConnection, ReadError, read); |
| 677 | |
| 678 | pub fn reader(bconn: *MockBufferedConnection) Reader { |
| 679 | return Reader{ .context = bconn }; |
| 680 | } |
| 681 | |
| 682 | pub fn writeAll(bconn: *MockBufferedConnection, buffer: []const u8) WriteError!void { |
| 683 | return bconn.conn.writeAll(buffer); |
| 684 | } |
| 685 | |
| 686 | pub fn write(bconn: *MockBufferedConnection, buffer: []const u8) WriteError!usize { |
| 687 | return bconn.conn.write(buffer); |
| 688 | } |
| 689 | |
| 690 | pub const WriteError = std.io.FixedBufferStream([]const u8).WriteError; |
| 691 | pub const Writer = std.io.Writer(*MockBufferedConnection, WriteError, write); |
| 692 | |
| 693 | pub fn writer(bconn: *MockBufferedConnection) Writer { |
| 694 | return Writer{ .context = bconn }; |
| 695 | } |
| 696 | }; |
| 697 | |
| 617 | 698 | test "HeadersParser.findHeadersEnd" { |
| 618 | 699 | var r: HeadersParser = undefined; |
| 619 | 700 | const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\nHello"; |
| ... | ... | @@ -662,18 +743,29 @@ test "HeadersParser.findChunkedLen" { |
| 662 | 743 | |
| 663 | 744 | test "HeadersParser.read length" { |
| 664 | 745 | // mock BufferedConnection for read |
| 665 | | if (true) return error.SkipZigTest; |
| 666 | 746 | |
| 667 | 747 | var r = HeadersParser.initDynamic(256); |
| 668 | 748 | defer r.header_bytes.deinit(std.testing.allocator); |
| 669 | 749 | const data = "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 5\r\n\r\nHello"; |
| 670 | 750 | var fbs = std.io.fixedBufferStream(data); |
| 671 | 751 | |
| 672 | | try r.waitForCompleteHead(fbs.reader(), std.testing.allocator); |
| 752 | var bconn = MockBufferedConnection{ |
| 753 | .conn = fbs, |
| 754 | }; |
| 755 | |
| 756 | while (true) { // read headers |
| 757 | try bconn.fill(); |
| 758 | |
| 759 | const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek()); |
| 760 | bconn.clear(@intCast(u16, nchecked)); |
| 761 | |
| 762 | if (r.state.isContent()) break; |
| 763 | } |
| 764 | |
| 673 | 765 | var buf: [8]u8 = undefined; |
| 674 | 766 | |
| 675 | 767 | r.next_chunk_length = 5; |
| 676 | | const len = try r.read(fbs.reader(), &buf, false); |
| 768 | const len = try r.read(&bconn, &buf, false); |
| 677 | 769 | try std.testing.expectEqual(@as(usize, 5), len); |
| 678 | 770 | try std.testing.expectEqualStrings("Hello", buf[0..len]); |
| 679 | 771 | |
| ... | ... | @@ -682,18 +774,28 @@ test "HeadersParser.read length" { |
| 682 | 774 | |
| 683 | 775 | test "HeadersParser.read chunked" { |
| 684 | 776 | // mock BufferedConnection for read |
| 685 | | if (true) return error.SkipZigTest; |
| 686 | 777 | |
| 687 | 778 | var r = HeadersParser.initDynamic(256); |
| 688 | 779 | defer r.header_bytes.deinit(std.testing.allocator); |
| 689 | 780 | const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\n\r\n"; |
| 690 | 781 | var fbs = std.io.fixedBufferStream(data); |
| 691 | 782 | |
| 692 | | try r.waitForCompleteHead(fbs.reader(), std.testing.allocator); |
| 783 | var bconn = MockBufferedConnection{ |
| 784 | .conn = fbs, |
| 785 | }; |
| 786 | |
| 787 | while (true) { // read headers |
| 788 | try bconn.fill(); |
| 789 | |
| 790 | const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek()); |
| 791 | bconn.clear(@intCast(u16, nchecked)); |
| 792 | |
| 793 | if (r.state.isContent()) break; |
| 794 | } |
| 693 | 795 | var buf: [8]u8 = undefined; |
| 694 | 796 | |
| 695 | 797 | r.state = .chunk_head_size; |
| 696 | | const len = try r.read(fbs.reader(), &buf, false); |
| 798 | const len = try r.read(&bconn, &buf, false); |
| 697 | 799 | try std.testing.expectEqual(@as(usize, 5), len); |
| 698 | 800 | try std.testing.expectEqualStrings("Hello", buf[0..len]); |
| 699 | 801 | |
| ... | ... | @@ -702,22 +804,39 @@ test "HeadersParser.read chunked" { |
| 702 | 804 | |
| 703 | 805 | test "HeadersParser.read chunked trailer" { |
| 704 | 806 | // mock BufferedConnection for read |
| 705 | | if (true) return error.SkipZigTest; |
| 706 | 807 | |
| 707 | 808 | var r = HeadersParser.initDynamic(256); |
| 708 | 809 | defer r.header_bytes.deinit(std.testing.allocator); |
| 709 | 810 | const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n2\r\nHe\r\n2\r\nll\r\n1\r\no\r\n0\r\nContent-Type: text/plain\r\n\r\n"; |
| 710 | 811 | var fbs = std.io.fixedBufferStream(data); |
| 711 | 812 | |
| 712 | | try r.waitForCompleteHead(fbs.reader(), std.testing.allocator); |
| 813 | var bconn = MockBufferedConnection{ |
| 814 | .conn = fbs, |
| 815 | }; |
| 816 | |
| 817 | while (true) { // read headers |
| 818 | try bconn.fill(); |
| 819 | |
| 820 | const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek()); |
| 821 | bconn.clear(@intCast(u16, nchecked)); |
| 822 | |
| 823 | if (r.state.isContent()) break; |
| 824 | } |
| 713 | 825 | var buf: [8]u8 = undefined; |
| 714 | 826 | |
| 715 | 827 | r.state = .chunk_head_size; |
| 716 | | const len = try r.read(fbs.reader(), &buf, false); |
| 828 | const len = try r.read(&bconn, &buf, false); |
| 717 | 829 | try std.testing.expectEqual(@as(usize, 5), len); |
| 718 | 830 | try std.testing.expectEqualStrings("Hello", buf[0..len]); |
| 719 | 831 | |
| 720 | | try r.waitForCompleteHead(fbs.reader(), std.testing.allocator); |
| 832 | while (true) { // read headers |
| 833 | try bconn.fill(); |
| 834 | |
| 835 | const nchecked = try r.checkCompleteHead(std.testing.allocator, bconn.peek()); |
| 836 | bconn.clear(@intCast(u16, nchecked)); |
| 837 | |
| 838 | if (r.state.isContent()) break; |
| 839 | } |
| 721 | 840 | |
| 722 | 841 | try std.testing.expectEqualStrings("GET / HTTP/1.1\r\nHost: localhost\r\n\r\nContent-Type: text/plain\r\n\r\n", r.header_bytes.items); |
| 723 | 842 | } |