2022-07-09 20:09:30 +00:00
|
|
|
const std = @import("std");
|
|
|
|
const http = @import("../lib.zig");
|
|
|
|
|
|
|
|
const Method = http.Method;
|
2022-11-05 07:26:53 +00:00
|
|
|
const Fields = http.Fields;
|
2022-07-09 20:09:30 +00:00
|
|
|
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
pub fn parse(alloc: std.mem.Allocator, reader: anytype) !Request {
|
2022-10-13 09:23:57 +00:00
|
|
|
const method = try parseMethod(reader);
|
|
|
|
const uri = reader.readUntilDelimiterAlloc(alloc, ' ', max_path_len) catch |err| switch (err) {
|
2022-07-09 20:09:30 +00:00
|
|
|
error.StreamTooLong => return error.RequestUriTooLong,
|
|
|
|
else => return err,
|
|
|
|
};
|
2022-10-13 09:23:57 +00:00
|
|
|
errdefer alloc.free(uri);
|
2022-07-09 20:09:30 +00:00
|
|
|
|
2022-10-13 09:23:57 +00:00
|
|
|
const proto = try parseProto(reader);
|
2022-07-09 20:09:30 +00:00
|
|
|
|
|
|
|
// discard \r\n
|
2022-11-05 07:26:53 +00:00
|
|
|
switch (try reader.readByte()) {
|
|
|
|
'\r' => if (try reader.readByte() != '\n') return error.BadRequest,
|
|
|
|
'\n' => {},
|
|
|
|
else => return error.BadRequest,
|
|
|
|
}
|
2022-10-13 09:23:57 +00:00
|
|
|
|
|
|
|
var headers = try parseHeaders(alloc, reader);
|
2022-11-05 07:26:53 +00:00
|
|
|
errdefer headers.deinit();
|
2022-10-13 09:23:57 +00:00
|
|
|
|
|
|
|
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,
|
|
|
|
};
|
2022-07-09 20:09:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-13 09:23:57 +00:00
|
|
|
fn parseProto(reader: anytype) !Request.Protocol {
|
2022-07-09 20:09:30 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-13 09:23:57 +00:00
|
|
|
if (buf[0] != '1') return error.HttpVersionNotSupported;
|
|
|
|
return switch (buf[2]) {
|
|
|
|
'0' => .http_1_0,
|
|
|
|
'1' => .http_1_1,
|
2022-11-05 07:26:53 +00:00
|
|
|
else => .http_1_x,
|
2022-10-13 09:23:57 +00:00
|
|
|
};
|
2022-07-09 20:09:30 +00:00
|
|
|
}
|
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
fn parseHeaders(allocator: std.mem.Allocator, reader: anytype) !Fields {
|
|
|
|
var headers = Fields.init(allocator);
|
2022-07-09 20:09:30 +00:00
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
var buf: [4096]u8 = undefined;
|
2022-07-09 20:09:30 +00:00
|
|
|
while (true) {
|
2022-11-05 07:26:53 +00:00
|
|
|
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;
|
2022-07-09 20:09:30 +00:00
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
const name = std.mem.sliceTo(line, ':');
|
|
|
|
if (!isTokenValid(name)) return error.BadRequest;
|
|
|
|
if (name.len == line.len) return error.BadRequest;
|
2022-07-09 20:09:30 +00:00
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
const value = std.mem.trim(u8, line[name.len + 1 ..], " \t");
|
2022-07-09 20:09:30 +00:00
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
try headers.put(name, value);
|
2022-07-09 20:09:30 +00:00
|
|
|
}
|
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
return headers;
|
2022-07-09 20:09:30 +00:00
|
|
|
}
|
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
fn isTokenValid(token: []const u8) bool {
|
|
|
|
if (token.len == 0) return false;
|
|
|
|
for (token) |ch| {
|
|
|
|
switch (ch) {
|
|
|
|
'"', '(', ')', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}' => return false,
|
2022-07-09 20:09:30 +00:00
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~' => {},
|
|
|
|
else => if (!std.ascii.isAlphanumeric(ch)) return false,
|
2022-07-09 20:09:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
return true;
|
2022-07-09 20:09:30 +00:00
|
|
|
}
|
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
fn readBody(alloc: std.mem.Allocator, headers: Fields, reader: anytype) !?[]const u8 {
|
2022-07-09 20:09:30 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-13 09:23:57 +00:00
|
|
|
pub fn parseFree(allocator: std.mem.Allocator, request: *Request) void {
|
|
|
|
allocator.free(request.uri);
|
2022-11-05 07:26:53 +00:00
|
|
|
request.headers.deinit();
|
2022-10-13 09:23:57 +00:00
|
|
|
if (request.body) |body| allocator.free(body);
|
|
|
|
}
|