From 37340dc5493bc7d17512d60ffb759134013327e7 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 11 Apr 2025 12:19:11 +0200 Subject: [PATCH 1/4] dom: implement document.cookie --- src/html/document.zig | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/html/document.zig b/src/html/document.zig index 11d16c3e..fd4aae63 100644 --- a/src/html/document.zig +++ b/src/html/document.zig @@ -33,6 +33,9 @@ const Location = @import("location.zig").Location; const collection = @import("../dom/html_collection.zig"); const Walker = @import("../dom/walker.zig").WalkerDepthFirst; +const UserContext = @import("../user_context.zig").UserContext; +const Cookie = @import("../storage/cookie.zig").Cookie; + // WEB IDL https://html.spec.whatwg.org/#the-document-object pub const HTMLDocument = struct { pub const Self = parser.DocumentHTML; @@ -81,14 +84,18 @@ pub const HTMLDocument = struct { } } - // TODO: not implemented by libdom - pub fn get_cookie(_: *parser.DocumentHTML) ![]const u8 { - return error.NotImplemented; + pub fn get_cookie(_: *parser.DocumentHTML, allocator: std.mem.Allocator, userctx: UserContext) ![]const u8 { + var buf: std.ArrayListUnmanaged(u8) = .{}; + defer buf.deinit(allocator); + try userctx.cookie_jar.forRequest(&userctx.url.uri, buf.writer(allocator), .{ .navigation = true }); + return buf.toOwnedSlice(allocator); } - // TODO: not implemented by libdom - pub fn set_cookie(_: *parser.DocumentHTML, _: []const u8) ![]const u8 { - return error.NotImplemented; + pub fn set_cookie(_: *parser.DocumentHTML, allocator: std.mem.Allocator, userctx: UserContext, cookie_str: []const u8) ![]const u8 { + const c = try Cookie.parse(allocator, &userctx.url.uri, cookie_str); + try userctx.cookie_jar.add(c, std.time.timestamp()); + + return cookie_str; } pub fn get_title(self: *parser.DocumentHTML) ![]const u8 { @@ -258,4 +265,12 @@ pub fn testExecFn( .{ .src = "list.length", .ex = "1" }, }; try checkCases(js_env, &getElementsByName); + + var cookie = [_]Case{ + .{ .src = "document.cookie", .ex = "" }, + .{ .src = "document.cookie = 'name=Oeschger; SameSite=None; Secure'", .ex = "name=Oeschger; SameSite=None; Secure" }, + .{ .src = "document.cookie = 'favorite_food=tripe; SameSite=None; Secure'", .ex = "favorite_food=tripe; SameSite=None; Secure" }, + .{ .src = "document.cookie", .ex = "name=Oeschger; favorite_food=tripe" }, + }; + try checkCases(js_env, &cookie); } From c88bc653795fa54356f20427fd109d556bb757d1 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 11 Apr 2025 12:40:16 +0200 Subject: [PATCH 2/4] cookie: use a ; w/o space for cookie separator in requests --- src/storage/cookie.zig | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/storage/cookie.zig b/src/storage/cookie.zig index 63c56517..e8da2869 100644 --- a/src/storage/cookie.zig +++ b/src/storage/cookie.zig @@ -145,7 +145,7 @@ pub const Jar = struct { if (first) { first = false; } else { - try writer.writeAll(", "); + try writer.writeAll("; "); } try writeCookie(cookie, writer); } @@ -563,8 +563,8 @@ test "Jar: forRequest" { try jar.add(try Cookie.parse(testing.allocator, &test_uri_2, "domain1=9;domain=test.lightpanda.io"), now); // nothing fancy here - try expectCookies("global1=1, global2=2", &jar, test_uri, .{}); - try expectCookies("global1=1, global2=2", &jar, test_uri, .{ .origin_uri = &test_uri, .navigation = false }); + try expectCookies("global1=1; global2=2", &jar, test_uri, .{}); + try expectCookies("global1=1; global2=2", &jar, test_uri, .{ .origin_uri = &test_uri, .navigation = false }); // We have a cookie where Domain=lightpanda.io // This should _not_ match xyxlightpanda.io @@ -573,47 +573,47 @@ test "Jar: forRequest" { }); // matching path without trailing / - try expectCookies("global1=1, global2=2, path1=3", &jar, try std.Uri.parse("http://lightpanda.io/about"), .{ + try expectCookies("global1=1; global2=2; path1=3", &jar, try std.Uri.parse("http://lightpanda.io/about"), .{ .origin_uri = &test_uri, }); // incomplete prefix path - try expectCookies("global1=1, global2=2", &jar, try std.Uri.parse("http://lightpanda.io/abou"), .{ + try expectCookies("global1=1; global2=2", &jar, try std.Uri.parse("http://lightpanda.io/abou"), .{ .origin_uri = &test_uri, }); // path doesn't match - try expectCookies("global1=1, global2=2", &jar, try std.Uri.parse("http://lightpanda.io/aboutus"), .{ + try expectCookies("global1=1; global2=2", &jar, try std.Uri.parse("http://lightpanda.io/aboutus"), .{ .origin_uri = &test_uri, }); // path doesn't match cookie directory - try expectCookies("global1=1, global2=2", &jar, try std.Uri.parse("http://lightpanda.io/docs"), .{ + try expectCookies("global1=1; global2=2", &jar, try std.Uri.parse("http://lightpanda.io/docs"), .{ .origin_uri = &test_uri, }); // exact directory match - try expectCookies("global1=1, global2=2, path2=4", &jar, try std.Uri.parse("http://lightpanda.io/docs/"), .{ + try expectCookies("global1=1; global2=2; path2=4", &jar, try std.Uri.parse("http://lightpanda.io/docs/"), .{ .origin_uri = &test_uri, }); // sub directory match - try expectCookies("global1=1, global2=2, path2=4", &jar, try std.Uri.parse("http://lightpanda.io/docs/more"), .{ + try expectCookies("global1=1; global2=2; path2=4", &jar, try std.Uri.parse("http://lightpanda.io/docs/more"), .{ .origin_uri = &test_uri, }); // secure - try expectCookies("global1=1, global2=2, secure=5", &jar, try std.Uri.parse("https://lightpanda.io/"), .{ + try expectCookies("global1=1; global2=2; secure=5", &jar, try std.Uri.parse("https://lightpanda.io/"), .{ .origin_uri = &test_uri, }); // navigational cross domain, secure - try expectCookies("global1=1, global2=2, secure=5, sitenone=6, sitelax=7", &jar, try std.Uri.parse("https://lightpanda.io/x/"), .{ + try expectCookies("global1=1; global2=2; secure=5; sitenone=6; sitelax=7", &jar, try std.Uri.parse("https://lightpanda.io/x/"), .{ .origin_uri = &(try std.Uri.parse("https://example.com/")), }); // navigational cross domain, insecure - try expectCookies("global1=1, global2=2, sitelax=7", &jar, try std.Uri.parse("http://lightpanda.io/x/"), .{ + try expectCookies("global1=1; global2=2; sitelax=7", &jar, try std.Uri.parse("http://lightpanda.io/x/"), .{ .origin_uri = &(try std.Uri.parse("https://example.com/")), }); @@ -630,18 +630,18 @@ test "Jar: forRequest" { }); // non-navigational same origin - try expectCookies("global1=1, global2=2, sitelax=7, sitestrict=8", &jar, try std.Uri.parse("http://lightpanda.io/x/"), .{ + try expectCookies("global1=1; global2=2; sitelax=7; sitestrict=8", &jar, try std.Uri.parse("http://lightpanda.io/x/"), .{ .origin_uri = &(try std.Uri.parse("https://lightpanda.io/")), .navigation = false, }); // exact domain match + suffix - try expectCookies("global2=2, domain1=9", &jar, try std.Uri.parse("http://test.lightpanda.io/"), .{ + try expectCookies("global2=2; domain1=9", &jar, try std.Uri.parse("http://test.lightpanda.io/"), .{ .origin_uri = &test_uri, }); // domain suffix match + suffix - try expectCookies("global2=2, domain1=9", &jar, try std.Uri.parse("http://1.test.lightpanda.io/"), .{ + try expectCookies("global2=2; domain1=9", &jar, try std.Uri.parse("http://1.test.lightpanda.io/"), .{ .origin_uri = &test_uri, }); From 03101926608893ccb96724b147c218448f6ef09a Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 11 Apr 2025 14:27:08 +0200 Subject: [PATCH 3/4] dom: assume we are using an arena for cookie --- src/html/document.zig | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/html/document.zig b/src/html/document.zig index fd4aae63..3aa946aa 100644 --- a/src/html/document.zig +++ b/src/html/document.zig @@ -84,15 +84,14 @@ pub const HTMLDocument = struct { } } - pub fn get_cookie(_: *parser.DocumentHTML, allocator: std.mem.Allocator, userctx: UserContext) ![]const u8 { + pub fn get_cookie(_: *parser.DocumentHTML, arena: std.mem.Allocator, userctx: UserContext) ![]const u8 { var buf: std.ArrayListUnmanaged(u8) = .{}; - defer buf.deinit(allocator); - try userctx.cookie_jar.forRequest(&userctx.url.uri, buf.writer(allocator), .{ .navigation = true }); - return buf.toOwnedSlice(allocator); + try userctx.cookie_jar.forRequest(&userctx.url.uri, buf.writer(arena), .{ .navigation = true }); + return buf.items; } - pub fn set_cookie(_: *parser.DocumentHTML, allocator: std.mem.Allocator, userctx: UserContext, cookie_str: []const u8) ![]const u8 { - const c = try Cookie.parse(allocator, &userctx.url.uri, cookie_str); + pub fn set_cookie(_: *parser.DocumentHTML, arena: std.mem.Allocator, userctx: UserContext, cookie_str: []const u8) ![]const u8 { + const c = try Cookie.parse(arena, &userctx.url.uri, cookie_str); try userctx.cookie_jar.add(c, std.time.timestamp()); return cookie_str; From ee6382ef03bd7cd05e826b427dde5f0ce3d5581b Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 11 Apr 2025 16:23:03 +0200 Subject: [PATCH 4/4] dom: use cookie jar's allocator to parse cookie --- src/html/document.zig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/html/document.zig b/src/html/document.zig index 3aa946aa..19567078 100644 --- a/src/html/document.zig +++ b/src/html/document.zig @@ -90,8 +90,12 @@ pub const HTMLDocument = struct { return buf.items; } - pub fn set_cookie(_: *parser.DocumentHTML, arena: std.mem.Allocator, userctx: UserContext, cookie_str: []const u8) ![]const u8 { - const c = try Cookie.parse(arena, &userctx.url.uri, cookie_str); + pub fn set_cookie(_: *parser.DocumentHTML, userctx: UserContext, cookie_str: []const u8) ![]const u8 { + // we use the cookie jar's allocator to parse the cookie because it + // outlives the page's arena. + const c = try Cookie.parse(userctx.cookie_jar.allocator, &userctx.url.uri, cookie_str); + errdefer c.deinit(); + try userctx.cookie_jar.add(c, std.time.timestamp()); return cookie_str;