diff --git a/src/browser/webapi/storage/Cookie.zig b/src/browser/webapi/storage/Cookie.zig index da499efb..649a04cc 100644 --- a/src/browser/webapi/storage/Cookie.zig +++ b/src/browser/webapi/storage/Cookie.zig @@ -27,6 +27,10 @@ const public_suffix_list = @import("../../../data/public_suffix_list.zig").looku const Cookie = @This(); +const max_cookie_size = 4 * 1024; +const max_cookie_header_size = 8 * 1024; +const max_jar_size = 1024; + arena: ArenaAllocator, name: []const u8, value: []const u8, @@ -62,6 +66,10 @@ pub fn deinit(self: *const Cookie) void { // Duplicate attributes - use the last valid // Value-less attributes with a value? Ignore the value pub fn parse(allocator: Allocator, url: [:0]const u8, str: []const u8) !Cookie { + if (str.len > max_cookie_header_size) { + return error.CookieHeaderSizeExceeded; + } + try validateCookieString(str); const cookie_name, const cookie_value, const rest = parseNameValue(str) catch { @@ -119,6 +127,10 @@ pub fn parse(allocator: Allocator, url: [:0]const u8, str: []const u8) !Cookie { return error.InsecureSameSite; } + if (cookie_value.len > max_cookie_size) { + return error.CookieSizeExceeded; + } + var arena = ArenaAllocator.init(allocator); errdefer arena.deinit(); const aa = arena.allocator(); @@ -415,6 +427,13 @@ pub const Jar = struct { cookie.deinit(); }; + if (self.cookies.items.len >= max_jar_size) { + return error.CookieJarQuotaExceeded; + } + if (cookie.value.len > max_cookie_size) { + return error.CookieSizeExceeded; + } + for (self.cookies.items, 0..) |*c, i| { if (areCookiesEqual(&cookie, c)) { c.deinit(); @@ -635,6 +654,57 @@ test "Jar: add" { try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" }, .{ "over", "9002" } }, jar); } +test "Jar: add limit" { + var jar = Jar.init(testing.allocator); + defer jar.deinit(); + + const now = std.time.timestamp(); + + // add a too big cookie value. + try testing.expectError(error.CookieSizeExceeded, jar.add(.{ + .arena = std.heap.ArenaAllocator.init(testing.allocator), + .name = "v", + .domain = "lightpanda.io", + .path = "/", + .expires = null, + .value = "v" ** 4096 ++ "v", + }, now)); + + // generate unique names. + const names = comptime blk: { + @setEvalBranchQuota(max_jar_size); + var result: [max_jar_size][]const u8 = undefined; + for (0..max_jar_size) |i| { + result[i] = "v" ** i; + } + break :blk result; + }; + + // test the max number limit + var i: usize = 0; + while (i < max_jar_size) : (i += 1) { + const c = Cookie{ + .arena = std.heap.ArenaAllocator.init(testing.allocator), + .name = names[i], + .domain = "lightpanda.io", + .path = "/", + .expires = null, + .value = "v", + }; + + try jar.add(c, now); + } + + try testing.expectError(error.CookieJarQuotaExceeded, jar.add(.{ + .arena = std.heap.ArenaAllocator.init(testing.allocator), + .name = "last", + .domain = "lightpanda.io", + .path = "/", + .expires = null, + .value = "v", + }, now)); +} + test "Jar: forRequest" { const expectCookies = struct { fn expect(expected: []const u8, jar: *Jar, target_url: [:0]const u8, opts: Jar.LookupOpts) !void { @@ -959,6 +1029,11 @@ test "Cookie: parse domain" { try expectError(error.InvalidDomain, "http://lightpanda.io/", "b;domain=other.example.com"); } +test "Cookie: parse limit" { + try expectError(error.CookieHeaderSizeExceeded, "http://lightpanda.io/", "v" ** 8192 ++ ";domain=lightpanda.io"); + try expectError(error.CookieSizeExceeded, "http://lightpanda.io/", "v" ** 4096 ++ "v;domain=lightpanda.io"); +} + const ExpectedCookie = struct { name: []const u8, value: []const u8,