const std = @import("std"); const Parser = @This(); const Header = @import("Header.zig"); const Method = @import("Method.zig").Method; const Response = @import("../Response.zig"); const RequestHandler = @import("../RequestHandler.zig"); const RequestRouter = @import("../RequestRouter.zig"); const Error = error{ MethodNotSupported, HttpVersionNotSupported, MissingLineFeed, InvalidContentLength, RouterError, HandlerError, }; const Vec: type = @Vector(std.simd.suggestVectorLength(u8).?, u8); const vec_len = @typeInfo(Vec).vector.len; const Pattern = struct { value: Vec, mask: Vec, len: u6, pub fn init(comptime prefix: []const u8) Pattern { if (prefix.len > vec_len) { @compileError("Prefix length is too high"); } var value: [vec_len]u8 = undefined; var mask: [vec_len]u8 = undefined; for (0..vec_len) |i| { if (i < prefix.len) { value[i] = prefix[i]; mask[i] = 0xFF; } else { value[i] = 0x00; mask[i] = 0x00; } } } inline fn check(self: Pattern, vec: Vec) bool { return @reduce(.And, vec & self.mask == self.value); } }; const patterns = struct { pub const methods = struct { // NOTE These patterns are arranged in a specific order, such that the // first ones are the most common (based on vibes only). pub const GET = Pattern.init("GET "); pub const POST = Pattern.init("POST "); pub const HEAD = Pattern.init("HEAD "); pub const PUT = Pattern.init("PUT "); pub const DELETE = Pattern.init("DELETE "); pub const PATCH = Pattern.init("PATCH "); pub const OPTIONS = Pattern.init("OPTIONS "); pub const CONNECT = Pattern.init("CONNECT "); pub const TRACE = Pattern.init("TRACE "); }; pub const @"version_http/1.1" = Pattern.init("HTTP/1.1\r\n"); }; inline fn hasSpace(vec: Vec) bool { const has_space = vec == @as(Vec, @splat(' ')); return @reduce(.Or, has_space); } inline fn hasCRLF(vec: Vec) bool { const has_cr = vec == @as(Vec, @splat('\r')); const has_lf = vec == @as(Vec, @splat('\n')); return @reduce(.Or, has_cr | has_lf); } const State = union(enum) { pub fn methodComplete(method: Method) State { return .{ .method_complete = .{ .method = method, }, }; } pub fn pathname(method: Method, p: []const u8) State { return .{ .pathname_state = .{ .method = method, .pathname = p, }, }; } pub fn headerValue(name: []const u8, value: []const u8) State { return .{ .header_value = .{ .name = name, .value = value, }, }; } init: void, // TODO Add all methods here and in `consumeChar` (they are covered by // `consumeVec`, though) method_d: void, method_g: void, method_h: void, method_p: void, method_de: void, method_ge: void, method_he: void, method_pa: void, method_po: void, method_pu: void, method_del: void, method_hea: void, method_pat: void, method_pos: void, method_dele: void, method_patc: void, method_delet: void, method_complete: struct { method: Method }, pathname_state: struct { method: Method, pathname: []const u8 }, pathname_complete: void, version_h: void, version_ht: void, version_htt: void, version_http: void, @"version_http/": void, @"version_http/1": void, @"version_http/1.": void, version_complete: void, start_line_end: void, header_name_start: void, header_name: []const u8, header_value: struct { name: []const u8, value: []const u8 }, header_line_end: void, headers_end: void, body: []const u8, }; const ConsumeResult = struct { consumed: usize, done: bool, }; const ConsumeCharResult = enum { not_done, done, }; request_router: RequestRouter, response: *Response, state: State, content_length: usize, request_handler: ?RequestHandler = null, last_router_error: anyerror = undefined, last_handler_error: anyerror = undefined, pub fn init(request_router: RequestRouter, response: *Response) Parser { return .{ .request_router = request_router, .response = response, .state = .init, .content_length = 0, }; } pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult { var i: usize = 0; while (i < chars.len) { switch (self.state) { .body => |body| { const to_consume = @min(chars.len - i, self.content_length - body.len); const new_body = body.ptr[0 .. body.len + to_consume]; self.state = .{ .body = new_body }; i += to_consume; const done = new_body.len >= self.content_length; if (done) { self.request_handler.?.rawBody(self.response, new_body) catch |err| { self.last_handler_error = err; return error.HandlerError; }; } return .{ .consumed = i, .done = done, }; }, else => { if (chars.len - i >= vec_len) { const vec_res = try self.consumeVec(chars[i..][0..vec_len]); i += vec_res.consumed; if (vec_res.done) { return .{ .consumed = i, .done = true, }; } if (vec_res.consumed > 0) { continue; } } const char_res = try self.consumeChar(&chars[i]); i += 1; if (char_res == .done) return .{ .consumed = i, .done = true, }; }, } } return .{ .consumed = chars.len, .done = false, }; } pub fn consumeVec(self: *Parser, vec: *const [vec_len]u8) Error!ConsumeResult { switch (self.state) { .init => { inline for (@typeInfo(patterns.methods).@"struct".decls) |decl| { const pattern: Pattern = @field(patterns.methods, decl.name); if (pattern.check(vec)) { self.state = .methodComplete(@field(Method, decl.name)); return .{ .consumed = pattern.len, .done = false, }; } } return error.MethodNotSupported; }, .pathname_state => |s| { if (hasSpace(vec)) { // Delegate to `consumeChar`. return .{ .consumed = 0, .done = false, }; } self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + vec_len]); }, .pathname_complete => { if (patterns.@"version_http/1.1".check(vec)) { self.state = .header_name_start; return .{ .consumed = patterns.@"version_http/1.1".len, .done = false, }; } else { return error.HttpVersionNotSupported; } }, .header_value => |s| { if (hasCRLF(vec)) { // Delegate to `consumeChar`. return .{ .consumed = 0, .done = false, }; } self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + vec_len]); }, else => { // Delegate to `consumeChar`. return .{ .consumed = 0, .done = false, }; }, } } pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult { const c = c_ptr.*; const c_slice = @as([*]const u8, @ptrCast(c_ptr))[0..1]; switch (self.state) { .init => switch (c) { 'D' => self.state = .method_d, 'G' => self.state = .method_g, 'H' => self.state = .method_h, 'P' => self.state = .method_p, else => return error.MethodNotSupported, }, .method_d => switch (c) { 'E' => self.state = .method_de, else => return error.MethodNotSupported, }, .method_g => switch (c) { 'E' => self.state = .method_ge, else => return error.MethodNotSupported, }, .method_h => switch (c) { 'E' => self.state = .method_he, else => return error.MethodNotSupported, }, .method_p => switch (c) { 'A' => self.state = .method_pa, 'O' => self.state = .method_po, 'U' => self.state = .method_pu, else => return error.MethodNotSupported, }, .method_de => switch (c) { 'L' => self.state = .method_del, else => return error.MethodNotSupported, }, .method_ge => switch (c) { 'T' => self.state = .methodComplete(.GET), else => return error.MethodNotSupported, }, .method_he => switch (c) { 'A' => self.state = .method_hea, else => return error.MethodNotSupported, }, .method_pa => switch (c) { 'T' => self.state = .method_pat, else => return error.MethodNotSupported, }, .method_po => switch (c) { 'S' => self.state = .method_pos, else => return error.MethodNotSupported, }, .method_pu => switch (c) { 'T' => self.state = .methodComplete(.PUT), else => return error.MethodNotSupported, }, .method_del => switch (c) { 'E' => self.state = .method_dele, else => return error.MethodNotSupported, }, .method_hea => switch (c) { 'D' => self.state = .methodComplete(.HEAD), else => return error.MethodNotSupported, }, .method_pat => switch (c) { 'C' => self.state = .method_patc, else => return error.MethodNotSupported, }, .method_pos => switch (c) { 'T' => self.state = .methodComplete(.POST), else => return error.MethodNotSupported, }, .method_dele => switch (c) { 'T' => self.state = .method_delet, else => return error.MethodNotSupported, }, .method_patc => switch (c) { 'H' => self.state = .methodComplete(.PATCH), else => return error.MethodNotSupported, }, .method_delet => switch (c) { 'E' => self.state = .methodComplete(.DELETE), else => return error.MethodNotSupported, }, .method_complete => |s| switch (c) { ' ' => self.state = .pathname(s.method, @as([*]const u8, @ptrCast(c_ptr))[1..1]), else => return error.MethodNotSupported, }, .pathname_state => |s| switch (c) { ' ' => { self.state = .pathname_complete; self.request_handler = self.request_router.rawRoute(.init(s.method, s.pathname)) catch |err| { self.last_router_error = err; return error.RouterError; }; }, else => self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + 1]), }, .pathname_complete => switch (c) { 'H' => self.state = .version_h, else => return error.HttpVersionNotSupported, }, .version_h => switch (c) { 'T' => self.state = .version_ht, else => return error.HttpVersionNotSupported, }, .version_ht => switch (c) { 'T' => self.state = .version_htt, else => return error.HttpVersionNotSupported, }, .version_htt => switch (c) { 'P' => self.state = .version_http, else => return error.HttpVersionNotSupported, }, .version_http => switch (c) { '/' => self.state = .@"version_http/", else => return error.HttpVersionNotSupported, }, .@"version_http/" => switch (c) { '1' => self.state = .@"version_http/1", else => return error.HttpVersionNotSupported, }, .@"version_http/1" => switch (c) { '.' => self.state = .@"version_http/1.", else => return error.HttpVersionNotSupported, }, .@"version_http/1." => switch (c) { '1' => self.state = .version_complete, else => return error.HttpVersionNotSupported, }, .version_complete => switch (c) { '\r' => self.state = .start_line_end, else => return error.HttpVersionNotSupported, }, .start_line_end => switch (c) { '\n' => self.state = .header_name_start, else => return error.MissingLineFeed, }, .header_name_start => switch (c) { '\r' => self.state = .headers_end, else => self.state = .{ .header_name = c_slice }, }, .header_name => |name| switch (c) { ':' => { self.state = .headerValue(name, @as([*]const u8, @ptrCast(c_ptr))[1..1]); }, else => self.state = .{ .header_name = name.ptr[0 .. name.len + 1] }, }, .header_value => |s| switch (c) { '\r' => { self.state = .header_line_end; const header: Header = .init(s.name, std.mem.trim(u8, s.value, " \t")); if (header.isKnown(.@"Content-Length")) { self.content_length = std.fmt.parseInt(usize, header.value, 10) catch return error.InvalidContentLength; } self.request_handler.rawHeader(header) catch |err| { self.last_handler_error = err; return error.HandlerError; }; }, else => self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + 1]), }, .header_line_end => switch (c) { '\n' => self.state = .header_name_start, else => return error.MissingLineFeed, }, .headers_end => switch (c) { '\n' => { if (self.content_length == 0) { self.handler.rawBody(self.request, &.{}); return .done; } self.state = .{ .body = @as([*]const u8, @ptrCast(c_ptr))[1..1] }; }, else => return error.MissingLineFeed, }, .body => |body| { const new_body = body.ptr[0 .. body.len + 1]; self.state = .{ .body = new_body }; if (new_body.len >= self.content_length) { self.handler.rawBody(self.request, new_body); return .done; } }, } return .not_done; }