2022-11-05 07:26:53 +00:00
|
|
|
const std = @import("std");
|
|
|
|
|
|
|
|
pub const Fields = struct {
|
|
|
|
const HashContext = struct {
|
|
|
|
const hash_seed = 1;
|
2022-11-07 07:59:35 +00:00
|
|
|
pub fn eql(_: @This(), lhs: []const u8, rhs: []const u8, _: usize) bool {
|
2022-11-05 07:26:53 +00:00
|
|
|
return std.ascii.eqlIgnoreCase(lhs, rhs);
|
|
|
|
}
|
2022-11-07 07:59:35 +00:00
|
|
|
pub fn hash(_: @This(), s: []const u8) u32 {
|
2022-11-05 07:26:53 +00:00
|
|
|
var h = std.hash.Wyhash.init(hash_seed);
|
|
|
|
for (s) |ch| {
|
|
|
|
const c = [1]u8{std.ascii.toLower(ch)};
|
|
|
|
h.update(&c);
|
|
|
|
}
|
2022-11-07 07:59:35 +00:00
|
|
|
return @truncate(u32, h.final());
|
2022-11-05 07:26:53 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-07 07:59:35 +00:00
|
|
|
const HashMap = std.ArrayHashMapUnmanaged(
|
2022-11-05 07:26:53 +00:00
|
|
|
[]const u8,
|
|
|
|
[]const u8,
|
|
|
|
HashContext,
|
2022-11-07 07:59:35 +00:00
|
|
|
true,
|
2022-11-05 07:26:53 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
unmanaged: HashMap,
|
|
|
|
allocator: std.mem.Allocator,
|
|
|
|
|
|
|
|
pub fn init(allocator: std.mem.Allocator) Fields {
|
|
|
|
return Fields{
|
|
|
|
.unmanaged = .{},
|
|
|
|
.allocator = allocator,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn deinit(self: *Fields) void {
|
|
|
|
var hash_iter = self.unmanaged.iterator();
|
|
|
|
while (hash_iter.next()) |entry| {
|
|
|
|
self.allocator.free(entry.key_ptr.*);
|
|
|
|
self.allocator.free(entry.value_ptr.*);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.unmanaged.deinit(self.allocator);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn iterator(self: Fields) HashMap.Iterator {
|
|
|
|
return self.unmanaged.iterator();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get(self: Fields, key: []const u8) ?[]const u8 {
|
|
|
|
return self.unmanaged.get(key);
|
|
|
|
}
|
|
|
|
|
2022-11-05 08:54:00 +00:00
|
|
|
pub const ListIterator = struct {
|
|
|
|
remaining: []const u8,
|
|
|
|
|
|
|
|
fn extractElement(self: *ListIterator) ?[]const u8 {
|
|
|
|
if (self.remaining.len == 0) return null;
|
|
|
|
|
|
|
|
var start: usize = 0;
|
|
|
|
var is_quoted = false;
|
|
|
|
const end = for (self.remaining) |ch, i| {
|
|
|
|
if (start == i and std.ascii.isWhitespace(ch)) {
|
|
|
|
start += 1;
|
|
|
|
} else if (ch == '"') {
|
|
|
|
is_quoted = !is_quoted;
|
|
|
|
}
|
|
|
|
if (ch == ',' and !is_quoted) {
|
|
|
|
break i;
|
|
|
|
}
|
|
|
|
} else self.remaining.len;
|
|
|
|
|
|
|
|
const str = self.remaining[start..end];
|
|
|
|
if (end == self.remaining.len) {
|
|
|
|
self.remaining = "";
|
|
|
|
} else {
|
|
|
|
self.remaining = self.remaining[end + 1 ..];
|
|
|
|
}
|
|
|
|
|
|
|
|
return std.mem.trim(u8, str, " \t");
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn next(self: *ListIterator) ?[]const u8 {
|
|
|
|
while (self.extractElement()) |elem| {
|
|
|
|
if (elem.len != 0) return elem;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-18 03:39:24 +00:00
|
|
|
pub fn getList(self: Fields, key: []const u8) ListIterator {
|
|
|
|
return if (self.unmanaged.get(key)) |hdr| ListIterator{ .remaining = hdr } else ListIterator{ .remaining = "" };
|
2022-11-05 08:54:00 +00:00
|
|
|
}
|
|
|
|
|
2022-11-05 07:26:53 +00:00
|
|
|
pub fn put(self: *Fields, key: []const u8, val: []const u8) !void {
|
|
|
|
const key_clone = try self.allocator.alloc(u8, key.len);
|
|
|
|
std.mem.copy(u8, key_clone, key);
|
|
|
|
errdefer self.allocator.free(key_clone);
|
|
|
|
|
|
|
|
const val_clone = try self.allocator.alloc(u8, val.len);
|
|
|
|
std.mem.copy(u8, val_clone, val);
|
|
|
|
errdefer self.allocator.free(val_clone);
|
|
|
|
|
|
|
|
if (try self.unmanaged.fetchPut(self.allocator, key_clone, val_clone)) |entry| {
|
2022-11-18 04:11:23 +00:00
|
|
|
self.allocator.free(key_clone);
|
|
|
|
//self.allocator.free(entry.key);
|
2022-11-05 07:26:53 +00:00
|
|
|
self.allocator.free(entry.value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn append(self: *Fields, key: []const u8, val: []const u8) !void {
|
|
|
|
if (self.unmanaged.getEntry(key)) |entry| {
|
|
|
|
const new_val = try std.mem.join(self.allocator, ", ", &.{ entry.value_ptr.*, val });
|
|
|
|
self.allocator.free(entry.value_ptr.*);
|
|
|
|
entry.value_ptr.* = new_val;
|
|
|
|
} else {
|
|
|
|
try self.put(key, val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn count(self: Fields) usize {
|
|
|
|
return self.unmanaged.count();
|
|
|
|
}
|
2022-11-18 03:39:24 +00:00
|
|
|
|
|
|
|
pub const CookieOptions = struct {
|
2022-11-18 04:11:23 +00:00
|
|
|
Secure: bool = true,
|
|
|
|
HttpOnly: bool = true,
|
2022-11-18 03:39:24 +00:00
|
|
|
SameSite: ?enum {
|
|
|
|
Strict,
|
|
|
|
Lax,
|
|
|
|
None,
|
|
|
|
} = null,
|
|
|
|
};
|
|
|
|
|
2022-11-18 06:51:51 +00:00
|
|
|
// TODO: Escape cookie values
|
2022-11-18 03:39:24 +00:00
|
|
|
pub fn setCookie(self: *Fields, name: []const u8, value: []const u8, opt: CookieOptions) !void {
|
2022-11-18 03:48:25 +00:00
|
|
|
const cookie = try std.fmt.allocPrint(
|
2022-11-18 03:39:24 +00:00
|
|
|
self.allocator,
|
2022-11-18 03:48:25 +00:00
|
|
|
"{s}={s}{s}{s}{s}{s}",
|
2022-11-18 03:39:24 +00:00
|
|
|
.{
|
|
|
|
name,
|
|
|
|
value,
|
|
|
|
if (opt.Secure) "; Secure" else "",
|
|
|
|
if (opt.HttpOnly) "; HttpOnly" else "",
|
2022-11-18 03:48:25 +00:00
|
|
|
if (opt.SameSite) |_| "; SameSite=" else "",
|
|
|
|
if (opt.SameSite) |same_site| @tagName(same_site) else "",
|
2022-11-18 03:39:24 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
defer self.allocator.free(cookie);
|
|
|
|
|
|
|
|
// TODO: reduce unnecessary allocations
|
2022-11-18 03:48:25 +00:00
|
|
|
try self.append("Set-Cookie", cookie);
|
2022-11-18 03:39:24 +00:00
|
|
|
}
|
2022-11-18 06:51:51 +00:00
|
|
|
|
|
|
|
// TODO: perform validation at request parse time?
|
|
|
|
pub fn getCookie(self: *Fields, name: []const u8) !?[]const u8 {
|
|
|
|
const hdr = self.get("Cookie") orelse return null;
|
|
|
|
var iter = std.mem.split(u8, hdr, ";");
|
|
|
|
while (iter.next()) |cookie| {
|
|
|
|
const trimmed = std.mem.trimLeft(u8, cookie, " ");
|
|
|
|
const cookie_name = std.mem.sliceTo(trimmed, '=');
|
|
|
|
if (std.mem.eql(u8, name, cookie_name)) {
|
|
|
|
const rest = trimmed[cookie_name.len..];
|
|
|
|
if (rest.len == 0) return error.InvalidCookie;
|
|
|
|
return rest[1..];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2022-11-05 07:26:53 +00:00
|
|
|
};
|