const std = @import("std"); const http = @import("../lib.zig"); const Method = http.Method; const Fields = http.Fields; const Request = @import("../request.zig").Request; const request_buf_size = 1 << 16; const max_path_len = 1 << 10; const max_body_len = 1 << 12; fn ParseError(comptime Reader: type) type { return error{ MethodNotImplemented, } | Reader.ReadError; } const Encoding = enum { identity, chunked, }; pub fn parse(alloc: std.mem.Allocator, reader: anytype) !Request { const method = try parseMethod(reader); const uri = reader.readUntilDelimiterAlloc(alloc, ' ', max_path_len) catch |err| switch (err) { error.StreamTooLong => return error.RequestUriTooLong, else => return err, }; errdefer alloc.free(uri); const proto = try parseProto(reader); // discard \r\n switch (try reader.readByte()) { '\r' => if (try reader.readByte() != '\n') return error.BadRequest, '\n' => {}, else => return error.BadRequest, } var headers = try parseHeaders(alloc, reader); errdefer headers.deinit(); const body = if (method.requestHasBody()) try readBody(alloc, headers, reader) else null; errdefer if (body) |b| alloc.free(b); return Request{ .protocol = proto, .method = method, .uri = uri, .headers = headers, .body = body, }; } fn parseMethod(reader: anytype) !Method { var buf: [8]u8 = undefined; const str = reader.readUntilDelimiter(&buf, ' ') catch |err| switch (err) { error.StreamTooLong => return error.MethodNotImplemented, else => return err, }; inline for (@typeInfo(Method).Enum.fields) |method| { if (std.mem.eql(u8, method.name, str)) { return @intToEnum(Method, method.value); } } return error.MethodNotImplemented; } fn parseProto(reader: anytype) !Request.Protocol { var buf: [8]u8 = undefined; const proto = reader.readUntilDelimiter(&buf, '/') catch |err| switch (err) { error.StreamTooLong => return error.UnknownProtocol, else => return err, }; if (!std.mem.eql(u8, proto, "HTTP")) { return error.UnknownProtocol; } const count = try reader.read(buf[0..3]); if (count != 3 or buf[1] != '.') { return error.BadRequest; } if (buf[0] != '1') return error.HttpVersionNotSupported; return switch (buf[2]) { '0' => .http_1_0, '1' => .http_1_1, else => .http_1_x, }; } fn parseHeaders(allocator: std.mem.Allocator, reader: anytype) !Fields { var headers = Fields.init(allocator); var buf: [4096]u8 = undefined; while (true) { const full_line = reader.readUntilDelimiter(&buf, '\n') catch |err| switch (err) { error.StreamTooLong => return error.HeaderLineTooLong, else => return err, }; const line = std.mem.trimRight(u8, full_line, "\r"); if (line.len == 0) break; const name = std.mem.sliceTo(line, ':'); if (!isTokenValid(name)) return error.BadRequest; if (name.len == line.len) return error.BadRequest; const value = std.mem.trim(u8, line[name.len + 1 ..], " \t"); try headers.append(name, value); } return headers; } fn isTokenValid(token: []const u8) bool { if (token.len == 0) return false; for (token) |ch| { switch (ch) { '"', '(', ')', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}' => return false, '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~' => {}, else => if (!std.ascii.isAlphanumeric(ch)) return false, } } return true; } fn readBody(alloc: std.mem.Allocator, headers: Fields, reader: anytype) !?[]const u8 { const xfer_encoding = try parseEncoding(headers.get("Transfer-Encoding")); if (xfer_encoding != .identity) return error.UnsupportedMediaType; const content_encoding = try parseEncoding(headers.get("Content-Encoding")); if (content_encoding != .identity) return error.UnsupportedMediaType; const len_str = headers.get("Content-Length") orelse return null; const len = std.fmt.parseInt(usize, len_str, 10) catch return error.BadRequest; if (len > max_body_len) return error.RequestEntityTooLarge; const body = try alloc.alloc(u8, len); errdefer alloc.free(body); reader.readNoEof(body) catch return error.BadRequest; return body; } // TODO: assumes that there's only one encoding, not layered encodings fn parseEncoding(encoding: ?[]const u8) !Encoding { if (encoding == null) return .identity; if (std.mem.eql(u8, encoding.?, "identity")) return .identity; if (std.mem.eql(u8, encoding.?, "chunked")) return .chunked; return error.UnsupportedMediaType; } pub fn parseFree(allocator: std.mem.Allocator, request: *Request) void { allocator.free(request.uri); request.headers.deinit(); if (request.body) |body| allocator.free(body); }