srctree

Gregory Mullen parent b12f07b8 178c8ee5
start conversion of request headers to be static

started because websockets, but got distracted
src/auth/cookie.zig added: 131, removed: 77, total 54
@@ -117,7 +117,7 @@ pub fn CookieAuth(HMAC: type) type {
return base.authenticate(headers);
}
 
if (headers.get("Cookie")) |cookies| {
if (headers.getCustom("Cookie")) |cookies| {
// This actually isn't technically invalid, it's only
// currently not implemented.
if (cookies.value_list.next != null) return error.InvalidAuth;
 
src/auth/mtls.zig added: 131, removed: 77, total 54
@@ -14,7 +14,7 @@ pub const MTLS = @This();
pub fn authenticate(ptr: *anyopaque, headers: *const Headers) Error!User {
const mtls: *MTLS = @ptrCast(@alignCast(ptr));
var success: bool = false;
if (headers.get("MTLS_ENABLED")) |enabled| {
if (headers.getCustom("MTLS_ENABLED")) |enabled| {
if (enabled.value_list.next) |_| return error.InvalidAuth;
// MTLS validation as currently supported here is done by the
// reverse proxy. Constant time compare would provide no security
@@ -27,7 +27,7 @@ pub fn authenticate(ptr: *anyopaque, headers: *const Headers) Error!User {
if (!success) return error.UnknownUser;
 
if (mtls.base) |base| {
if (headers.get("MTLS_FINGERPRINT")) |enabled| {
if (headers.getCustom("MTLS_FINGERPRINT")) |enabled| {
// Verse does not specify an order for which is valid so it is
// an error if there is ever more than a single value for the
// mTLS fingerprint
@@ -70,21 +70,21 @@ test MTLS {
 
var headers = Headers.init(a);
defer headers.raze();
try headers.add("MTLS_ENABLED", "SUCCESS");
try headers.add("MTLS_FINGERPRINT", "LOLTOTALLYVALID");
try headers.addCustom("MTLS_ENABLED", "SUCCESS");
try headers.addCustom("MTLS_FINGERPRINT", "LOLTOTALLYVALID");
 
const user = try provider_.authenticate(&headers);
 
try std.testing.expectEqual(null, user.user_ptr);
 
try headers.add("MTLS_ENABLED", "SUCCESS");
try headers.addCustom("MTLS_ENABLED", "SUCCESS");
const err = provider_.authenticate(&headers);
try std.testing.expectError(error.InvalidAuth, err);
 
headers.raze();
headers = Headers.init(a);
 
try headers.add("MTLS_ENABLED", "FAILURE!");
try headers.addCustom("MTLS_ENABLED", "FAILURE!");
const err2 = provider_.authenticate(&headers);
try std.testing.expectError(error.UnknownUser, err2);
// TODO there's likely a few more error states we should validate;
 
src/frame.zig added: 131, removed: 77, total 54
@@ -281,7 +281,7 @@ pub fn sendError(vrs: *Frame, comptime code: std.http.Status) !void {
}
 
pub fn headersAdd(vrs: *Frame, comptime name: []const u8, value: []const u8) !void {
try vrs.headers.add(name, value);
try vrs.headers.addCustom(name, value);
}
 
const ONESHOT_SIZE = 14720;
 
src/headers.zig added: 131, removed: 77, total 54
@@ -1,8 +1,17 @@
alloc: Allocator,
headers: HeaderMap,
known: KnownMap,
custom: CustomMap,
 
const Headers = @This();
 
pub const KnownHeaders = enum {
accept,
accept_encoding,
host,
user_agent,
cookies,
};
 
pub const Header = struct {
name: []const u8,
value: []const u8,
@@ -19,17 +28,19 @@ const ValueList = struct {
next: ?*ValueList = null,
};
 
const HeaderMap = std.StringArrayHashMap(*ValueList);
const KnownMap = EnumMap(KnownHeaders, []const u8);
const CustomMap = std.StringArrayHashMap(*ValueList);
 
pub fn init(a: Allocator) Headers {
return .{
.alloc = a,
.headers = HeaderMap.init(a),
.known = KnownMap{},
.custom = CustomMap.init(a),
};
}
 
pub fn raze(h: *Headers) void {
const values = h.headers.values();
const values = h.custom.values();
for (values) |val| {
var next: ?*ValueList = val.*.next;
h.alloc.destroy(val);
@@ -39,16 +50,16 @@ pub fn raze(h: *Headers) void {
h.alloc.destroy(destroy);
}
}
h.headers.deinit();
h.custom.deinit();
}
 
fn normalize(_: []const u8) !void {
comptime unreachable;
}
 
pub fn add(h: *Headers, name: []const u8, value: []const u8) !void {
pub fn addCustom(h: *Headers, name: []const u8, value: []const u8) !void {
// TODO normalize lower
const gop = try h.headers.getOrPut(name);
const gop = try h.custom.getOrPut(name);
if (gop.found_existing) {
var end: *ValueList = gop.value_ptr.*;
while (end.*.next != null) {
@@ -64,8 +75,8 @@ pub fn add(h: *Headers, name: []const u8, value: []const u8) !void {
}
}
 
pub fn get(h: *const Headers, name: []const u8) ?HeaderList {
if (h.headers.get(name)) |header| {
pub fn getCustom(h: *const Headers, name: []const u8) ?HeaderList {
if (h.custom.get(name)) |header| {
return .{
.name = name,
.value_list = header,
@@ -81,16 +92,16 @@ pub fn iterator(h: *Headers) Iterator {
 
pub const Iterator = struct {
header: *Headers,
inner: HeaderMap.Iterator,
entry: ?HeaderMap.Entry = null,
inner: CustomMap.Iterator,
entry: ?CustomMap.Entry = null,
current: ?*ValueList = null,
current_name: ?[]const u8 = null,
 
pub fn init(h: *Headers) Iterator {
h.headers.lockPointers();
h.custom.lockPointers();
return .{
.header = h,
.inner = h.headers.iterator(),
.inner = h.custom.iterator(),
};
}
 
@@ -108,7 +119,7 @@ pub const Iterator = struct {
i.current = entry.value_ptr.*;
i.current_name = entry.key_ptr.*;
} else {
i.header.headers.unlockPointers();
i.header.custom.unlockPointers();
return null;
}
return i.next();
@@ -136,7 +147,7 @@ pub fn toSlice(h: Headers, a: Allocator) ![]Header {
 
pub fn format(h: Headers, comptime fmts: []const u8, _: std.fmt.FormatOptions, out: anytype) !void {
comptime if (fmts.len > 0) @compileError("Header format string must be empty");
var iter = h.headers.iterator();
var iter = h.custom.iterator();
 
while (iter.next()) |next| {
var old: ?*ValueList = next.value_ptr.*;
@@ -151,19 +162,20 @@ test Headers {
const a = std.testing.allocator;
var hmap = init(a);
defer hmap.raze();
try hmap.add("first", "1");
try hmap.add("first", "2");
try hmap.add("first", "3");
try hmap.add("second", "4");
try hmap.addCustom("first", "1");
try hmap.addCustom("first", "2");
try hmap.addCustom("first", "3");
try hmap.addCustom("second", "4");
 
try std.testing.expectEqual(2, hmap.headers.count());
const first = hmap.headers.get("first");
try std.testing.expectEqual(2, hmap.custom.count());
const first = hmap.custom.get("first");
try std.testing.expectEqualStrings(first.?.value, "1");
try std.testing.expectEqualStrings(first.?.next.?.value, "2");
try std.testing.expectEqualStrings(first.?.next.?.next.?.value, "3");
const second = hmap.headers.get("second");
const second = hmap.custom.get("second");
try std.testing.expectEqualStrings(second.?.value, "4");
}
 
const std = @import("std");
const Allocator = std.mem.Allocator;
const EnumMap = std.EnumMap;
 
src/request.zig added: 131, removed: 77, total 54
@@ -6,12 +6,12 @@ pub const Data = @import("request-data.zig");
remote_addr: RemoteAddr,
method: Methods,
uri: []const u8,
host: ?[]const u8,
host: ?Host,
user_agent: ?UserAgent,
referer: ?[]const u8,
referer: ?Referer,
accept: ?Accept,
accept_encoding: Encoding = Encoding.default,
authorization: ?[]const u8,
authorization: ?Authorization,
 
headers: Headers,
/// Default API, still unstable, but unlike to drastically change
@@ -84,10 +84,43 @@ pub const Methods = enum(u8) {
}
};
 
fn initCommon(
a: Allocator,
remote_addr: RemoteAddr,
method: Methods,
uri: []const u8,
host: ?Host,
agent: ?UserAgent,
referer: ?Referer,
accept: ?Accept,
accept_encoding: Encoding,
authorization: ?Authorization,
headers: Headers,
cookies: ?[]const u8,
data: Data,
raw: RawReq,
) !Request {
return .{
.accept = accept,
.accept_encoding = accept_encoding,
.authorization = authorization,
.cookie_jar = if (cookies) |ch| try Cookies.Jar.initFromHeader(a, ch) else try Cookies.Jar.init(a),
.data = data,
.headers = headers,
.host = host,
.method = method,
.raw = raw,
.referer = referer,
.remote_addr = remote_addr,
.uri = uri,
.user_agent = agent,
};
}
 
pub fn initZWSGI(a: Allocator, zwsgi: *zWSGIRequest, data: Data) !Request {
var uri: ?[]const u8 = null;
var method: Methods = Methods.GET;
var remote_addr: RemoteAddr = undefined;
var method: ?Methods = null;
var remote_addr: ?RemoteAddr = null;
var headers = Headers.init(a);
var accept: ?Accept = null;
var host: ?Host = null;
@@ -98,11 +131,14 @@ pub fn initZWSGI(a: Allocator, zwsgi: *zWSGIRequest, data: Data) !Request {
var cookie_header: ?[]const u8 = null;
 
for (zwsgi.vars) |v| {
try headers.add(v.key, v.val);
try headers.addCustom(v.key, v.val);
if (eql(u8, v.key, "PATH_INFO")) {
uri = v.val;
} else if (eql(u8, v.key, "REQUEST_METHOD")) {
method = Methods.fromStr(v.val) catch Methods.GET;
method = Methods.fromStr(v.val) catch {
std.debug.print("Unsupported Method seen '{any}'", .{v.val});
return error.InvalidRequest;
};
} else if (eql(u8, v.key, "REMOTE_ADDR")) {
remote_addr = v.val;
} else if (eqlIgnoreCase("HTTP_ACCEPT", v.key)) {
@@ -121,20 +157,23 @@ pub fn initZWSGI(a: Allocator, zwsgi: *zWSGIRequest, data: Data) !Request {
cookie_header = v.val;
}
}
return .{
.remote_addr = remote_addr,
.host = host,
.user_agent = uagent,
.accept = accept,
.authorization = authorization,
.referer = referer,
.method = Methods.GET,
.uri = uri orelse return error.InvalidRequest,
.headers = headers,
.cookie_jar = if (cookie_header) |ch| try Cookies.Jar.initFromHeader(a, ch) else try Cookies.Jar.init(a),
.data = data,
.raw = .{ .zwsgi = zwsgi },
};
 
return initCommon(
a,
remote_addr orelse return error.InvalidRequest,
method orelse return error.InvalidRequest,
uri orelse return error.InvalidRequest,
host,
uagent,
referer,
accept,
encoding,
authorization,
headers,
cookie_header,
data,
.{ .zwsgi = zwsgi },
);
}
 
pub fn initHttp(a: Allocator, http: *std.http.Server.Request, data: Data) !Request {
@@ -150,7 +189,7 @@ pub fn initHttp(a: Allocator, http: *std.http.Server.Request, data: Data) !Reque
var cookie_header: ?[]const u8 = null;
 
while (itr.next()) |head| {
try headers.add(head.name, head.value);
try headers.addCustom(head.name, head.value);
if (eqlIgnoreCase("accept", head.name)) {
accept = head.value;
} else if (eqlIgnoreCase("host", head.name)) {
@@ -169,27 +208,29 @@ pub fn initHttp(a: Allocator, http: *std.http.Server.Request, data: Data) !Reque
}
 
var remote_addr: RemoteAddr = undefined;
const ipport = try allocPrint(a, "{}", .{http.server.connection.address});
if (indexOfScalar(u8, ipport, ':')) |i| {
remote_addr = ipport[0..i];
try headers.add("REMOTE_ADDR", remote_addr);
try headers.add("REMOTE_PORT", ipport[i + 1 ..]);
var ipbuf: [48]u8 = undefined;
const ipport = try bufPrint(&ipbuf, "{}", .{http.server.connection.address});
if (lastIndexOfScalar(u8, ipport, ':')) |i| {
// TODO lower this to remove the a.dupe
remote_addr = try a.dupe(u8, ipport[0..i]);
} else @panic("invalid address from http server");
 
return .{
.remote_addr = remote_addr,
.host = host,
.user_agent = uagent,
.accept = accept,
.authorization = authorization,
.referer = referer,
.method = translateStdHttp(http.head.method),
.uri = http.head.target,
.headers = headers,
.cookie_jar = if (cookie_header) |ch| try Cookies.Jar.initFromHeader(a, ch) else try Cookies.Jar.init(a),
.data = data,
.raw = .{ .http = http },
};
return initCommon(
a,
remote_addr,
translateStdHttp(http.head.method),
http.head.target,
host,
uagent,
referer,
accept,
encoding,
authorization,
headers,
cookie_header,
data,
.{ .http = http },
);
}
 
fn translateStdHttp(m: std.http.Method) Methods {
@@ -223,7 +264,8 @@ const zWSGIRequest = @import("zwsgi.zig").zWSGIRequest;
const std = @import("std");
const Allocator = std.mem.Allocator;
const indexOf = std.mem.indexOf;
const indexOfScalar = std.mem.indexOfScalar;
const lastIndexOfScalar = std.mem.lastIndexOfScalar;
const eql = std.mem.eql;
const eqlIgnoreCase = std.ascii.eqlIgnoreCase;
const allocPrint = std.fmt.allocPrint;
const bufPrint = std.fmt.bufPrint;