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;
|
|
|
|
|
|
|
|
fn ParseError(comptime Reader: type) type {
|
|
|
|
return error{
|
|
|
|
MethodNotImplemented,
|
|
|
|
} | Reader.ReadError;
|
|
|
|
}
|
|
|
|
|
|
|
|
const Encoding = enum {
|
|
|
|
identity,
|
|
|
|
chunked,
|
|
|
|
};
|
|
|
|
|
2022-11-05 10:09:59 +00:00
|
|
|
pub fn parse(alloc: std.mem.Allocator, reader: anytype) !Request(@TypeOf(reader)) {
|
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()) {
|
2022-11-10 09:53:09 +00:00
|
|
|
'\r' => if ((try reader.readByte()) != '\n') return error.BadRequest,
|
2022-11-05 07:26:53 +00:00
|
|
|
'\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
|
|
|
|
2022-11-07 07:38:21 +00:00
|
|
|
const body = try prepareBody(headers, reader);
|
|
|
|
if (body != null and !method.requestHasBody()) return error.BadRequest;
|
2022-10-13 09:23:57 +00:00
|
|
|
|
2022-11-05 10:09:59 +00:00
|
|
|
return Request(@TypeOf(reader)){
|
2022-10-13 09:23:57 +00:00
|
|
|
.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-11-05 10:09:59 +00:00
|
|
|
fn parseProto(reader: anytype) !http.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-27 13:43:06 +00:00
|
|
|
pub fn parseHeaders(allocator: std.mem.Allocator, reader: anytype) !Fields {
|
2022-11-05 07:26:53 +00:00
|
|
|
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,
|
|
|
|
};
|
2022-11-21 08:54:03 +00:00
|
|
|
const line = if (full_line.len != 0 and full_line[full_line.len - 1] == '\r')
|
|
|
|
full_line[0 .. full_line.len - 1]
|
|
|
|
else
|
|
|
|
full_line;
|
2022-11-05 07:26:53 +00:00
|
|
|
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-21 08:54:03 +00:00
|
|
|
const encoded_value = line[name.len + 1 ..];
|
|
|
|
const decoded_value = blk: {
|
|
|
|
var ii: usize = 0;
|
|
|
|
var io: usize = 0;
|
|
|
|
while (ii < encoded_value.len) : ({
|
|
|
|
ii += 1;
|
|
|
|
io += 1;
|
|
|
|
}) {
|
|
|
|
switch (encoded_value[ii]) {
|
|
|
|
'\r', '\n', 0 => return error.BadRequest,
|
|
|
|
else => {},
|
|
|
|
}
|
|
|
|
|
|
|
|
if (encoded_value[ii] == '%') {
|
|
|
|
if (encoded_value.len < ii + 2) return error.BadRequest;
|
|
|
|
|
|
|
|
const ch_buf = [2]u8{ encoded_value[ii + 1], encoded_value[ii + 2] };
|
|
|
|
encoded_value[io] = try std.fmt.parseInt(u8, &ch_buf, 16);
|
|
|
|
ii += 2;
|
|
|
|
} else {
|
|
|
|
encoded_value[io] = encoded_value[ii];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break :blk encoded_value[0..io];
|
|
|
|
};
|
2022-07-09 20:09:30 +00:00
|
|
|
|
2022-11-21 08:54:03 +00:00
|
|
|
const val = std.mem.trim(u8, decoded_value, " \t");
|
|
|
|
try headers.append(name, val);
|
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-07 07:38:21 +00:00
|
|
|
fn prepareBody(headers: Fields, reader: anytype) !?TransferStream(@TypeOf(reader)) {
|
|
|
|
const hdr = headers.get("Transfer-Encoding");
|
|
|
|
// TODO:
|
|
|
|
// if (hder != null and protocol == .http_1_0) return error.BadRequest;
|
|
|
|
const xfer_encoding = try parseEncoding(hdr);
|
2022-07-09 20:09:30 +00:00
|
|
|
const content_encoding = try parseEncoding(headers.get("Content-Encoding"));
|
|
|
|
if (content_encoding != .identity) return error.UnsupportedMediaType;
|
|
|
|
|
2022-11-07 07:38:21 +00:00
|
|
|
switch (xfer_encoding) {
|
|
|
|
.identity => {
|
|
|
|
const len_str = headers.get("Content-Length") orelse return null;
|
|
|
|
const len = std.fmt.parseInt(usize, len_str, 10) catch return error.BadRequest;
|
|
|
|
|
|
|
|
return TransferStream(@TypeOf(reader)){ .underlying = .{ .identity = std.io.limitedReader(reader, len) } };
|
|
|
|
},
|
|
|
|
.chunked => {
|
|
|
|
if (headers.get("Content-Length") != null) return error.BadRequest;
|
|
|
|
return TransferStream(@TypeOf(reader)){
|
|
|
|
.underlying = .{
|
2022-11-07 08:25:33 +00:00
|
|
|
.chunked = try ChunkedStream(@TypeOf(reader)).init(reader),
|
2022-11-07 07:38:21 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ChunkedStream(comptime R: type) type {
|
|
|
|
return struct {
|
|
|
|
const Self = @This();
|
|
|
|
|
2022-11-07 08:25:33 +00:00
|
|
|
remaining: ?usize = 0,
|
2022-11-07 07:38:21 +00:00
|
|
|
underlying: R,
|
|
|
|
|
|
|
|
const Error = R.Error || error{ Unexpected, InvalidChunkHeader, StreamTooLong, EndOfStream };
|
2022-11-07 08:25:33 +00:00
|
|
|
fn init(reader: R) !Self {
|
|
|
|
var self: Self = .{ .underlying = reader };
|
|
|
|
return self;
|
|
|
|
}
|
2022-11-07 07:38:21 +00:00
|
|
|
|
2022-11-07 08:25:33 +00:00
|
|
|
fn read(self: *Self, buf: []u8) !usize {
|
|
|
|
var count: usize = 0;
|
|
|
|
while (true) {
|
|
|
|
if (count == buf.len) return count;
|
|
|
|
if (self.remaining == null) return count;
|
|
|
|
if (self.remaining.? == 0) self.remaining = try self.readChunkHeader();
|
|
|
|
|
|
|
|
const max_read = std.math.min(buf.len, self.remaining.?);
|
|
|
|
const amt = try self.underlying.read(buf[count .. count + max_read]);
|
|
|
|
if (amt != max_read) return error.EndOfStream;
|
|
|
|
count += amt;
|
|
|
|
self.remaining.? -= amt;
|
|
|
|
if (self.remaining.? == 0) {
|
|
|
|
var crlf: [2]u8 = undefined;
|
|
|
|
_ = try self.underlying.readUntilDelimiter(&crlf, '\n');
|
2022-11-07 07:38:21 +00:00
|
|
|
self.remaining = try self.readChunkHeader();
|
|
|
|
}
|
|
|
|
|
2022-11-07 08:25:33 +00:00
|
|
|
if (count == buf.len) return count;
|
|
|
|
}
|
2022-11-07 07:38:21 +00:00
|
|
|
}
|
2022-07-09 20:09:30 +00:00
|
|
|
|
2022-11-07 07:38:21 +00:00
|
|
|
fn readChunkHeader(self: *Self) !?usize {
|
|
|
|
// TODO: Pick a reasonable limit for this
|
|
|
|
var buf = std.mem.zeroes([10]u8);
|
|
|
|
const line = self.underlying.readUntilDelimiter(&buf, '\n') catch |err| {
|
|
|
|
return if (err == error.StreamTooLong) error.InvalidChunkHeader else err;
|
|
|
|
};
|
|
|
|
if (line.len < 2 or line[line.len - 1] != '\r') return error.InvalidChunkHeader;
|
2022-07-09 20:09:30 +00:00
|
|
|
|
2022-11-07 07:38:21 +00:00
|
|
|
const size = std.fmt.parseInt(usize, line[0 .. line.len - 1], 16) catch return error.InvalidChunkHeader;
|
|
|
|
|
|
|
|
return if (size != 0) size else null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn TransferStream(comptime R: type) type {
|
|
|
|
return struct {
|
|
|
|
const Error = R.Error || ChunkedStream(R).Error;
|
|
|
|
const Reader = std.io.Reader(*@This(), Error, read);
|
|
|
|
|
|
|
|
underlying: union(enum) {
|
|
|
|
identity: std.io.LimitedReader(R),
|
|
|
|
chunked: ChunkedStream(R),
|
|
|
|
},
|
|
|
|
|
|
|
|
pub fn read(self: *@This(), buf: []u8) Error!usize {
|
|
|
|
return switch (self.underlying) {
|
|
|
|
.identity => |*r| try r.read(buf),
|
|
|
|
.chunked => |*r| try r.read(buf),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reader(self: *@This()) Reader {
|
|
|
|
return .{ .context = self };
|
|
|
|
}
|
|
|
|
};
|
2022-07-09 20:09:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|