fediglam/src/http/request/parser.zig

168 lines
4.9 KiB
Zig
Raw Normal View History

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;
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) {
error.StreamTooLong => return error.RequestUriTooLong,
else => return err,
};
2022-10-13 09:23:57 +00:00
errdefer alloc.free(uri);
2022-10-13 09:23:57 +00:00
const proto = try parseProto(reader);
// 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,
};
}
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 {
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-11-05 07:26:53 +00:00
fn parseHeaders(allocator: std.mem.Allocator, reader: anytype) !Fields {
var headers = Fields.init(allocator);
2022-11-05 07:26:53 +00:00
var buf: [4096]u8 = undefined;
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-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-11-05 07:26:53 +00:00
const value = std.mem.trim(u8, line[name.len + 1 ..], " \t");
2022-11-05 08:54:00 +00:00
try headers.append(name, value);
}
2022-11-05 07:26:53 +00:00
return headers;
}
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-11-05 07:26:53 +00:00
'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~' => {},
else => if (!std.ascii.isAlphanumeric(ch)) return false,
}
}
2022-11-05 07:26:53 +00:00
return true;
}
2022-11-05 07:26:53 +00:00
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;
}
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);
}