Skip to content

Commit e7b62d3

Browse files
committed
setCookies
1 parent 42bcc36 commit e7b62d3

File tree

2 files changed

+117
-12
lines changed

2 files changed

+117
-12
lines changed

src/browser/storage/cookie.zig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,9 @@ pub const Cookie = struct {
266266
path: []const u8,
267267
domain: []const u8,
268268
expires: ?i64,
269-
secure: bool,
270-
http_only: bool,
271-
same_site: SameSite,
269+
secure: bool = false,
270+
http_only: bool = false,
271+
same_site: SameSite = .none,
272272

273273
const SameSite = enum {
274274
strict,
@@ -372,7 +372,7 @@ pub const Cookie = struct {
372372
if (std.mem.endsWith(u8, host, value) == false) {
373373
return error.InvalidDomain;
374374
}
375-
domain = value;
375+
domain = value; // TODO to lower case: https://www.rfc-editor.org/rfc/rfc6265#section-5.2.3
376376
},
377377
.secure => secure = true,
378378
.@"max-age" => max_age = std.fmt.parseInt(i64, value, 10) catch continue,

src/cdp/domains/network.zig

Lines changed: 113 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub fn processMessage(cmd: anytype) !void {
2929
setCacheDisabled,
3030
setExtraHTTPHeaders,
3131
deleteCookies,
32+
setCookies,
3233
}, cmd.input.action) orelse return error.UnknownMethod;
3334

3435
switch (action) {
@@ -37,6 +38,7 @@ pub fn processMessage(cmd: anytype) !void {
3738
.setCacheDisabled => return cmd.sendResult(null, .{}),
3839
.setExtraHTTPHeaders => return setExtraHTTPHeaders(cmd),
3940
.deleteCookies => return deleteCookies(cmd),
41+
.setCookies => return setCookies(cmd),
4042
}
4143
}
4244

@@ -73,19 +75,17 @@ fn setExtraHTTPHeaders(cmd: anytype) !void {
7375
return cmd.sendResult(null, .{});
7476
}
7577

76-
// const CookiePartitionKey = struct {
77-
// topLevelSite: []const u8,
78-
// hasCrossSiteAncestor: bool,
79-
// };
78+
const CookiePartitionKey = struct {
79+
topLevelSite: []const u8,
80+
hasCrossSiteAncestor: bool,
81+
};
8082

8183
const Cookie = @import("../../browser/storage/storage.zig").Cookie;
8284
const CookieJar = @import("../../browser/storage/storage.zig").CookieJar;
8385

84-
fn cookieMatches(cookie: *const Cookie, name: []const u8, url: ?[]const u8, domain: ?[]const u8, path: ?[]const u8) bool {
86+
fn cookieMatches(cookie: *const Cookie, name: []const u8, domain: ?[]const u8, path: ?[]const u8) bool {
8587
if (!std.mem.eql(u8, cookie.name, name)) return false;
8688

87-
_ = url; // TODO
88-
8989
if (domain) |domain_| {
9090
if (!std.mem.eql(u8, cookie.domain, domain_)) return false;
9191
}
@@ -112,13 +112,118 @@ fn deleteCookies(cmd: anytype) !void {
112112
while (index > 0) {
113113
index -= 1;
114114
const cookie = &cookies.items[index];
115-
if (cookieMatches(cookie, params.name, params.url, params.domain, params.path)) {
115+
const domain = try percentEncodedDomain(cmd.arena, params.url, params.domain);
116+
// TBD does chrome take the path from the url as default? (unlike setCookies)
117+
if (cookieMatches(cookie, params.name, domain, params.path)) {
116118
cookies.swapRemove(index).deinit();
117119
}
118120
}
119121
return cmd.sendResult(null, .{});
120122
}
121123

124+
const SameSite = enum {
125+
Strict,
126+
Lax,
127+
None,
128+
};
129+
const CookiePriority = enum {
130+
Low,
131+
Medium,
132+
High,
133+
};
134+
const CookieSourceScheme = enum {
135+
Unset,
136+
NonSecure,
137+
Secure,
138+
};
139+
140+
fn isHostChar(c: u8) bool {
141+
return switch (c) {
142+
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_', '~' => true,
143+
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' => true,
144+
':' => true,
145+
'[', ']' => true,
146+
else => false,
147+
};
148+
}
149+
150+
// Note: Chrome does not apply rules like removing a leading `.` from the domain.
151+
fn percentEncodedDomain(allocator: Allocator, default_url: ?[]const u8, domain: ?[]const u8) !?[]const u8 {
152+
if (domain) |domain_| {
153+
return try allocator.dupe(u8, domain_);
154+
} else if (default_url) |url| {
155+
const uri = std.Uri.parse(url) catch return error.InvalidParams;
156+
157+
switch (uri.host orelse return error.InvalidParams) {
158+
.raw => |str| {
159+
var list = std.ArrayList(u8).init(allocator);
160+
try list.ensureTotalCapacity(str.len); // Expect no precents needed
161+
try std.Uri.Component.percentEncode(list.writer(), str, isHostChar);
162+
return list.items; // @memory retains memory used before growing
163+
},
164+
.percent_encoded => |str| {
165+
return try allocator.dupe(u8, str);
166+
},
167+
}
168+
} else return null;
169+
}
170+
171+
fn setCookies(cmd: anytype) !void {
172+
const params = (try cmd.params(struct {
173+
cookies: []const struct {
174+
name: []const u8,
175+
value: []const u8,
176+
url: ?[]const u8 = null,
177+
domain: ?[]const u8 = null,
178+
path: ?[]const u8 = null,
179+
secure: bool = false, // default: https://www.rfc-editor.org/rfc/rfc6265#section-5.3
180+
httpOnly: bool = false, // default: https://www.rfc-editor.org/rfc/rfc6265#section-5.3
181+
sameSite: SameSite = .None, // default: https://datatracker.ietf.org/doc/html/draft-west-first-party-cookies
182+
expires: ?i64 = null, // -1? says google
183+
priority: CookiePriority = .Medium, // default: https://datatracker.ietf.org/doc/html/draft-west-cookie-priority-00
184+
sameParty: ?bool = null,
185+
sourceScheme: ?CookieSourceScheme = null,
186+
// sourcePort: Temporary ability and it will be removed from CDP
187+
partitionKey: ?CookiePartitionKey = null,
188+
},
189+
})) orelse return error.InvalidParams;
190+
191+
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
192+
for (params.cookies) |param| {
193+
if (param.priority != .Medium or param.sameParty != null or param.sourceScheme != null or param.partitionKey != null) {
194+
return error.NotYetImplementedParams;
195+
}
196+
if (param.name.len == 0) return error.InvalidParams;
197+
if (param.value.len == 0) return error.InvalidParams;
198+
199+
var arena = std.heap.ArenaAllocator.init(bc.session.cookie_jar.allocator);
200+
errdefer arena.deinit();
201+
const a = arena.allocator();
202+
203+
// NOTE: The param.url can affect the default domain, path, source port, and source scheme.
204+
const domain = try percentEncodedDomain(a, param.url, param.domain) orelse return error.InvalidParams;
205+
206+
const cookie = Cookie{
207+
.arena = arena,
208+
.name = try a.dupe(u8, param.name),
209+
.value = try a.dupe(u8, param.value),
210+
.path = if (param.path) |path| try a.dupe(u8, path) else "/", // Chrome does not actually take the path from the url and just defaults to "/".
211+
.domain = domain,
212+
.expires = param.expires,
213+
.secure = param.secure,
214+
.http_only = param.httpOnly,
215+
.same_site = switch (param.sameSite) {
216+
.Strict => .strict,
217+
.Lax => .lax,
218+
.None => .none,
219+
},
220+
};
221+
try bc.session.cookie_jar.add(cookie, std.time.timestamp());
222+
}
223+
224+
return cmd.sendResult(null, .{});
225+
}
226+
122227
// Upsert a header into the headers array.
123228
// returns true if the header was added, false if it was updated
124229
fn putAssumeCapacity(headers: *std.ArrayListUnmanaged(std.http.Header), extra: std.http.Header) bool {

0 commit comments

Comments
 (0)