From d574875f00db32c40019c69465d450a6a58da67f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 16 Feb 2024 17:12:03 -0700 Subject: [PATCH] Revert "std.http: remove 'done' flag" This reverts commit 42be972a72c86b32ad8403d082ab42763c6facec. Using a bit to distinguish between headers and trailers is fine. It was just named and documented poorly. --- lib/std/http/Client.zig | 18 +++--- lib/std/http/Server.zig | 12 ++-- lib/std/http/protocol.zig | 115 ++++++++++++++++++-------------------- test/standalone/http.zig | 2 +- 4 files changed, 71 insertions(+), 76 deletions(-) diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 3eae537d29..2064d767ba 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -649,7 +649,7 @@ pub const Request = struct { /// Frees all resources associated with the request. pub fn deinit(req: *Request) void { if (req.connection) |connection| { - if (req.response.parser.state != .complete) { + if (!req.response.parser.done) { // If the response wasn't fully read, then we need to close the connection. connection.closing = true; } @@ -664,7 +664,7 @@ pub const Request = struct { // or keep those which will be used. // This needs to be kept in sync with deinit and request. fn redirect(req: *Request, uri: Uri) !void { - assert(req.response.parser.state == .complete); + assert(req.response.parser.done); req.client.connection_pool.release(req.client.allocator, req.connection.?); req.connection = null; @@ -823,12 +823,12 @@ pub const Request = struct { } fn transferRead(req: *Request, buf: []u8) TransferReadError!usize { - if (req.response.parser.state == .complete) return 0; + if (req.response.parser.done) return 0; var index: usize = 0; while (index == 0) { const amt = try req.response.parser.read(req.connection.?, buf[index..], req.response.skip); - if (amt == 0 and req.response.parser.state == .complete) break; + if (amt == 0 and req.response.parser.done) break; index += amt; } @@ -873,7 +873,7 @@ pub const Request = struct { if (req.response.status == .@"continue") { // We're done parsing the continue response; reset to prepare // for the real response. - req.response.parser.state = .complete; + req.response.parser.done = true; req.response.parser.reset(); if (req.handle_continue) @@ -885,7 +885,7 @@ pub const Request = struct { // we're switching protocols, so this connection is no longer doing http if (req.method == .CONNECT and req.response.status.class() == .success) { connection.closing = false; - req.response.parser.state = .complete; + req.response.parser.done = true; return; // the connection is not HTTP past this point } @@ -899,7 +899,7 @@ pub const Request = struct { if (req.method == .HEAD or req.response.status.class() == .informational or req.response.status == .no_content or req.response.status == .not_modified) { - req.response.parser.state = .complete; + req.response.parser.done = true; return; // The response is empty; no further setup or redirection is necessary. } @@ -914,7 +914,7 @@ pub const Request = struct { } else if (req.response.content_length) |cl| { req.response.parser.next_chunk_length = cl; - if (cl == 0) req.response.parser.state = .complete; + if (cl == 0) req.response.parser.done = true; } else { // read until the connection is closed req.response.parser.next_chunk_length = std.math.maxInt(u64); @@ -973,7 +973,7 @@ pub const Request = struct { try req.send(.{}); } else { req.response.skip = false; - if (req.response.parser.state != .complete) { + if (!req.response.parser.done) { switch (req.response.transfer_compression) { .identity => req.response.compression = .none, .compress, .@"x-compress" => return error.CompressionUnsupported, diff --git a/lib/std/http/Server.zig b/lib/std/http/Server.zig index 8444ab8346..6b2302a816 100644 --- a/lib/std/http/Server.zig +++ b/lib/std/http/Server.zig @@ -352,7 +352,7 @@ pub const Response = struct { return .reset; } - if (res.request.parser.state != .complete) { + if (!res.request.parser.done) { // If the response wasn't fully read, then we need to close the connection. res.connection.closing = true; return .closing; @@ -447,12 +447,12 @@ pub const Response = struct { } fn transferRead(res: *Response, buf: []u8) TransferReadError!usize { - if (res.request.parser.state == .complete) return 0; + if (res.request.parser.done) return 0; var index: usize = 0; while (index == 0) { const amt = try res.request.parser.read(&res.connection, buf[index..], false); - if (amt == 0 and res.request.parser.state == .complete) break; + if (amt == 0 and res.request.parser.done) break; index += amt; } @@ -502,9 +502,9 @@ pub const Response = struct { if (res.request.content_length) |len| { res.request.parser.next_chunk_length = len; - if (len == 0) res.request.parser.state = .complete; + if (len == 0) res.request.parser.done = true; } else { - res.request.parser.state = .complete; + res.request.parser.done = true; } }, .chunked => { @@ -513,7 +513,7 @@ pub const Response = struct { }, } - if (res.request.parser.state != .complete) { + if (!res.request.parser.done) { switch (res.request.transfer_compression) { .identity => res.request.compression = .none, .compress, .@"x-compress" => return error.CompressionUnsupported, diff --git a/lib/std/http/protocol.zig b/lib/std/http/protocol.zig index b250c1c0b6..62016e408d 100644 --- a/lib/std/http/protocol.zig +++ b/lib/std/http/protocol.zig @@ -7,76 +7,64 @@ const assert = std.debug.assert; const use_vectors = builtin.zig_backend != .stage2_x86_64; pub const State = enum { - /// Begin header parsing states. invalid, + + // Begin header and trailer parsing states. + start, seen_n, seen_r, seen_rn, seen_rnr, - headers_end, - /// Begin transfer-encoding: chunked parsing states. + finished, + + // Begin transfer-encoding: chunked parsing states. + chunk_head_size, chunk_head_ext, chunk_head_r, chunk_data, chunk_data_suffix, chunk_data_suffix_r, - /// When the parser has finished parsing a complete message. A message is - /// only complete after the entire body has been read and any trailing - /// headers have been parsed. - complete, /// Returns true if the parser is in a content state (ie. not waiting for more headers). pub fn isContent(self: State) bool { return switch (self) { - .invalid, - .start, - .seen_n, - .seen_r, - .seen_rn, - .seen_rnr, - => false, - - .headers_end, - .chunk_head_size, - .chunk_head_ext, - .chunk_head_r, - .chunk_data, - .chunk_data_suffix, - .chunk_data_suffix_r, - .complete, - => true, + .invalid, .start, .seen_n, .seen_r, .seen_rn, .seen_rnr => false, + .finished, .chunk_head_size, .chunk_head_ext, .chunk_head_r, .chunk_data, .chunk_data_suffix, .chunk_data_suffix_r => true, }; } }; pub const HeadersParser = struct { - state: State, + state: State = .start, /// A fixed buffer of len `max_header_bytes`. /// Pointers into this buffer are not stable until after a message is complete. header_bytes_buffer: []u8, header_bytes_len: u32, next_chunk_length: u64, + /// `false`: headers. `true`: trailers. + done: bool, /// Initializes the parser with a provided buffer `buf`. pub fn init(buf: []u8) HeadersParser { return .{ - .state = .start, .header_bytes_buffer = buf, .header_bytes_len = 0, + .done = false, .next_chunk_length = 0, }; } /// Reinitialize the parser. - /// Asserts the parser is in the `complete` state. + /// Asserts the parser is in the "done" state. pub fn reset(hp: *HeadersParser) void { - assert(hp.state == .complete); + assert(hp.done); hp.* = .{ .state = .start, .header_bytes_buffer = hp.header_bytes_buffer, .header_bytes_len = 0, + .done = false, .next_chunk_length = 0, }; } @@ -101,8 +89,7 @@ pub const HeadersParser = struct { while (true) { switch (r.state) { .invalid => unreachable, - .complete => unreachable, - .headers_end => return index, + .finished => return index, .start => switch (len - index) { 0 => return index, 1 => { @@ -126,7 +113,7 @@ pub const HeadersParser = struct { switch (b16) { int16("\r\n") => r.state = .seen_rn, - int16("\n\n") => r.state = .headers_end, + int16("\n\n") => r.state = .finished, else => {}, } @@ -145,7 +132,7 @@ pub const HeadersParser = struct { switch (b16) { int16("\r\n") => r.state = .seen_rn, - int16("\n\n") => r.state = .headers_end, + int16("\n\n") => r.state = .finished, else => {}, } @@ -170,7 +157,7 @@ pub const HeadersParser = struct { switch (b16) { int16("\r\n") => r.state = .seen_rn, - int16("\n\n") => r.state = .headers_end, + int16("\n\n") => r.state = .finished, else => {}, } @@ -180,7 +167,7 @@ pub const HeadersParser = struct { } switch (b32) { - int32("\r\n\r\n") => r.state = .headers_end, + int32("\r\n\r\n") => r.state = .finished, else => {}, } @@ -228,7 +215,7 @@ pub const HeadersParser = struct { switch (b16) { int16("\r\n") => r.state = .seen_rn, - int16("\n\n") => r.state = .headers_end, + int16("\n\n") => r.state = .finished, else => {}, } }, @@ -245,7 +232,7 @@ pub const HeadersParser = struct { switch (b16) { int16("\r\n") => r.state = .seen_rn, - int16("\n\n") => r.state = .headers_end, + int16("\n\n") => r.state = .finished, else => {}, } @@ -262,10 +249,10 @@ pub const HeadersParser = struct { const b16 = intShift(u16, b32); if (b32 == int32("\r\n\r\n")) { - r.state = .headers_end; + r.state = .finished; return index + i + 4; } else if (b16 == int16("\n\n")) { - r.state = .headers_end; + r.state = .finished; return index + i + 2; } } @@ -282,7 +269,7 @@ pub const HeadersParser = struct { switch (b16) { int16("\r\n") => r.state = .seen_rn, - int16("\n\n") => r.state = .headers_end, + int16("\n\n") => r.state = .finished, else => {}, } @@ -302,7 +289,7 @@ pub const HeadersParser = struct { 0 => return index, else => { switch (bytes[index]) { - '\n' => r.state = .headers_end, + '\n' => r.state = .finished, else => r.state = .start, } @@ -334,7 +321,7 @@ pub const HeadersParser = struct { switch (b16) { int16("\r\n") => r.state = .seen_rn, int16("\n\r") => r.state = .seen_rnr, - int16("\n\n") => r.state = .headers_end, + int16("\n\n") => r.state = .finished, else => {}, } @@ -353,12 +340,12 @@ pub const HeadersParser = struct { switch (b16) { int16("\r\n") => r.state = .seen_rn, - int16("\n\n") => r.state = .headers_end, + int16("\n\n") => r.state = .finished, else => {}, } switch (b24) { - int24("\n\r\n") => r.state = .headers_end, + int24("\n\r\n") => r.state = .finished, else => {}, } @@ -388,8 +375,8 @@ pub const HeadersParser = struct { } switch (b16) { - int16("\r\n") => r.state = .headers_end, - int16("\n\n") => r.state = .headers_end, + int16("\r\n") => r.state = .finished, + int16("\n\n") => r.state = .finished, else => {}, } @@ -401,7 +388,7 @@ pub const HeadersParser = struct { 0 => return index, else => { switch (bytes[index]) { - '\n' => r.state = .headers_end, + '\n' => r.state = .finished, else => r.state = .start, } @@ -502,6 +489,13 @@ pub const HeadersParser = struct { return len; } + /// Returns whether or not the parser has finished parsing a complete + /// message. A message is only complete after the entire body has been read + /// and any trailing headers have been parsed. + pub fn isComplete(r: *HeadersParser) bool { + return r.done and r.state == .finished; + } + pub const CheckCompleteHeadError = error{HttpHeadersOversize}; /// Pushes `in` into the parser. Returns the number of bytes consumed by @@ -532,12 +526,13 @@ pub const HeadersParser = struct { /// See `std.http.Client.Connection for an example of `conn`. pub fn read(r: *HeadersParser, conn: anytype, buffer: []u8, skip: bool) !usize { assert(r.state.isContent()); + if (r.done) return 0; + var out_index: usize = 0; while (true) { switch (r.state) { - .complete => return out_index, .invalid, .start, .seen_n, .seen_r, .seen_rn, .seen_rnr => unreachable, - .headers_end => { + .finished => { const data_avail = r.next_chunk_length; if (skip) { @@ -547,8 +542,7 @@ pub const HeadersParser = struct { conn.drop(@intCast(nread)); r.next_chunk_length -= nread; - if (r.next_chunk_length == 0 or nread == 0) - r.state = .complete; + if (r.next_chunk_length == 0 or nread == 0) r.done = true; return out_index; } else if (out_index < buffer.len) { @@ -558,8 +552,7 @@ pub const HeadersParser = struct { const nread = try conn.read(buffer[0..can_read]); r.next_chunk_length -= nread; - if (r.next_chunk_length == 0 or nread == 0) - r.state = .complete; + if (r.next_chunk_length == 0 or nread == 0) r.done = true; return nread; } else { @@ -576,12 +569,14 @@ pub const HeadersParser = struct { .invalid => return error.HttpChunkInvalid, .chunk_data => if (r.next_chunk_length == 0) { if (std.mem.eql(u8, conn.peek(), "\r\n")) { - r.state = .complete; + r.state = .finished; + r.done = true; } else { - // The trailer section is formatted identically - // to the header section. + // The trailer section is formatted identically to the header section. r.state = .seen_rn; } + r.done = true; + return out_index; }, else => return out_index, @@ -619,21 +614,21 @@ pub const HeadersParser = struct { }; inline fn int16(array: *const [2]u8) u16 { - return @bitCast(array.*); + return @as(u16, @bitCast(array.*)); } inline fn int24(array: *const [3]u8) u24 { - return @bitCast(array.*); + return @as(u24, @bitCast(array.*)); } inline fn int32(array: *const [4]u8) u32 { - return @bitCast(array.*); + return @as(u32, @bitCast(array.*)); } inline fn intShift(comptime T: type, x: anytype) T { switch (@import("builtin").cpu.arch.endian()) { - .little => return @truncate(x >> (@bitSizeOf(@TypeOf(x)) - @bitSizeOf(T))), - .big => return @truncate(x), + .little => return @as(T, @truncate(x >> (@bitSizeOf(@TypeOf(x)) - @bitSizeOf(T)))), + .big => return @as(T, @truncate(x)), } } diff --git a/test/standalone/http.zig b/test/standalone/http.zig index ee029538bc..d6756fbeb8 100644 --- a/test/standalone/http.zig +++ b/test/standalone/http.zig @@ -673,7 +673,7 @@ pub fn main() !void { var req = try client.open(.GET, uri, .{ .server_header_buffer = headers_buf, }); - req.response.parser.state = .complete; + req.response.parser.done = true; req.connection.?.closing = false; requests[i] = req; }