| author | |
| committer | |
| log | aecbfa3a1e9aa379368e6a9a999ca42fc4803f18 |
| tree | c23bdaf7457839f684d53a027ed789412fb4105e |
| parent | 08bdaf3bd650ec7682f1424c3644fc3b762ccf27 |
| signature |
4 files changed, 282 insertions(+), 388 deletions(-)
lib/std/http/Client.zig+131-31| ... | ... | @@ -32,7 +32,20 @@ pub const ConnectionPool = struct { |
| 32 | 32 | is_tls: bool, |
| 33 | 33 | }; |
| 34 | 34 | |
| 35 | const Queue = std.TailQueue(Connection); | |
| 35 | pub const StoredConnection = struct { | |
| 36 | buffered: BufferedConnection, | |
| 37 | host: []u8, | |
| 38 | port: u16, | |
| 39 | ||
| 40 | closing: bool = false, | |
| 41 | ||
| 42 | pub fn deinit(self: *StoredConnection, client: *Client) void { | |
| 43 | self.buffered.close(client); | |
| 44 | client.allocator.free(self.host); | |
| 45 | } | |
| 46 | }; | |
| 47 | ||
| 48 | const Queue = std.TailQueue(StoredConnection); | |
| 36 | 49 | pub const Node = Queue.Node; |
| 37 | 50 | |
| 38 | 51 | mutex: std.Thread.Mutex = .{}, |
| ... | ... | @@ -49,7 +62,7 @@ pub const ConnectionPool = struct { |
| 49 | 62 | |
| 50 | 63 | var next = pool.free.last; |
| 51 | 64 | while (next) |node| : (next = node.prev) { |
| 52 | if ((node.data.protocol == .tls) != criteria.is_tls) continue; | |
| 65 | if ((node.data.buffered.conn.protocol == .tls) != criteria.is_tls) continue; | |
| 53 | 66 | if (node.data.port != criteria.port) continue; |
| 54 | 67 | if (mem.eql(u8, node.data.host, criteria.host)) continue; |
| 55 | 68 | |
| ... | ... | @@ -85,7 +98,7 @@ pub const ConnectionPool = struct { |
| 85 | 98 | pool.used.remove(node); |
| 86 | 99 | |
| 87 | 100 | if (node.data.closing) { |
| 88 | node.data.close(client); | |
| 101 | node.data.deinit(client); | |
| 89 | 102 | |
| 90 | 103 | return client.allocator.destroy(node); |
| 91 | 104 | } |
| ... | ... | @@ -93,7 +106,7 @@ pub const ConnectionPool = struct { |
| 93 | 106 | if (pool.free_len + 1 >= pool.free_size) { |
| 94 | 107 | const popped = pool.free.popFirst() orelse unreachable; |
| 95 | 108 | |
| 96 | popped.data.close(client); | |
| 109 | popped.data.deinit(client); | |
| 97 | 110 | |
| 98 | 111 | return client.allocator.destroy(popped); |
| 99 | 112 | } |
| ... | ... | @@ -118,7 +131,7 @@ pub const ConnectionPool = struct { |
| 118 | 131 | defer client.allocator.destroy(node); |
| 119 | 132 | next = node.next; |
| 120 | 133 | |
| 121 | node.data.close(client); | |
| 134 | node.data.deinit(client); | |
| 122 | 135 | } |
| 123 | 136 | |
| 124 | 137 | next = pool.used.first; |
| ... | ... | @@ -126,7 +139,7 @@ pub const ConnectionPool = struct { |
| 126 | 139 | defer client.allocator.destroy(node); |
| 127 | 140 | next = node.next; |
| 128 | 141 | |
| 129 | node.data.close(client); | |
| 142 | node.data.deinit(client); | |
| 130 | 143 | } |
| 131 | 144 | |
| 132 | 145 | pool.* = undefined; |
| ... | ... | @@ -140,13 +153,8 @@ pub const ZstdDecompressor = std.compress.zstd.DecompressStream(Request.Transfer |
| 140 | 153 | pub const Connection = struct { |
| 141 | 154 | stream: net.Stream, |
| 142 | 155 | /// undefined unless protocol is tls. |
| 143 | tls_client: *std.crypto.tls.Client, // TODO: allocate this, it's currently 16 KB. | |
| 156 | tls_client: *std.crypto.tls.Client, | |
| 144 | 157 | protocol: Protocol, |
| 145 | host: []u8, | |
| 146 | port: u16, | |
| 147 | ||
| 148 | // This connection has been part of a non keepalive request and cannot be added to the pool. | |
| 149 | closing: bool = false, | |
| 150 | 158 | |
| 151 | 159 | pub const Protocol = enum { plain, tls }; |
| 152 | 160 | |
| ... | ... | @@ -211,8 +219,89 @@ pub const Connection = struct { |
| 211 | 219 | } |
| 212 | 220 | |
| 213 | 221 | conn.stream.close(); |
| 222 | } | |
| 223 | }; | |
| 224 | ||
| 225 | pub const BufferedConnection = struct { | |
| 226 | pub const buffer_size = 0x2000; | |
| 227 | ||
| 228 | conn: Connection, | |
| 229 | buf: [buffer_size]u8 = undefined, | |
| 230 | start: u16 = 0, | |
| 231 | end: u16 = 0, | |
| 232 | ||
| 233 | pub fn fill(bconn: *BufferedConnection) ReadError!void { | |
| 234 | if (bconn.end != bconn.start) return; | |
| 235 | ||
| 236 | const nread = try bconn.conn.read(bconn.buf[0..]); | |
| 237 | if (nread == 0) return error.EndOfStream; | |
| 238 | bconn.start = 0; | |
| 239 | bconn.end = @truncate(u16, nread); | |
| 240 | } | |
| 241 | ||
| 242 | pub fn peek(bconn: *BufferedConnection) []const u8 { | |
| 243 | return bconn.buf[bconn.start..bconn.end]; | |
| 244 | } | |
| 245 | ||
| 246 | pub fn clear(bconn: *BufferedConnection, num: u16) void { | |
| 247 | bconn.start += num; | |
| 248 | } | |
| 249 | ||
| 250 | pub fn readAtLeast(bconn: *BufferedConnection, buffer: []u8, len: usize) ReadError!usize { | |
| 251 | var out_index: u16 = 0; | |
| 252 | while (out_index < len) { | |
| 253 | const available = bconn.end - bconn.start; | |
| 254 | const left = buffer.len - out_index; | |
| 255 | ||
| 256 | if (available > 0) { | |
| 257 | const can_read = @truncate(u16, @min(available, left)); | |
| 258 | ||
| 259 | std.mem.copy(u8, buffer[out_index..], bconn.buf[bconn.start..][0..can_read]); | |
| 260 | out_index += can_read; | |
| 261 | bconn.start += can_read; | |
| 262 | ||
| 263 | continue; | |
| 264 | } | |
| 214 | 265 | |
| 215 | client.allocator.free(conn.host); | |
| 266 | if (left > bconn.buf.len) { | |
| 267 | // skip the buffer if the output is large enough | |
| 268 | return bconn.conn.read(buffer[out_index..]); | |
| 269 | } | |
| 270 | ||
| 271 | try bconn.fill(); | |
| 272 | } | |
| 273 | ||
| 274 | return out_index; | |
| 275 | } | |
| 276 | ||
| 277 | pub fn read(bconn: *BufferedConnection, buffer: []u8) ReadError!usize { | |
| 278 | return bconn.readAtLeast(buffer, 1); | |
| 279 | } | |
| 280 | ||
| 281 | pub const ReadError = Connection.ReadError || error{EndOfStream}; | |
| 282 | pub const Reader = std.io.Reader(*BufferedConnection, ReadError, read); | |
| 283 | ||
| 284 | pub fn reader(bconn: *BufferedConnection) Reader { | |
| 285 | return Reader{ .context = bconn }; | |
| 286 | } | |
| 287 | ||
| 288 | pub fn writeAll(bconn: *BufferedConnection, buffer: []const u8) WriteError!void { | |
| 289 | return bconn.conn.writeAll(buffer); | |
| 290 | } | |
| 291 | ||
| 292 | pub fn write(bconn: *BufferedConnection, buffer: []const u8) WriteError!usize { | |
| 293 | return bconn.conn.write(buffer); | |
| 294 | } | |
| 295 | ||
| 296 | pub const WriteError = Connection.WriteError; | |
| 297 | pub const Writer = std.io.Writer(*BufferedConnection, WriteError, write); | |
| 298 | ||
| 299 | pub fn writer(bconn: *BufferedConnection) Writer { | |
| 300 | return Writer{ .context = bconn }; | |
| 301 | } | |
| 302 | ||
| 303 | pub fn close(bconn: *BufferedConnection, client: *const Client) void { | |
| 304 | bconn.conn.close(client); | |
| 216 | 305 | } |
| 217 | 306 | }; |
| 218 | 307 | |
| ... | ... | @@ -417,7 +506,7 @@ pub const Request = struct { |
| 417 | 506 | req.* = undefined; |
| 418 | 507 | } |
| 419 | 508 | |
| 420 | pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError; | |
| 509 | pub const TransferReadError = BufferedConnection.ReadError || proto.HeadersParser.ReadError; | |
| 421 | 510 | |
| 422 | 511 | pub const TransferReader = std.io.Reader(*Request, TransferReadError, transferRead); |
| 423 | 512 | |
| ... | ... | @@ -430,7 +519,7 @@ pub const Request = struct { |
| 430 | 519 | |
| 431 | 520 | var index: usize = 0; |
| 432 | 521 | while (index == 0) { |
| 433 | const amt = try req.response.parser.read(req.connection.data.reader(), buf[index..], req.response.skip); | |
| 522 | const amt = try req.response.parser.read(&req.connection.data.buffered, buf[index..], req.response.skip); | |
| 434 | 523 | if (amt == 0 and req.response.parser.isComplete()) break; |
| 435 | 524 | index += amt; |
| 436 | 525 | } |
| ... | ... | @@ -438,10 +527,17 @@ pub const Request = struct { |
| 438 | 527 | return index; |
| 439 | 528 | } |
| 440 | 529 | |
| 441 | pub const WaitForCompleteHeadError = Connection.ReadError || proto.HeadersParser.WaitForCompleteHeadError || Response.Headers.ParseError || error{ BadHeader, InvalidCompression, StreamTooLong, InvalidWindowSize } || error{CompressionNotSupported}; | |
| 530 | pub const WaitForCompleteHeadError = BufferedConnection.ReadError || proto.HeadersParser.CheckCompleteHeadError || Response.Headers.ParseError || error{ BadHeader, InvalidCompression, StreamTooLong, InvalidWindowSize } || error{CompressionNotSupported}; | |
| 442 | 531 | |
| 443 | 532 | pub fn waitForCompleteHead(req: *Request) !void { |
| 444 | try req.response.parser.waitForCompleteHead(req.connection.data.reader(), req.client.allocator); | |
| 533 | while (true) { | |
| 534 | try req.connection.data.buffered.fill(); | |
| 535 | ||
| 536 | const nchecked = try req.response.parser.checkCompleteHead(req.client.allocator, req.connection.data.buffered.peek()); | |
| 537 | req.connection.data.buffered.clear(@intCast(u16, nchecked)); | |
| 538 | ||
| 539 | if (req.response.parser.state.isContent()) break; | |
| 540 | } | |
| 445 | 541 | |
| 446 | 542 | req.response.headers = try Response.Headers.parse(req.response.parser.header_bytes.items); |
| 447 | 543 | |
| ... | ... | @@ -550,7 +646,7 @@ pub const Request = struct { |
| 550 | 646 | return index; |
| 551 | 647 | } |
| 552 | 648 | |
| 553 | pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong }; | |
| 649 | pub const WriteError = BufferedConnection.WriteError || error{ NotWriteable, MessageTooLong }; | |
| 554 | 650 | |
| 555 | 651 | pub const Writer = std.io.Writer(*Request, WriteError, write); |
| 556 | 652 | |
| ... | ... | @@ -562,16 +658,16 @@ pub const Request = struct { |
| 562 | 658 | pub fn write(req: *Request, bytes: []const u8) WriteError!usize { |
| 563 | 659 | switch (req.headers.transfer_encoding) { |
| 564 | 660 | .chunked => { |
| 565 | try req.connection.data.writer().print("{x}\r\n", .{bytes.len}); | |
| 566 | try req.connection.data.writeAll(bytes); | |
| 567 | try req.connection.data.writeAll("\r\n"); | |
| 661 | try req.connection.data.conn.writer().print("{x}\r\n", .{bytes.len}); | |
| 662 | try req.connection.data.conn.writeAll(bytes); | |
| 663 | try req.connection.data.conn.writeAll("\r\n"); | |
| 568 | 664 | |
| 569 | 665 | return bytes.len; |
| 570 | 666 | }, |
| 571 | 667 | .content_length => |*len| { |
| 572 | 668 | if (len.* < bytes.len) return error.MessageTooLong; |
| 573 | 669 | |
| 574 | const amt = try req.connection.data.write(bytes); | |
| 670 | const amt = try req.connection.data.conn.write(bytes); | |
| 575 | 671 | len.* -= amt; |
| 576 | 672 | return amt; |
| 577 | 673 | }, |
| ... | ... | @@ -582,7 +678,7 @@ pub const Request = struct { |
| 582 | 678 | /// Finish the body of a request. This notifies the server that you have no more data to send. |
| 583 | 679 | pub fn finish(req: *Request) !void { |
| 584 | 680 | switch (req.headers.transfer_encoding) { |
| 585 | .chunked => try req.connection.data.writeAll("0\r\n"), | |
| 681 | .chunked => try req.connection.data.conn.writeAll("0\r\n"), | |
| 586 | 682 | .content_length => |len| if (len != 0) return error.MessageNotCompleted, |
| 587 | 683 | .none => {}, |
| 588 | 684 | } |
| ... | ... | @@ -610,10 +706,14 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio |
| 610 | 706 | errdefer client.allocator.destroy(conn); |
| 611 | 707 | conn.* = .{ .data = undefined }; |
| 612 | 708 | |
| 709 | const stream = try net.tcpConnectToHost(client.allocator, host, port); | |
| 710 | ||
| 613 | 711 | conn.data = .{ |
| 614 | .stream = try net.tcpConnectToHost(client.allocator, host, port), | |
| 615 | .tls_client = undefined, | |
| 616 | .protocol = protocol, | |
| 712 | .buffered = .{ .conn = .{ | |
| 713 | .stream = stream, | |
| 714 | .tls_client = undefined, | |
| 715 | .protocol = protocol, | |
| 716 | } }, | |
| 617 | 717 | .host = try client.allocator.dupe(u8, host), |
| 618 | 718 | .port = port, |
| 619 | 719 | }; |
| ... | ... | @@ -621,11 +721,11 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio |
| 621 | 721 | switch (protocol) { |
| 622 | 722 | .plain => {}, |
| 623 | 723 | .tls => { |
| 624 | conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client); | |
| 625 | conn.data.tls_client.* = try std.crypto.tls.Client.init(conn.data.stream, client.ca_bundle, host); | |
| 724 | conn.data.buffered.conn.tls_client = try client.allocator.create(std.crypto.tls.Client); | |
| 725 | conn.data.buffered.conn.tls_client.* = try std.crypto.tls.Client.init(stream, client.ca_bundle, host); | |
| 626 | 726 | // This is appropriate for HTTPS because the HTTP headers contain |
| 627 | 727 | // the content length which is used to detect truncation attacks. |
| 628 | conn.data.tls_client.allow_truncation_attacks = true; | |
| 728 | conn.data.buffered.conn.tls_client.allow_truncation_attacks = true; | |
| 629 | 729 | }, |
| 630 | 730 | } |
| 631 | 731 | |
| ... | ... | @@ -634,7 +734,7 @@ pub fn connect(client: *Client, host: []const u8, port: u16, protocol: Connectio |
| 634 | 734 | return conn; |
| 635 | 735 | } |
| 636 | 736 | |
| 637 | pub const RequestError = ConnectError || Connection.WriteError || error{ | |
| 737 | pub const RequestError = ConnectError || BufferedConnection.WriteError || error{ | |
| 638 | 738 | UnsupportedUrlScheme, |
| 639 | 739 | UriMissingHost, |
| 640 | 740 | |
| ... | ... | @@ -708,7 +808,7 @@ pub fn request(client: *Client, uri: Uri, headers: Request.Headers, options: Opt |
| 708 | 808 | req.arena = std.heap.ArenaAllocator.init(client.allocator); |
| 709 | 809 | |
| 710 | 810 | { |
| 711 | var buffered = std.io.bufferedWriter(req.connection.data.writer()); | |
| 811 | var buffered = std.io.bufferedWriter(req.connection.data.buffered.writer()); | |
| 712 | 812 | const writer = buffered.writer(); |
| 713 | 813 | |
| 714 | 814 | const escaped_path = try Uri.escapePath(client.allocator, uri.path); |
lib/std/http/Client/Response.zig deleted-276| ... | ... | @@ -1,276 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const http = std.http; | |
| 3 | const mem = std.mem; | |
| 4 | const testing = std.testing; | |
| 5 | const assert = std.debug.assert; | |
| 6 | ||
| 7 | const protocol = @import("../protocol.zig"); | |
| 8 | const Client = @import("../Client.zig"); | |
| 9 | const Response = @This(); | |
| 10 | ||
| 11 | headers: Headers, | |
| 12 | state: State, | |
| 13 | header_bytes_owned: bool, | |
| 14 | /// This could either be a fixed buffer provided by the API user or it | |
| 15 | /// could be our own array list. | |
| 16 | header_bytes: std.ArrayListUnmanaged(u8), | |
| 17 | max_header_bytes: usize, | |
| 18 | next_chunk_length: u64, | |
| 19 | done: bool = false, | |
| 20 | ||
| 21 | compression: union(enum) { | |
| 22 | deflate: Client.DeflateDecompressor, | |
| 23 | gzip: Client.GzipDecompressor, | |
| 24 | zstd: Client.ZstdDecompressor, | |
| 25 | none: void, | |
| 26 | } = .none, | |
| 27 | ||
| 28 | pub const Headers = struct { | |
| 29 | status: http.Status, | |
| 30 | version: http.Version, | |
| 31 | location: ?[]const u8 = null, | |
| 32 | content_length: ?u64 = null, | |
| 33 | transfer_encoding: ?http.TransferEncoding = null, | |
| 34 | transfer_compression: ?http.ContentEncoding = null, | |
| 35 | connection: http.Connection = .close, | |
| 36 | upgrade: ?[]const u8 = null, | |
| 37 | ||
| 38 | number_of_headers: usize = 0, | |
| 39 | ||
| 40 | pub fn parse(bytes: []const u8) !Headers { | |
| 41 | var it = mem.split(u8, bytes[0 .. bytes.len - 4], "\r\n"); | |
| 42 | ||
| 43 | const first_line = it.first(); | |
| 44 | if (first_line.len < 12) | |
| 45 | return error.ShortHttpStatusLine; | |
| 46 | ||
| 47 | const version: http.Version = switch (int64(first_line[0..8])) { | |
| 48 | int64("HTTP/1.0") => .@"HTTP/1.0", | |
| 49 | int64("HTTP/1.1") => .@"HTTP/1.1", | |
| 50 | else => return error.BadHttpVersion, | |
| 51 | }; | |
| 52 | if (first_line[8] != ' ') return error.HttpHeadersInvalid; | |
| 53 | const status = @intToEnum(http.Status, parseInt3(first_line[9..12].*)); | |
| 54 | ||
| 55 | var headers: Headers = .{ | |
| 56 | .version = version, | |
| 57 | .status = status, | |
| 58 | }; | |
| 59 | ||
| 60 | while (it.next()) |line| { | |
| 61 | headers.number_of_headers += 1; | |
| 62 | ||
| 63 | if (line.len == 0) return error.HttpHeadersInvalid; | |
| 64 | switch (line[0]) { | |
| 65 | ' ', '\t' => return error.HttpHeaderContinuationsUnsupported, | |
| 66 | else => {}, | |
| 67 | } | |
| 68 | var line_it = mem.split(u8, line, ": "); | |
| 69 | const header_name = line_it.first(); | |
| 70 | const header_value = line_it.rest(); | |
| 71 | if (std.ascii.eqlIgnoreCase(header_name, "location")) { | |
| 72 | if (headers.location != null) return error.HttpHeadersInvalid; | |
| 73 | headers.location = header_value; | |
| 74 | } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) { | |
| 75 | if (headers.content_length != null) return error.HttpHeadersInvalid; | |
| 76 | headers.content_length = try std.fmt.parseInt(u64, header_value, 10); | |
| 77 | } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) { | |
| 78 | // Transfer-Encoding: second, first | |
| 79 | // Transfer-Encoding: deflate, chunked | |
| 80 | var iter = std.mem.splitBackwards(u8, header_value, ","); | |
| 81 | ||
| 82 | if (iter.next()) |first| { | |
| 83 | const trimmed = std.mem.trim(u8, first, " "); | |
| 84 | ||
| 85 | if (std.meta.stringToEnum(http.TransferEncoding, trimmed)) |te| { | |
| 86 | if (headers.transfer_encoding != null) return error.HttpHeadersInvalid; | |
| 87 | headers.transfer_encoding = te; | |
| 88 | } else if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| { | |
| 89 | if (headers.transfer_compression != null) return error.HttpHeadersInvalid; | |
| 90 | headers.transfer_compression = ce; | |
| 91 | } else { | |
| 92 | return error.HttpTransferEncodingUnsupported; | |
| 93 | } | |
| 94 | } | |
| 95 | ||
| 96 | if (iter.next()) |second| { | |
| 97 | if (headers.transfer_compression != null) return error.HttpTransferEncodingUnsupported; | |
| 98 | ||
| 99 | const trimmed = std.mem.trim(u8, second, " "); | |
| 100 | ||
| 101 | if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| { | |
| 102 | headers.transfer_compression = ce; | |
| 103 | } else { | |
| 104 | return error.HttpTransferEncodingUnsupported; | |
| 105 | } | |
| 106 | } | |
| 107 | ||
| 108 | if (iter.next()) |_| return error.HttpTransferEncodingUnsupported; | |
| 109 | } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) { | |
| 110 | if (headers.transfer_compression != null) return error.HttpHeadersInvalid; | |
| 111 | ||
| 112 | const trimmed = std.mem.trim(u8, header_value, " "); | |
| 113 | ||
| 114 | if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| { | |
| 115 | headers.transfer_compression = ce; | |
| 116 | } else { | |
| 117 | return error.HttpTransferEncodingUnsupported; | |
| 118 | } | |
| 119 | } else if (std.ascii.eqlIgnoreCase(header_name, "connection")) { | |
| 120 | if (std.ascii.eqlIgnoreCase(header_value, "keep-alive")) { | |
| 121 | headers.connection = .keep_alive; | |
| 122 | } else if (std.ascii.eqlIgnoreCase(header_value, "close")) { | |
| 123 | headers.connection = .close; | |
| 124 | } else { | |
| 125 | return error.HttpConnectionHeaderUnsupported; | |
| 126 | } | |
| 127 | } else if (std.ascii.eqlIgnoreCase(header_name, "upgrade")) { | |
| 128 | headers.upgrade = header_value; | |
| 129 | } | |
| 130 | } | |
| 131 | ||
| 132 | return headers; | |
| 133 | } | |
| 134 | ||
| 135 | test "parse headers" { | |
| 136 | const example = | |
| 137 | "HTTP/1.1 301 Moved Permanently\r\n" ++ | |
| 138 | "Location: https://www.example.com/\r\n" ++ | |
| 139 | "Content-Type: text/html; charset=UTF-8\r\n" ++ | |
| 140 | "Content-Length: 220\r\n\r\n"; | |
| 141 | const parsed = try Headers.parse(example); | |
| 142 | try testing.expectEqual(http.Version.@"HTTP/1.1", parsed.version); | |
| 143 | try testing.expectEqual(http.Status.moved_permanently, parsed.status); | |
| 144 | try testing.expectEqualStrings("https://www.example.com/", parsed.location orelse | |
| 145 | return error.TestFailed); | |
| 146 | try testing.expectEqual(@as(?u64, 220), parsed.content_length); | |
| 147 | } | |
| 148 | ||
| 149 | test "header continuation" { | |
| 150 | const example = | |
| 151 | "HTTP/1.0 200 OK\r\n" ++ | |
| 152 | "Content-Type: text/html;\r\n charset=UTF-8\r\n" ++ | |
| 153 | "Content-Length: 220\r\n\r\n"; | |
| 154 | try testing.expectError( | |
| 155 | error.HttpHeaderContinuationsUnsupported, | |
| 156 | Headers.parse(example), | |
| 157 | ); | |
| 158 | } | |
| 159 | ||
| 160 | test "extra content length" { | |
| 161 | const example = | |
| 162 | "HTTP/1.0 200 OK\r\n" ++ | |
| 163 | "Content-Length: 220\r\n" ++ | |
| 164 | "Content-Type: text/html; charset=UTF-8\r\n" ++ | |
| 165 | "content-length: 220\r\n\r\n"; | |
| 166 | try testing.expectError( | |
| 167 | error.HttpHeadersInvalid, | |
| 168 | Headers.parse(example), | |
| 169 | ); | |
| 170 | } | |
| 171 | }; | |
| 172 | ||
| 173 | inline fn int64(array: *const [8]u8) u64 { | |
| 174 | return @bitCast(u64, array.*); | |
| 175 | } | |
| 176 | ||
| 177 | pub const State = enum { | |
| 178 | /// Begin header parsing states. | |
| 179 | invalid, | |
| 180 | start, | |
| 181 | seen_r, | |
| 182 | seen_rn, | |
| 183 | seen_rnr, | |
| 184 | finished, | |
| 185 | /// Begin transfer-encoding: chunked parsing states. | |
| 186 | chunk_size_prefix_r, | |
| 187 | chunk_size_prefix_n, | |
| 188 | chunk_size, | |
| 189 | chunk_r, | |
| 190 | chunk_data, | |
| 191 | ||
| 192 | pub fn isContent(self: State) bool { | |
| 193 | return switch (self) { | |
| 194 | .invalid, .start, .seen_r, .seen_rn, .seen_rnr => false, | |
| 195 | .finished, .chunk_size_prefix_r, .chunk_size_prefix_n, .chunk_size, .chunk_r, .chunk_data => true, | |
| 196 | }; | |
| 197 | } | |
| 198 | }; | |
| 199 | ||
| 200 | pub fn initDynamic(max: usize) Response { | |
| 201 | return .{ | |
| 202 | .state = .start, | |
| 203 | .headers = undefined, | |
| 204 | .header_bytes = .{}, | |
| 205 | .max_header_bytes = max, | |
| 206 | .header_bytes_owned = true, | |
| 207 | .next_chunk_length = undefined, | |
| 208 | }; | |
| 209 | } | |
| 210 | ||
| 211 | pub fn initStatic(buf: []u8) Response { | |
| 212 | return .{ | |
| 213 | .state = .start, | |
| 214 | .headers = undefined, | |
| 215 | .header_bytes = .{ .items = buf[0..0], .capacity = buf.len }, | |
| 216 | .max_header_bytes = buf.len, | |
| 217 | .header_bytes_owned = false, | |
| 218 | .next_chunk_length = undefined, | |
| 219 | }; | |
| 220 | } | |
| 221 | ||
| 222 | fn parseInt3(nnn: @Vector(3, u8)) u10 { | |
| 223 | const zero: @Vector(3, u8) = .{ '0', '0', '0' }; | |
| 224 | const mmm: @Vector(3, u10) = .{ 100, 10, 1 }; | |
| 225 | return @reduce(.Add, @as(@Vector(3, u10), nnn -% zero) *% mmm); | |
| 226 | } | |
| 227 | ||
| 228 | test parseInt3 { | |
| 229 | const expectEqual = std.testing.expectEqual; | |
| 230 | try expectEqual(@as(u10, 0), parseInt3("000".*)); | |
| 231 | try expectEqual(@as(u10, 418), parseInt3("418".*)); | |
| 232 | try expectEqual(@as(u10, 999), parseInt3("999".*)); | |
| 233 | } | |
| 234 | ||
| 235 | test "find headers end basic" { | |
| 236 | var buffer: [1]u8 = undefined; | |
| 237 | var r = Response.initStatic(&buffer); | |
| 238 | try testing.expectEqual(@as(usize, 10), r.findHeadersEnd("HTTP/1.1 4")); | |
| 239 | try testing.expectEqual(@as(usize, 2), r.findHeadersEnd("18")); | |
| 240 | try testing.expectEqual(@as(usize, 8), r.findHeadersEnd(" lol\r\n\r\nblah blah")); | |
| 241 | } | |
| 242 | ||
| 243 | test "find headers end vectorized" { | |
| 244 | var buffer: [1]u8 = undefined; | |
| 245 | var r = Response.initStatic(&buffer); | |
| 246 | const example = | |
| 247 | "HTTP/1.1 301 Moved Permanently\r\n" ++ | |
| 248 | "Location: https://www.example.com/\r\n" ++ | |
| 249 | "Content-Type: text/html; charset=UTF-8\r\n" ++ | |
| 250 | "Content-Length: 220\r\n" ++ | |
| 251 | "\r\ncontent"; | |
| 252 | try testing.expectEqual(@as(usize, 131), r.findHeadersEnd(example)); | |
| 253 | } | |
| 254 | ||
| 255 | test "find headers end bug" { | |
| 256 | var buffer: [1]u8 = undefined; | |
| 257 | var r = Response.initStatic(&buffer); | |
| 258 | const trail = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; | |
| 259 | const example = | |
| 260 | "HTTP/1.1 200 OK\r\n" ++ | |
| 261 | "Access-Control-Allow-Origin: https://render.githubusercontent.com\r\n" ++ | |
| 262 | "content-disposition: attachment; filename=zig-0.10.0.tar.gz\r\n" ++ | |
| 263 | "Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox\r\n" ++ | |
| 264 | "Content-Type: application/x-gzip\r\n" ++ | |
| 265 | "ETag: \"bfae0af6b01c7c0d89eb667cb5f0e65265968aeebda2689177e6b26acd3155ca\"\r\n" ++ | |
| 266 | "Strict-Transport-Security: max-age=31536000\r\n" ++ | |
| 267 | "Vary: Authorization,Accept-Encoding,Origin\r\n" ++ | |
| 268 | "X-Content-Type-Options: nosniff\r\n" ++ | |
| 269 | "X-Frame-Options: deny\r\n" ++ | |
| 270 | "X-XSS-Protection: 1; mode=block\r\n" ++ | |
| 271 | "Date: Fri, 06 Jan 2023 22:26:22 GMT\r\n" ++ | |
| 272 | "Transfer-Encoding: chunked\r\n" ++ | |
| 273 | "X-GitHub-Request-Id: 89C6:17E9:A7C9E:124B51:63B8A00E\r\n" ++ | |
| 274 | "connection: close\r\n\r\n" ++ trail; | |
| 275 | try testing.expectEqual(@as(usize, example.len - trail.len), r.findHeadersEnd(example)); | |
| 276 | } |
lib/std/http/Server.zig+102-12| ... | ... | @@ -74,6 +74,89 @@ pub const Connection = struct { |
| 74 | 74 | } |
| 75 | 75 | }; |
| 76 | 76 | |
| 77 | pub const BufferedConnection = struct { | |
| 78 | pub const buffer_size = 0x2000; | |
| 79 | ||
| 80 | conn: Connection, | |
| 81 | buf: [buffer_size]u8 = undefined, | |
| 82 | start: u16 = 0, | |
| 83 | end: u16 = 0, | |
| 84 | ||
| 85 | pub fn fill(bconn: *BufferedConnection) ReadError!void { | |
| 86 | if (bconn.end != bconn.start) return; | |
| 87 | ||
| 88 | const nread = try bconn.conn.read(bconn.buf[0..]); | |
| 89 | if (nread == 0) return error.EndOfStream; | |
| 90 | bconn.start = 0; | |
| 91 | bconn.end = @truncate(u16, nread); | |
| 92 | } | |
| 93 | ||
| 94 | pub fn peek(bconn: *BufferedConnection) []const u8 { | |
| 95 | return bconn.buf[bconn.start..bconn.end]; | |
| 96 | } | |
| 97 | ||
| 98 | pub fn clear(bconn: *BufferedConnection, num: u16) void { | |
| 99 | bconn.start += num; | |
| 100 | } | |
| 101 | ||
| 102 | pub fn readAtLeast(bconn: *BufferedConnection, buffer: []u8, len: usize) ReadError!usize { | |
| 103 | var out_index: u16 = 0; | |
| 104 | while (out_index < len) { | |
| 105 | const available = bconn.end - bconn.start; | |
| 106 | const left = buffer.len - out_index; | |
| 107 | ||
| 108 | if (available > 0) { | |
| 109 | const can_read = @truncate(u16, @min(available, left)); | |
| 110 | ||
| 111 | std.mem.copy(u8, buffer[out_index..], bconn.buf[bconn.start..][0..can_read]); | |
| 112 | out_index += can_read; | |
| 113 | bconn.start += can_read; | |
| 114 | ||
| 115 | continue; | |
| 116 | } | |
| 117 | ||
| 118 | if (left > bconn.buf.len) { | |
| 119 | // skip the buffer if the output is large enough | |
| 120 | return bconn.conn.read(buffer[out_index..]); | |
| 121 | } | |
| 122 | ||
| 123 | try bconn.fill(); | |
| 124 | } | |
| 125 | ||
| 126 | return out_index; | |
| 127 | } | |
| 128 | ||
| 129 | pub fn read(bconn: *BufferedConnection, buffer: []u8) ReadError!usize { | |
| 130 | return bconn.readAtLeast(buffer, 1); | |
| 131 | } | |
| 132 | ||
| 133 | pub const ReadError = Connection.ReadError || error{EndOfStream}; | |
| 134 | pub const Reader = std.io.Reader(*BufferedConnection, ReadError, read); | |
| 135 | ||
| 136 | pub fn reader(bconn: *BufferedConnection) Reader { | |
| 137 | return Reader{ .context = bconn }; | |
| 138 | } | |
| 139 | ||
| 140 | pub fn writeAll(bconn: *BufferedConnection, buffer: []const u8) WriteError!void { | |
| 141 | return bconn.conn.writeAll(buffer); | |
| 142 | } | |
| 143 | ||
| 144 | pub fn write(bconn: *BufferedConnection, buffer: []const u8) WriteError!usize { | |
| 145 | return bconn.conn.write(buffer); | |
| 146 | } | |
| 147 | ||
| 148 | pub const WriteError = Connection.WriteError; | |
| 149 | pub const Writer = std.io.Writer(*BufferedConnection, WriteError, write); | |
| 150 | ||
| 151 | pub fn writer(bconn: *BufferedConnection) Writer { | |
| 152 | return Writer{ .context = bconn }; | |
| 153 | } | |
| 154 | ||
| 155 | pub fn close(bconn: *BufferedConnection) void { | |
| 156 | bconn.conn.close(); | |
| 157 | } | |
| 158 | }; | |
| 159 | ||
| 77 | 160 | pub const Request = struct { |
| 78 | 161 | pub const Headers = struct { |
| 79 | 162 | method: http.Method, |
| ... | ... | @@ -222,7 +305,7 @@ pub const Response = struct { |
| 222 | 305 | |
| 223 | 306 | server: *Server, |
| 224 | 307 | address: net.Address, |
| 225 | connection: Connection, | |
| 308 | connection: BufferedConnection, | |
| 226 | 309 | |
| 227 | 310 | headers: Headers = .{}, |
| 228 | 311 | request: Request, |
| ... | ... | @@ -237,10 +320,10 @@ pub const Response = struct { |
| 237 | 320 | |
| 238 | 321 | if (!res.request.parser.done) { |
| 239 | 322 | // If the response wasn't fully read, then we need to close the connection. |
| 240 | res.connection.closing = true; | |
| 323 | res.connection.conn.closing = true; | |
| 241 | 324 | } |
| 242 | 325 | |
| 243 | if (res.connection.closing) { | |
| 326 | if (res.connection.conn.closing) { | |
| 244 | 327 | res.connection.close(); |
| 245 | 328 | |
| 246 | 329 | if (res.request.parser.header_bytes_owned) { |
| ... | ... | @@ -296,7 +379,7 @@ pub const Response = struct { |
| 296 | 379 | try buffered.flush(); |
| 297 | 380 | } |
| 298 | 381 | |
| 299 | pub const TransferReadError = Connection.ReadError || proto.HeadersParser.ReadError; | |
| 382 | pub const TransferReadError = BufferedConnection.ReadError || proto.HeadersParser.ReadError; | |
| 300 | 383 | |
| 301 | 384 | pub const TransferReader = std.io.Reader(*Response, TransferReadError, transferRead); |
| 302 | 385 | |
| ... | ... | @@ -309,7 +392,7 @@ pub const Response = struct { |
| 309 | 392 | |
| 310 | 393 | var index: usize = 0; |
| 311 | 394 | while (index == 0) { |
| 312 | const amt = try res.request.parser.read(res.connection.reader(), buf[index..], false); | |
| 395 | const amt = try res.request.parser.read(&res.connection, buf[index..], false); | |
| 313 | 396 | if (amt == 0 and res.request.parser.isComplete()) break; |
| 314 | 397 | index += amt; |
| 315 | 398 | } |
| ... | ... | @@ -317,17 +400,24 @@ pub const Response = struct { |
| 317 | 400 | return index; |
| 318 | 401 | } |
| 319 | 402 | |
| 320 | pub const WaitForCompleteHeadError = Connection.ReadError || proto.HeadersParser.WaitForCompleteHeadError || Request.Headers.ParseError || error{ BadHeader, InvalidCompression, StreamTooLong, InvalidWindowSize } || error{CompressionNotSupported}; | |
| 403 | pub const WaitForCompleteHeadError = BufferedConnection.ReadError || proto.HeadersParser.WaitForCompleteHeadError || Request.Headers.ParseError || error{ BadHeader, InvalidCompression, StreamTooLong, InvalidWindowSize } || error{CompressionNotSupported}; | |
| 321 | 404 | |
| 322 | 405 | pub fn waitForCompleteHead(res: *Response) !void { |
| 323 | try res.request.parser.waitForCompleteHead(res.connection.reader(), res.server.allocator); | |
| 406 | while (true) { | |
| 407 | try res.connection.fill(); | |
| 408 | ||
| 409 | const nchecked = try res.request.parser.checkCompleteHead(res.server.allocator, res.connection.peek()); | |
| 410 | res.connection.clear(@intCast(u16, nchecked)); | |
| 411 | ||
| 412 | if (res.request.parser.state.isContent()) break; | |
| 413 | } | |
| 324 | 414 | |
| 325 | 415 | res.request.headers = try Request.Headers.parse(res.request.parser.header_bytes.items); |
| 326 | 416 | |
| 327 | 417 | if (res.headers.connection == .keep_alive and res.request.headers.connection == .keep_alive) { |
| 328 | res.connection.closing = false; | |
| 418 | res.connection.conn.closing = false; | |
| 329 | 419 | } else { |
| 330 | res.connection.closing = true; | |
| 420 | res.connection.conn.closing = true; | |
| 331 | 421 | } |
| 332 | 422 | |
| 333 | 423 | if (res.request.headers.transfer_encoding) |te| { |
| ... | ... | @@ -388,7 +478,7 @@ pub const Response = struct { |
| 388 | 478 | return index; |
| 389 | 479 | } |
| 390 | 480 | |
| 391 | pub const WriteError = Connection.WriteError || error{ NotWriteable, MessageTooLong }; | |
| 481 | pub const WriteError = BufferedConnection.WriteError || error{ NotWriteable, MessageTooLong }; | |
| 392 | 482 | |
| 393 | 483 | pub const Writer = std.io.Writer(*Response, WriteError, write); |
| 394 | 484 | |
| ... | ... | @@ -479,10 +569,10 @@ pub fn accept(server: *Server, options: HeaderStrategy) AcceptError!*Response { |
| 479 | 569 | res.* = .{ |
| 480 | 570 | .server = server, |
| 481 | 571 | .address = in.address, |
| 482 | .connection = .{ | |
| 572 | .connection = .{ .conn = .{ | |
| 483 | 573 | .stream = in.stream, |
| 484 | 574 | .protocol = .plain, |
| 485 | }, | |
| 575 | } }, | |
| 486 | 576 | .request = .{ |
| 487 | 577 | .parser = switch (options) { |
| 488 | 578 | .dynamic => |max| proto.HeadersParser.initDynamic(max), |
lib/std/http/protocol.zig+49-69| ... | ... | @@ -29,9 +29,6 @@ pub const State = enum { |
| 29 | 29 | } |
| 30 | 30 | }; |
| 31 | 31 | |
| 32 | const read_buffer_size = 0x4000; | |
| 33 | const ReadBufferIndex = std.math.IntFittingRange(0, read_buffer_size); | |
| 34 | ||
| 35 | 32 | pub const HeadersParser = struct { |
| 36 | 33 | state: State = .start, |
| 37 | 34 | /// Wether or not `header_bytes` is allocated or was provided as a fixed buffer. |
| ... | ... | @@ -46,10 +43,6 @@ pub const HeadersParser = struct { |
| 46 | 43 | /// A message is only done when the entire payload has been read |
| 47 | 44 | done: bool = false, |
| 48 | 45 | |
| 49 | read_buffer: [read_buffer_size]u8 = undefined, | |
| 50 | read_buffer_start: ReadBufferIndex = 0, | |
| 51 | read_buffer_len: ReadBufferIndex = 0, | |
| 52 | ||
| 53 | 46 | pub fn initDynamic(max: usize) HeadersParser { |
| 54 | 47 | return .{ |
| 55 | 48 | .header_bytes = .{}, |
| ... | ... | @@ -232,7 +225,7 @@ pub const HeadersParser = struct { |
| 232 | 225 | } |
| 233 | 226 | }, |
| 234 | 227 | 4...vector_len - 1 => { |
| 235 | for (0..vector_len - 4) |i_usize| { | |
| 228 | inline for (0..vector_len - 3) |i_usize| { | |
| 236 | 229 | const i = @truncate(u32, i_usize); |
| 237 | 230 | |
| 238 | 231 | const b32 = int32(chunk[i..][0..4]); |
| ... | ... | @@ -246,6 +239,27 @@ pub const HeadersParser = struct { |
| 246 | 239 | return index + i + 2; |
| 247 | 240 | } |
| 248 | 241 | } |
| 242 | ||
| 243 | const b24 = int24(chunk[vector_len - 3 ..][0..3]); | |
| 244 | const b16 = intShift(u16, b24); | |
| 245 | const b8 = intShift(u8, b24); | |
| 246 | ||
| 247 | switch (b8) { | |
| 248 | '\r' => r.state = .seen_r, | |
| 249 | '\n' => r.state = .seen_n, | |
| 250 | else => {}, | |
| 251 | } | |
| 252 | ||
| 253 | switch (b16) { | |
| 254 | int16("\r\n") => r.state = .seen_rn, | |
| 255 | int16("\n\n") => r.state = .finished, | |
| 256 | else => {}, | |
| 257 | } | |
| 258 | ||
| 259 | switch (b24) { | |
| 260 | int24("\r\n\r") => r.state = .seen_rnr, | |
| 261 | else => {}, | |
| 262 | } | |
| 249 | 263 | }, |
| 250 | 264 | else => unreachable, |
| 251 | 265 | } |
| ... | ... | @@ -475,30 +489,6 @@ pub const HeadersParser = struct { |
| 475 | 489 | return i; |
| 476 | 490 | } |
| 477 | 491 | |
| 478 | /// Set of errors that `waitForCompleteHead` can throw except any errors inherited by `reader` | |
| 479 | pub const WaitForCompleteHeadError = CheckCompleteHeadError || error{UnexpectedEndOfStream}; | |
| 480 | ||
| 481 | /// Waits for the complete head to be available. This function will continue trying to read until the head is complete | |
| 482 | /// or an error occurs. | |
| 483 | pub fn waitForCompleteHead(r: *HeadersParser, reader: anytype, allocator: std.mem.Allocator) !void { | |
| 484 | if (r.state.isContent()) return; | |
| 485 | ||
| 486 | while (true) { | |
| 487 | if (r.read_buffer_start == r.read_buffer_len) { | |
| 488 | const nread = try reader.read(r.read_buffer[0..]); | |
| 489 | if (nread == 0) return error.UnexpectedEndOfStream; | |
| 490 | ||
| 491 | r.read_buffer_start = 0; | |
| 492 | r.read_buffer_len = @intCast(ReadBufferIndex, nread); | |
| 493 | } | |
| 494 | ||
| 495 | const amt = try r.checkCompleteHead(allocator, r.read_buffer[r.read_buffer_start..r.read_buffer_len]); | |
| 496 | r.read_buffer_start += @intCast(ReadBufferIndex, amt); | |
| 497 | ||
| 498 | if (amt != 0) return; | |
| 499 | } | |
| 500 | } | |
| 501 | ||
| 502 | 492 | pub const ReadError = error{ |
| 503 | 493 | UnexpectedEndOfStream, |
| 504 | 494 | HttpHeadersExceededSizeLimit, |
| ... | ... | @@ -507,48 +497,40 @@ pub const HeadersParser = struct { |
| 507 | 497 | |
| 508 | 498 | /// Reads the body of the message into `buffer`. If `skip` is true, the buffer will be unused and the body will be |
| 509 | 499 | /// skipped. Returns the number of bytes placed in the buffer. |
| 510 | pub fn read(r: *HeadersParser, reader: anytype, buffer: []u8, skip: bool) !usize { | |
| 500 | pub fn read(r: *HeadersParser, bconn: anytype, buffer: []u8, skip: bool) !usize { | |
| 511 | 501 | assert(r.state.isContent()); |
| 512 | 502 | if (r.done) return 0; |
| 513 | 503 | |
| 514 | if (r.read_buffer_start == r.read_buffer_len) { | |
| 515 | const nread = try reader.read(r.read_buffer[0..]); | |
| 516 | if (nread == 0) return error.UnexpectedEndOfStream; | |
| 517 | ||
| 518 | r.read_buffer_start = 0; | |
| 519 | r.read_buffer_len = @intCast(ReadBufferIndex, nread); | |
| 520 | } | |
| 521 | ||
| 522 | 504 | var out_index: usize = 0; |
| 523 | 505 | while (true) { |
| 524 | 506 | switch (r.state) { |
| 525 | 507 | .invalid, .start, .seen_n, .seen_r, .seen_rn, .seen_rnr => unreachable, |
| 526 | 508 | .finished => { |
| 527 | const buf_avail = r.read_buffer_len - r.read_buffer_start; | |
| 528 | 509 | const data_avail = r.next_chunk_length; |
| 529 | const out_avail = buffer.len; | |
| 530 | 510 | |
| 531 | // TODO https://github.com/ziglang/zig/issues/14039 | |
| 532 | const read_available = @intCast(usize, @min(buf_avail, data_avail)); | |
| 533 | 511 | if (skip) { |
| 534 | r.next_chunk_length -= read_available; | |
| 535 | r.read_buffer_start += @intCast(ReadBufferIndex, read_available); | |
| 536 | } else { | |
| 537 | const can_read = @min(read_available, out_avail); | |
| 538 | r.next_chunk_length -= can_read; | |
| 512 | try bconn.fill(); | |
| 539 | 513 | |
| 540 | mem.copy(u8, buffer[out_index..], r.read_buffer[r.read_buffer_start..][0..can_read]); | |
| 541 | r.read_buffer_start += @intCast(ReadBufferIndex, can_read); | |
| 542 | out_index += can_read; | |
| 514 | const nread = @min(bconn.peek().len, data_avail); | |
| 515 | bconn.clear(@intCast(u16, nread)); | |
| 516 | r.next_chunk_length -= nread; | |
| 517 | ||
| 518 | return 0; | |
| 543 | 519 | } |
| 544 | 520 | |
| 545 | if (r.next_chunk_length == 0) r.done = true; | |
| 521 | const out_avail = buffer.len; | |
| 546 | 522 | |
| 547 | return out_index; | |
| 523 | const can_read = @min(data_avail, out_avail); | |
| 524 | const nread = try bconn.read(buffer[0..can_read]); | |
| 525 | r.next_chunk_length -= nread; | |
| 526 | ||
| 527 | return nread; | |
| 548 | 528 | }, |
| 549 | 529 | .chunk_data_suffix, .chunk_data_suffix_r, .chunk_head_size, .chunk_head_ext, .chunk_head_r => { |
| 550 | const i = r.findChunkedLen(r.read_buffer[r.read_buffer_start..r.read_buffer_len]); | |
| 551 | r.read_buffer_start += @intCast(ReadBufferIndex, i); | |
| 530 | try bconn.fill(); | |
| 531 | ||
| 532 | const i = r.findChunkedLen(bconn.peek()); | |
| 533 | bconn.clear(@intCast(u16, i)); | |
| 552 | 534 | |
| 553 | 535 | switch (r.state) { |
| 554 | 536 | .invalid => return error.HttpChunkInvalid, |
| ... | ... | @@ -565,22 +547,20 @@ pub const HeadersParser = struct { |
| 565 | 547 | continue; |
| 566 | 548 | }, |
| 567 | 549 | .chunk_data => { |
| 568 | const buf_avail = r.read_buffer_len - r.read_buffer_start; | |
| 569 | 550 | const data_avail = r.next_chunk_length; |
| 570 | const out_avail = buffer.len; | |
| 551 | const out_avail = buffer.len - out_index; | |
| 571 | 552 | |
| 572 | // TODO https://github.com/ziglang/zig/issues/14039 | |
| 573 | const read_available = @intCast(usize, @min(buf_avail, data_avail)); | |
| 574 | 553 | if (skip) { |
| 575 | r.next_chunk_length -= read_available; | |
| 576 | r.read_buffer_start += @intCast(ReadBufferIndex, read_available); | |
| 577 | } else { | |
| 578 | const can_read = @min(read_available, out_avail); | |
| 579 | r.next_chunk_length -= can_read; | |
| 554 | try bconn.fill(); | |
| 580 | 555 | |
| 581 | mem.copy(u8, buffer[out_index..], r.read_buffer[r.read_buffer_start..][0..can_read]); | |
| 582 | r.read_buffer_start += @intCast(ReadBufferIndex, can_read); | |
| 583 | out_index += can_read; | |
| 556 | const nread = @min(bconn.peek().len, data_avail); | |
| 557 | bconn.clear(@intCast(u16, nread)); | |
| 558 | r.next_chunk_length -= nread; | |
| 559 | } else { | |
| 560 | const can_read = @min(data_avail, out_avail); | |
| 561 | const nread = try bconn.read(buffer[out_index..][0..can_read]); | |
| 562 | r.next_chunk_length -= nread; | |
| 563 | out_index += nread; | |
| 584 | 564 | } |
| 585 | 565 | |
| 586 | 566 | if (r.next_chunk_length == 0) { |