| ... | ... | @@ -22,9 +22,6 @@ out: *std.io.BufferedWriter, |
| 22 | 22 | state: State, |
| 23 | 23 | head_parse_err: Request.Head.ParseError, |
| 24 | 24 | |
| 25 | | /// being deleted... |
| 26 | | next_request_start: usize = 0, |
| 27 | | |
| 28 | 25 | pub const State = enum { |
| 29 | 26 | /// The connection is available to be used for the first time, or reused. |
| 30 | 27 | ready, |
| ... | ... | @@ -95,6 +92,8 @@ pub fn receiveHead(s: *Server) ReceiveHeadError!Request { |
| 95 | 92 | if (hp.state == .finished) return .{ |
| 96 | 93 | .server = s, |
| 97 | 94 | .head_end = head_end, |
| 95 | .trailers_len = 0, |
| 96 | .read_err = null, |
| 98 | 97 | .head = Request.Head.parse(buf[0..head_end]) catch |err| { |
| 99 | 98 | s.head_parse_err = err; |
| 100 | 99 | return error.HttpHeadersInvalid; |
| ... | ... | @@ -108,11 +107,38 @@ pub const Request = struct { |
| 108 | 107 | server: *Server, |
| 109 | 108 | /// Index into `Server.in` internal buffer. |
| 110 | 109 | head_end: usize, |
| 110 | /// Number of bytes of HTTP trailers. These are at the end of a |
| 111 | /// transfer-encoding: chunked message. |
| 112 | trailers_len: usize, |
| 111 | 113 | head: Head, |
| 112 | 114 | reader_state: union { |
| 113 | 115 | remaining_content_length: u64, |
| 114 | | chunk_parser: http.ChunkParser, |
| 116 | remaining_chunk_len: RemainingChunkLen, |
| 115 | 117 | }, |
| 118 | read_err: ?ReadError, |
| 119 | |
| 120 | pub const ReadError = error{ |
| 121 | HttpChunkInvalid, |
| 122 | HttpHeadersOversize, |
| 123 | }; |
| 124 | |
| 125 | pub const max_chunk_header_len = 22; |
| 126 | |
| 127 | pub const RemainingChunkLen = enum(u64) { |
| 128 | head = 0, |
| 129 | n = 1, |
| 130 | rn = 2, |
| 131 | done = std.math.maxInt(u64), |
| 132 | _, |
| 133 | |
| 134 | pub fn init(integer: u64) RemainingChunkLen { |
| 135 | return @enumFromInt(integer); |
| 136 | } |
| 137 | |
| 138 | pub fn int(rcl: RemainingChunkLen) u64 { |
| 139 | return @intFromEnum(rcl); |
| 140 | } |
| 141 | }; |
| 116 | 142 | |
| 117 | 143 | pub const Compression = union(enum) { |
| 118 | 144 | deflate: std.compress.zlib.Decompressor, |
| ... | ... | @@ -559,177 +585,240 @@ pub const Request = struct { |
| 559 | 585 | }; |
| 560 | 586 | } |
| 561 | 587 | |
| 562 | | pub const ReadError = net.Stream.ReadError || error{ |
| 563 | | HttpChunkInvalid, |
| 564 | | HttpHeadersOversize, |
| 565 | | }; |
| 566 | | |
| 567 | | fn contentLengthReader_read( |
| 588 | fn contentLengthRead( |
| 568 | 589 | ctx: ?*anyopaque, |
| 569 | 590 | bw: *std.io.BufferedWriter, |
| 570 | 591 | limit: std.io.Reader.Limit, |
| 571 | | ) std.io.Reader.Error!usize { |
| 592 | ) std.io.Reader.RwError!usize { |
| 572 | 593 | const request: *Request = @alignCast(@ptrCast(ctx)); |
| 573 | | _ = request; |
| 574 | | _ = bw; |
| 575 | | _ = limit; |
| 576 | | @panic("TODO"); |
| 594 | const remaining_content_length = &request.reader_state.remaining_content_length; |
| 595 | const remaining = remaining_content_length.*; |
| 596 | const server = request.server; |
| 597 | if (remaining == 0) { |
| 598 | server.state = .ready; |
| 599 | return error.EndOfStream; |
| 600 | } |
| 601 | const n = try server.in.read(bw, limit.min(.limited(remaining))); |
| 602 | const new_remaining = remaining - n; |
| 603 | remaining_content_length.* = new_remaining; |
| 604 | return n; |
| 577 | 605 | } |
| 578 | 606 | |
| 579 | | fn contentLengthReader_readVec(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize { |
| 580 | | const request: *Request = @alignCast(@ptrCast(ctx)); |
| 581 | | _ = request; |
| 582 | | _ = data; |
| 583 | | @panic("TODO"); |
| 607 | fn contentLengthReadVec(context: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize { |
| 608 | const request: *Request = @alignCast(@ptrCast(context)); |
| 609 | const remaining_content_length = &request.reader_state.remaining_content_length; |
| 610 | const server = request.server; |
| 611 | const remaining = remaining_content_length.*; |
| 612 | if (remaining == 0) { |
| 613 | server.state = .ready; |
| 614 | return error.EndOfStream; |
| 615 | } |
| 616 | const n = try server.in.readVecLimit(data, .limited(remaining)); |
| 617 | const new_remaining = remaining - n; |
| 618 | remaining_content_length.* = new_remaining; |
| 619 | return n; |
| 584 | 620 | } |
| 585 | 621 | |
| 586 | | fn contentLengthReader_discard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize { |
| 622 | fn contentLengthDiscard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize { |
| 587 | 623 | const request: *Request = @alignCast(@ptrCast(ctx)); |
| 588 | | _ = request; |
| 589 | | _ = limit; |
| 590 | | @panic("TODO"); |
| 624 | const remaining_content_length = &request.reader_state.remaining_content_length; |
| 625 | const server = request.server; |
| 626 | const remaining = remaining_content_length.*; |
| 627 | if (remaining == 0) { |
| 628 | server.state = .ready; |
| 629 | return error.EndOfStream; |
| 630 | } |
| 631 | const n = try server.in.discard(limit.min(.limited(remaining))); |
| 632 | const new_remaining = remaining - n; |
| 633 | remaining_content_length.* = new_remaining; |
| 634 | return n; |
| 591 | 635 | } |
| 592 | 636 | |
| 593 | | fn chunkedReader_read( |
| 637 | fn chunkedRead( |
| 594 | 638 | ctx: ?*anyopaque, |
| 595 | 639 | bw: *std.io.BufferedWriter, |
| 596 | 640 | limit: std.io.Reader.Limit, |
| 597 | | ) std.io.Reader.Error!usize { |
| 641 | ) std.io.Reader.RwError!usize { |
| 598 | 642 | const request: *Request = @alignCast(@ptrCast(ctx)); |
| 599 | | _ = request; |
| 600 | | _ = bw; |
| 601 | | _ = limit; |
| 602 | | @panic("TODO"); |
| 643 | const chunk_len_ptr = &request.reader_state.remaining_chunk_len; |
| 644 | const in = request.server.in; |
| 645 | len: switch (chunk_len_ptr.*) { |
| 646 | .head => { |
| 647 | var cp: http.ChunkParser = .init; |
| 648 | const i = cp.feed(in.bufferContents()); |
| 649 | switch (cp.state) { |
| 650 | .invalid => return request.failRead(error.HttpChunkInvalid), |
| 651 | .data => { |
| 652 | if (i > max_chunk_header_len) return request.failRead(error.HttpChunkInvalid); |
| 653 | in.toss(i); |
| 654 | }, |
| 655 | else => { |
| 656 | try in.fill(max_chunk_header_len); |
| 657 | const next_i = cp.feed(in.bufferContents()[i..]); |
| 658 | if (cp.state != .data) return request.failRead(error.HttpChunkInvalid); |
| 659 | const header_len = i + next_i; |
| 660 | if (header_len > max_chunk_header_len) return request.failRead(error.HttpChunkInvalid); |
| 661 | in.toss(header_len); |
| 662 | }, |
| 663 | } |
| 664 | if (cp.chunk_len == 0) return parseTrailers(request, 0); |
| 665 | const n = try in.read(bw, limit.min(.limited(cp.chunk_len))); |
| 666 | chunk_len_ptr.* = .init(cp.chunk_len + 2 - n); |
| 667 | return n; |
| 668 | }, |
| 669 | .n => { |
| 670 | if ((try in.peekByte()) != '\n') return request.failRead(error.HttpChunkInvalid); |
| 671 | in.toss(1); |
| 672 | continue :len .head; |
| 673 | }, |
| 674 | .rn => { |
| 675 | const rn = try in.peekArray(2); |
| 676 | if (rn[0] != '\r' or rn[1] != '\n') return request.failRead(error.HttpChunkInvalid); |
| 677 | in.toss(2); |
| 678 | continue :len .head; |
| 679 | }, |
| 680 | else => |remaining_chunk_len| { |
| 681 | const n = try in.read(bw, limit.min(.limited(@intFromEnum(remaining_chunk_len) - 2))); |
| 682 | chunk_len_ptr.* = .init(@intFromEnum(remaining_chunk_len) - n); |
| 683 | return n; |
| 684 | }, |
| 685 | .done => return error.EndOfStream, |
| 686 | } |
| 603 | 687 | } |
| 604 | 688 | |
| 605 | | fn chunkedReader_readVec(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize { |
| 689 | fn chunkedReadVec(ctx: ?*anyopaque, data: []const []u8) std.io.Reader.Error!usize { |
| 606 | 690 | const request: *Request = @alignCast(@ptrCast(ctx)); |
| 607 | | _ = request; |
| 608 | | _ = data; |
| 609 | | @panic("TODO"); |
| 691 | const chunk_len_ptr = &request.reader_state.remaining_chunk_len; |
| 692 | const in = request.server.in; |
| 693 | var already_requested_more = false; |
| 694 | var amt_read: usize = 0; |
| 695 | data: for (data) |d| { |
| 696 | len: switch (chunk_len_ptr.*) { |
| 697 | .head => { |
| 698 | var cp: http.ChunkParser = .init; |
| 699 | const available_buffer = in.bufferContents(); |
| 700 | const i = cp.feed(available_buffer); |
| 701 | if (cp.state == .invalid) return request.failRead(error.HttpChunkInvalid); |
| 702 | if (i == available_buffer.len) { |
| 703 | if (already_requested_more) { |
| 704 | chunk_len_ptr.* = .head; |
| 705 | return amt_read; |
| 706 | } |
| 707 | already_requested_more = true; |
| 708 | try in.fill(max_chunk_header_len); |
| 709 | const next_i = cp.feed(in.bufferContents()[i..]); |
| 710 | if (cp.state != .data) return request.failRead(error.HttpChunkInvalid); |
| 711 | const header_len = i + next_i; |
| 712 | if (header_len > max_chunk_header_len) return request.failRead(error.HttpChunkInvalid); |
| 713 | in.toss(header_len); |
| 714 | } else { |
| 715 | if (i > max_chunk_header_len) return request.failRead(error.HttpChunkInvalid); |
| 716 | in.toss(i); |
| 717 | } |
| 718 | if (cp.chunk_len == 0) return parseTrailers(request, amt_read); |
| 719 | continue :len .init(cp.chunk_len + 2); |
| 720 | }, |
| 721 | .n => { |
| 722 | if (in.bufferContents().len < 1) already_requested_more = true; |
| 723 | if ((try in.takeByte()) != '\n') return request.failRead(error.HttpChunkInvalid); |
| 724 | continue :len .head; |
| 725 | }, |
| 726 | .rn => { |
| 727 | if (in.bufferContents().len < 2) already_requested_more = true; |
| 728 | const rn = try in.takeArray(2); |
| 729 | if (rn[0] != '\r' or rn[1] != '\n') return request.failRead(error.HttpChunkInvalid); |
| 730 | continue :len .head; |
| 731 | }, |
| 732 | else => |remaining_chunk_len| { |
| 733 | const available_buffer = in.bufferContents(); |
| 734 | const copy_len = @min(available_buffer.len, d.len, remaining_chunk_len.int() - 2); |
| 735 | @memcpy(d[0..copy_len], available_buffer[0..copy_len]); |
| 736 | amt_read += copy_len; |
| 737 | in.toss(copy_len); |
| 738 | const next_chunk_len: RemainingChunkLen = .init(remaining_chunk_len.int() - copy_len); |
| 739 | if (copy_len == d.len) { |
| 740 | chunk_len_ptr.* = next_chunk_len; |
| 741 | continue :data; |
| 742 | } |
| 743 | if (already_requested_more) { |
| 744 | chunk_len_ptr.* = next_chunk_len; |
| 745 | return amt_read; |
| 746 | } |
| 747 | already_requested_more = true; |
| 748 | try in.fill(3); |
| 749 | continue :len next_chunk_len; |
| 750 | }, |
| 751 | .done => return error.EndOfStream, |
| 752 | } |
| 753 | } |
| 754 | return amt_read; |
| 610 | 755 | } |
| 611 | 756 | |
| 612 | | fn chunkedReader_discard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize { |
| 757 | fn chunkedDiscard(ctx: ?*anyopaque, limit: std.io.Reader.Limit) std.io.Reader.Error!usize { |
| 613 | 758 | const request: *Request = @alignCast(@ptrCast(ctx)); |
| 614 | | _ = request; |
| 615 | | _ = limit; |
| 616 | | @panic("TODO"); |
| 617 | | } |
| 618 | | |
| 619 | | fn read_cl(context: *const anyopaque, buffer: []u8) ReadError!usize { |
| 620 | | const request: *Request = @alignCast(@ptrCast(context)); |
| 621 | | const s = request.server; |
| 622 | | |
| 623 | | const remaining_content_length = &request.reader_state.remaining_content_length; |
| 624 | | if (remaining_content_length.* == 0) { |
| 625 | | s.state = .ready; |
| 626 | | return 0; |
| 759 | const chunk_len_ptr = &request.reader_state.remaining_chunk_len; |
| 760 | const in = request.server.in; |
| 761 | len: switch (chunk_len_ptr.*) { |
| 762 | .head => { |
| 763 | var cp: http.ChunkParser = .init; |
| 764 | const i = cp.feed(in.bufferContents()); |
| 765 | switch (cp.state) { |
| 766 | .invalid => return request.failRead(error.HttpChunkInvalid), |
| 767 | .data => { |
| 768 | if (i > max_chunk_header_len) return request.failRead(error.HttpChunkInvalid); |
| 769 | in.toss(i); |
| 770 | }, |
| 771 | else => { |
| 772 | try in.fill(max_chunk_header_len); |
| 773 | const next_i = cp.feed(in.bufferContents()[i..]); |
| 774 | if (cp.state != .data) return request.failRead(error.HttpChunkInvalid); |
| 775 | const header_len = i + next_i; |
| 776 | if (header_len > max_chunk_header_len) return request.failRead(error.HttpChunkInvalid); |
| 777 | in.toss(header_len); |
| 778 | }, |
| 779 | } |
| 780 | if (cp.chunk_len == 0) return parseTrailers(request, 0); |
| 781 | const n = try in.discard(limit.min(.limited(cp.chunk_len))); |
| 782 | chunk_len_ptr.* = .init(cp.chunk_len + 2 - n); |
| 783 | return n; |
| 784 | }, |
| 785 | .n => { |
| 786 | if ((try in.peekByte()) != '\n') return request.failRead(error.HttpChunkInvalid); |
| 787 | in.toss(1); |
| 788 | continue :len .head; |
| 789 | }, |
| 790 | .rn => { |
| 791 | const rn = try in.peekArray(2); |
| 792 | if (rn[0] != '\r' or rn[1] != '\n') return request.failRead(error.HttpChunkInvalid); |
| 793 | in.toss(2); |
| 794 | continue :len .head; |
| 795 | }, |
| 796 | else => |remaining_chunk_len| { |
| 797 | const n = try in.discard(limit.min(.limited(remaining_chunk_len.int() - 2))); |
| 798 | chunk_len_ptr.* = .init(remaining_chunk_len.int() - n); |
| 799 | return n; |
| 800 | }, |
| 801 | .done => return error.EndOfStream, |
| 627 | 802 | } |
| 628 | | assert(s.state == .receiving_body); |
| 629 | | const available = try fill(s, request.head_end); |
| 630 | | const len = @min(remaining_content_length.*, available.len, buffer.len); |
| 631 | | @memcpy(buffer[0..len], available[0..len]); |
| 632 | | remaining_content_length.* -= len; |
| 633 | | s.next_request_start += len; |
| 634 | | if (remaining_content_length.* == 0) |
| 635 | | s.state = .ready; |
| 636 | | return len; |
| 637 | 803 | } |
| 638 | 804 | |
| 639 | | fn fill(s: *Server, head_end: usize) ReadError![]u8 { |
| 640 | | const available = s.read_buffer[s.next_request_start..s.read_buffer_len]; |
| 641 | | if (available.len > 0) return available; |
| 642 | | s.next_request_start = head_end; |
| 643 | | s.read_buffer_len = head_end + try s.connection.stream.read(s.read_buffer[head_end..]); |
| 644 | | return s.read_buffer[head_end..s.read_buffer_len]; |
| 645 | | } |
| 646 | | |
| 647 | | fn read_chunked(context: *const anyopaque, buffer: []u8) ReadError!usize { |
| 648 | | const request: *Request = @alignCast(@ptrCast(context)); |
| 649 | | const s = request.server; |
| 650 | | |
| 651 | | const cp = &request.reader_state.chunk_parser; |
| 652 | | const head_end = request.head_end; |
| 653 | | |
| 654 | | // Protect against returning 0 before the end of stream. |
| 655 | | var out_end: usize = 0; |
| 656 | | while (out_end == 0) { |
| 657 | | switch (cp.state) { |
| 658 | | .invalid => return 0, |
| 659 | | .data => { |
| 660 | | assert(s.state == .receiving_body); |
| 661 | | const available = try fill(s, head_end); |
| 662 | | const len = @min(cp.chunk_len, available.len, buffer.len); |
| 663 | | @memcpy(buffer[0..len], available[0..len]); |
| 664 | | cp.chunk_len -= len; |
| 665 | | if (cp.chunk_len == 0) |
| 666 | | cp.state = .data_suffix; |
| 667 | | out_end += len; |
| 668 | | s.next_request_start += len; |
| 669 | | continue; |
| 670 | | }, |
| 671 | | else => { |
| 672 | | assert(s.state == .receiving_body); |
| 673 | | const available = try fill(s, head_end); |
| 674 | | const n = cp.feed(available); |
| 675 | | switch (cp.state) { |
| 676 | | .invalid => return error.HttpChunkInvalid, |
| 677 | | .data => { |
| 678 | | if (cp.chunk_len == 0) { |
| 679 | | // The next bytes in the stream are trailers, |
| 680 | | // or \r\n to indicate end of chunked body. |
| 681 | | // |
| 682 | | // This function must append the trailers at |
| 683 | | // head_end so that headers and trailers are |
| 684 | | // together. |
| 685 | | // |
| 686 | | // Since returning 0 would indicate end of |
| 687 | | // stream, this function must read all the |
| 688 | | // trailers before returning. |
| 689 | | if (s.next_request_start > head_end) rebase(s, head_end); |
| 690 | | var hp: http.HeadParser = .{}; |
| 691 | | { |
| 692 | | const bytes = s.read_buffer[head_end..s.read_buffer_len]; |
| 693 | | const end = hp.feed(bytes); |
| 694 | | if (hp.state == .finished) { |
| 695 | | cp.state = .invalid; |
| 696 | | s.state = .ready; |
| 697 | | s.next_request_start = s.read_buffer_len - bytes.len + end; |
| 698 | | return out_end; |
| 699 | | } |
| 700 | | } |
| 701 | | while (true) { |
| 702 | | const buf = s.read_buffer[s.read_buffer_len..]; |
| 703 | | if (buf.len == 0) |
| 704 | | return error.HttpHeadersOversize; |
| 705 | | const read_n = try s.connection.stream.read(buf); |
| 706 | | s.read_buffer_len += read_n; |
| 707 | | const bytes = buf[0..read_n]; |
| 708 | | const end = hp.feed(bytes); |
| 709 | | if (hp.state == .finished) { |
| 710 | | cp.state = .invalid; |
| 711 | | s.state = .ready; |
| 712 | | s.next_request_start = s.read_buffer_len - bytes.len + end; |
| 713 | | return out_end; |
| 714 | | } |
| 715 | | } |
| 716 | | } |
| 717 | | const data = available[n..]; |
| 718 | | const len = @min(cp.chunk_len, data.len, buffer.len); |
| 719 | | @memcpy(buffer[0..len], data[0..len]); |
| 720 | | cp.chunk_len -= len; |
| 721 | | if (cp.chunk_len == 0) |
| 722 | | cp.state = .data_suffix; |
| 723 | | out_end += len; |
| 724 | | s.next_request_start += n + len; |
| 725 | | continue; |
| 726 | | }, |
| 727 | | else => continue, |
| 728 | | } |
| 729 | | }, |
| 805 | /// Called when next bytes in the stream are trailers, or "\r\n" to indicate |
| 806 | /// end of chunked body. |
| 807 | fn parseTrailers(request: *Request, amt_read: usize) std.io.Reader.Error!usize { |
| 808 | const in = request.server.in; |
| 809 | var hp: http.HeadParser = .{}; |
| 810 | var trailers_len: usize = 0; |
| 811 | while (true) { |
| 812 | if (trailers_len >= in.buffer.len) return request.failRead(error.HttpHeadersOversize); |
| 813 | try in.fill(trailers_len + 1); |
| 814 | trailers_len += hp.feed(in.bufferContents()[trailers_len..]); |
| 815 | if (hp.state == .finished) { |
| 816 | request.reader_state.remaining_chunk_len = .done; |
| 817 | request.server.state = .ready; |
| 818 | request.trailers_len = trailers_len; |
| 819 | return amt_read; |
| 730 | 820 | } |
| 731 | 821 | } |
| 732 | | return out_end; |
| 733 | 822 | } |
| 734 | 823 | |
| 735 | 824 | pub const ReaderError = error{ |
| ... | ... | @@ -752,7 +841,6 @@ pub const Request = struct { |
| 752 | 841 | const s = request.server; |
| 753 | 842 | assert(s.state == .received_head); |
| 754 | 843 | s.state = .receiving_body; |
| 755 | | s.next_request_start = request.head_end; |
| 756 | 844 | |
| 757 | 845 | if (request.head.expect) |expect| { |
| 758 | 846 | if (mem.eql(u8, expect, "100-continue")) { |
| ... | ... | @@ -765,13 +853,13 @@ pub const Request = struct { |
| 765 | 853 | |
| 766 | 854 | switch (request.head.transfer_encoding) { |
| 767 | 855 | .chunked => { |
| 768 | | request.reader_state = .{ .chunk_parser = http.ChunkParser.init }; |
| 856 | request.reader_state = .{ .remaining_chunk_len = .head }; |
| 769 | 857 | return .{ |
| 770 | 858 | .context = request, |
| 771 | 859 | .vtable = &.{ |
| 772 | | .read = &chunkedReader_read, |
| 773 | | .readVec = &chunkedReader_readVec, |
| 774 | | .discard = &chunkedReader_discard, |
| 860 | .read = &chunkedRead, |
| 861 | .readVec = &chunkedReadVec, |
| 862 | .discard = &chunkedDiscard, |
| 775 | 863 | }, |
| 776 | 864 | }; |
| 777 | 865 | }, |
| ... | ... | @@ -782,9 +870,9 @@ pub const Request = struct { |
| 782 | 870 | return .{ |
| 783 | 871 | .context = request, |
| 784 | 872 | .vtable = &.{ |
| 785 | | .read = &contentLengthReader_read, |
| 786 | | .readVec = &contentLengthReader_readVec, |
| 787 | | .discard = &contentLengthReader_discard, |
| 873 | .read = &contentLengthRead, |
| 874 | .readVec = &contentLengthReadVec, |
| 875 | .discard = &contentLengthDiscard, |
| 788 | 876 | }, |
| 789 | 877 | }; |
| 790 | 878 | }, |
| ... | ... | @@ -822,6 +910,11 @@ pub const Request = struct { |
| 822 | 910 | } |
| 823 | 911 | return false; |
| 824 | 912 | } |
| 913 | |
| 914 | fn failRead(r: *Request, err: ReadError) error{ReadFailed} { |
| 915 | r.read_err = err; |
| 916 | return error.ReadFailed; |
| 917 | } |
| 825 | 918 | }; |
| 826 | 919 | |
| 827 | 920 | pub const Response = struct { |
| ... | ... | @@ -1165,14 +1258,3 @@ pub const Response = struct { |
| 1165 | 1258 | }; |
| 1166 | 1259 | } |
| 1167 | 1260 | }; |
| 1168 | | |
| 1169 | | fn rebase(s: *Server, index: usize) void { |
| 1170 | | const leftover = s.read_buffer[s.next_request_start..s.read_buffer_len]; |
| 1171 | | const dest = s.read_buffer[index..][0..leftover.len]; |
| 1172 | | if (leftover.len <= s.next_request_start - index) { |
| 1173 | | @memcpy(dest, leftover); |
| 1174 | | } else { |
| 1175 | | mem.copyBackwards(u8, dest, leftover); |
| 1176 | | } |
| 1177 | | s.read_buffer_len = index + leftover.len; |
| 1178 | | } |