diff --git a/lib/std/http.zig b/lib/std/http.zig index cf92b462b8..944271df27 100644 --- a/lib/std/http.zig +++ b/lib/std/http.zig @@ -242,10 +242,60 @@ pub const Status = enum(u10) { } }; +pub const Headers = struct { + state: State = .start, + invalid_index: u32 = undefined, + + pub const State = enum { invalid, start, line, nl_r, nl_n, nl2_r, finished }; + + /// Returns how many bytes are processed into headers. Always less than or + /// equal to bytes.len. If the amount returned is less than bytes.len, it + /// means the headers ended and the first byte after the double \r\n\r\n is + /// located at `bytes[result]`. + pub fn feed(h: *Headers, bytes: []const u8) usize { + for (bytes) |b, i| { + switch (h.state) { + .start => switch (b) { + '\r' => h.state = .nl_r, + '\n' => return invalid(h, i), + else => {}, + }, + .nl_r => switch (b) { + '\n' => h.state = .nl_n, + else => return invalid(h, i), + }, + .nl_n => switch (b) { + '\r' => h.state = .nl2_r, + else => h.state = .line, + }, + .nl2_r => switch (b) { + '\n' => h.state = .finished, + else => return invalid(h, i), + }, + .line => switch (b) { + '\r' => h.state = .nl_r, + '\n' => return invalid(h, i), + else => {}, + }, + .invalid => return i, + .finished => return i, + } + } + return bytes.len; + } + + fn invalid(h: *Headers, i: usize) usize { + h.invalid_index = @intCast(u32, i); + h.state = .invalid; + return i; + } +}; + const std = @import("std.zig"); test { _ = Client; _ = Method; _ = Status; + _ = Headers; } diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index d27d879663..efae62680d 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -16,6 +16,7 @@ pub const Request = struct { headers: std.ArrayListUnmanaged(u8) = .{}, tls_client: std.crypto.tls.Client, protocol: Protocol, + response_headers: http.Headers = .{}, pub const Protocol = enum { http, https }; @@ -51,18 +52,53 @@ pub const Request = struct { } } + pub fn readAll(req: *Request, buffer: []u8) !usize { + return readAtLeast(req, buffer, buffer.len); + } + pub fn read(req: *Request, buffer: []u8) !usize { + return readAtLeast(req, buffer, 1); + } + + pub fn readAtLeast(req: *Request, buffer: []u8, len: usize) !usize { + assert(len <= buffer.len); + var index: usize = 0; + while (index < len) { + const headers_finished = req.response_headers.state == .finished; + const amt = try readAdvanced(req, buffer[index..]); + if (amt == 0 and headers_finished) break; + index += amt; + } + return index; + } + + /// This one can return 0 without meaning EOF. + /// TODO change to readvAdvanced + pub fn readAdvanced(req: *Request, buffer: []u8) !usize { + if (req.response_headers.state == .finished) return readRaw(req, buffer); + + const amt = try readRaw(req, buffer); + const data = buffer[0..amt]; + const i = req.response_headers.feed(data); + if (req.response_headers.state == .invalid) return error.InvalidHttpHeaders; + if (i < data.len) { + const rest = data[i..]; + std.mem.copy(u8, buffer, rest); + return rest.len; + } + return 0; + } + + /// Only abstracts over http/https. + fn readRaw(req: *Request, buffer: []u8) !usize { switch (req.protocol) { .http => return req.stream.read(buffer), .https => return req.tls_client.read(req.stream, buffer), } } - pub fn readAll(req: *Request, buffer: []u8) !usize { - return readAtLeast(req, buffer, buffer.len); - } - - pub fn readAtLeast(req: *Request, buffer: []u8, len: usize) !usize { + /// Only abstracts over http/https. + fn readAtLeastRaw(req: *Request, buffer: []u8, len: usize) !usize { switch (req.protocol) { .http => return req.stream.readAtLeast(buffer, len), .https => return req.tls_client.readAtLeast(req.stream, buffer, len),