Skip to content

Commit 8ab0024

Browse files
committed
Network.getCookies
1 parent e8f8dc9 commit 8ab0024

File tree

3 files changed

+201
-200
lines changed

3 files changed

+201
-200
lines changed

src/browser/storage/cookie.zig

Lines changed: 87 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -66,87 +66,33 @@ pub const Jar = struct {
6666
}
6767
}
6868

69-
pub fn forRequest(self: *Jar, target_uri: *const Uri, writer: anytype, opts: LookupOpts) !void {
70-
const target_path = target_uri.path.percent_encoded;
71-
const target_host = (target_uri.host orelse return error.InvalidURI).percent_encoded;
72-
73-
const same_site = try areSameSite(opts.origin_uri, target_host);
74-
const is_secure = std.mem.eql(u8, target_uri.scheme, "https");
75-
76-
var i: usize = 0;
77-
var cookies = self.cookies.items;
78-
const navigation = opts.navigation;
79-
const request_time = opts.request_time orelse std.time.timestamp();
80-
81-
var first = true;
82-
while (i < cookies.len) {
83-
const cookie = &cookies[i];
84-
85-
if (isCookieExpired(cookie, request_time)) {
86-
cookie.deinit();
87-
_ = self.cookies.swapRemove(i);
88-
// don't increment i !
89-
continue;
69+
pub fn removeExpired(self: *Jar, request_time: ?i64) void {
70+
if (self.cookies.items.len == 0) return;
71+
const time = request_time orelse std.time.timestamp();
72+
var i: usize = self.cookies.items.len - 1;
73+
while (i > 0) {
74+
defer i -= 1;
75+
const cookie = &self.cookies.items[i];
76+
if (isCookieExpired(cookie, time)) {
77+
self.cookies.swapRemove(i).deinit();
9078
}
91-
i += 1;
79+
}
80+
}
9281

93-
if (is_secure == false and cookie.secure) {
94-
// secure cookie can only be sent over HTTPs
95-
continue;
96-
}
82+
pub fn forRequest(self: *Jar, target_uri: *const Uri, writer: anytype, opts: LookupOpts) !void {
83+
const target = PreparedUri{
84+
.host = (target_uri.host orelse return error.InvalidURI).percent_encoded,
85+
.path = target_uri.path.percent_encoded,
86+
.secure = std.mem.eql(u8, target_uri.scheme, "https"),
87+
};
88+
const same_site = try areSameSite(opts.origin_uri, target.host);
9789

98-
if (same_site == false) {
99-
// If we aren't on the "same site" (matching 2nd level domain
100-
// taking into account public suffix list), then the cookie
101-
// can only be sent if cookie.same_site == .none, or if
102-
// we're navigating to (as opposed to, say, loading an image)
103-
// and cookie.same_site == .lax
104-
switch (cookie.same_site) {
105-
.strict => continue,
106-
.lax => if (navigation == false) continue,
107-
.none => {},
108-
}
109-
}
90+
removeExpired(self, opts.request_time);
11091

111-
{
112-
const domain = cookie.domain;
113-
if (domain[0] == '.') {
114-
// When a Set-Cookie header has a Domain attribute
115-
// Then we will _always_ prefix it with a dot, extending its
116-
// availability to all subdomains (yes, setting the Domain
117-
// attributes EXPANDS the domains which the cookie will be
118-
// sent to, to always include all subdomains).
119-
if (std.mem.eql(u8, target_host, domain[1..]) == false and std.mem.endsWith(u8, target_host, domain) == false) {
120-
continue;
121-
}
122-
} else if (std.mem.eql(u8, target_host, domain) == false) {
123-
// When the Domain attribute isn't specific, then the cookie
124-
// is only sent on an exact match.
125-
continue;
126-
}
127-
}
92+
var first = true;
93+
for (self.cookies.items) |*cookie| {
94+
if (!cookie.appliesTo(&target, same_site, opts.navigation)) continue;
12895

129-
{
130-
const path = cookie.path;
131-
if (path[path.len - 1] == '/') {
132-
// If our cookie has a trailing slash, we can only match is
133-
// the target path is a perfix. I.e., if our path is
134-
// /doc/ we can only match /doc/*
135-
if (std.mem.startsWith(u8, target_path, path) == false) {
136-
continue;
137-
}
138-
} else {
139-
// Our cookie path is something like /hello
140-
if (std.mem.startsWith(u8, target_path, path) == false) {
141-
// The target path has to either be /hello (it isn't)
142-
continue;
143-
} else if (target_path.len < path.len or (target_path.len > path.len and target_path[path.len] != '/')) {
144-
// Or it has to be something like /hello/* (it isn't)
145-
// it isn't!
146-
continue;
147-
}
148-
}
149-
}
15096
// we have a match!
15197
if (first) {
15298
first = false;
@@ -180,44 +126,6 @@ pub const Jar = struct {
180126
}
181127
};
182128

183-
// pub const CookieList = struct {
184-
// _cookies: std.ArrayListUnmanaged(*const Cookie) = .{},
185-
186-
// pub fn deinit(self: *CookieList, allocator: Allocator) void {
187-
// self._cookies.deinit(allocator);
188-
// }
189-
190-
// pub fn cookies(self: *const CookieList) []*const Cookie {
191-
// return self._cookies.items;
192-
// }
193-
194-
// pub fn len(self: *const CookieList) usize {
195-
// return self._cookies.items.len;
196-
// }
197-
198-
// pub fn write(self: *const CookieList, writer: anytype) !void {
199-
// const all = self._cookies.items;
200-
// if (all.len == 0) {
201-
// return;
202-
// }
203-
// try writeCookie(all[0], writer);
204-
// for (all[1..]) |cookie| {
205-
// try writer.writeAll("; ");
206-
// try writeCookie(cookie, writer);
207-
// }
208-
// }
209-
210-
// fn writeCookie(cookie: *const Cookie, writer: anytype) !void {
211-
// if (cookie.name.len > 0) {
212-
// try writer.writeAll(cookie.name);
213-
// try writer.writeByte('=');
214-
// }
215-
// if (cookie.value.len > 0) {
216-
// try writer.writeAll(cookie.value);
217-
// }
218-
// }
219-
// };
220-
221129
fn isCookieExpired(cookie: *const Cookie, now: i64) bool {
222130
const ce = cookie.expires orelse return false;
223131
return ce <= now;
@@ -447,6 +355,71 @@ pub const Cookie = struct {
447355
const value = trim(str[sep + 1 .. key_value_end]);
448356
return .{ name, value, rest };
449357
}
358+
359+
pub fn appliesTo(self: *const Cookie, url: *const PreparedUri, same_site: bool, navigation: bool) bool {
360+
if (url.secure == false and self.secure) {
361+
// secure cookie can only be sent over HTTPs
362+
return false;
363+
}
364+
365+
if (same_site == false) {
366+
// If we aren't on the "same site" (matching 2nd level domain
367+
// taking into account public suffix list), then the cookie
368+
// can only be sent if cookie.same_site == .none, or if
369+
// we're navigating to (as opposed to, say, loading an image)
370+
// and cookie.same_site == .lax
371+
switch (self.same_site) {
372+
.strict => return false,
373+
.lax => if (navigation == false) return false,
374+
.none => {},
375+
}
376+
}
377+
378+
{
379+
if (self.domain[0] == '.') {
380+
// When a Set-Cookie header has a Domain attribute
381+
// Then we will _always_ prefix it with a dot, extending its
382+
// availability to all subdomains (yes, setting the Domain
383+
// attributes EXPANDS the domains which the cookie will be
384+
// sent to, to always include all subdomains).
385+
if (std.mem.eql(u8, url.host, self.domain[1..]) == false and std.mem.endsWith(u8, url.host, self.domain) == false) {
386+
return false;
387+
}
388+
} else if (std.mem.eql(u8, url.host, self.domain) == false) {
389+
// When the Domain attribute isn't specific, then the cookie
390+
// is only sent on an exact match.
391+
return false;
392+
}
393+
}
394+
395+
{
396+
if (self.path[self.path.len - 1] == '/') {
397+
// If our cookie has a trailing slash, we can only match is
398+
// the target path is a perfix. I.e., if our path is
399+
// /doc/ we can only match /doc/*
400+
if (std.mem.startsWith(u8, url.path, self.path) == false) {
401+
return false;
402+
}
403+
} else {
404+
// Our cookie path is something like /hello
405+
if (std.mem.startsWith(u8, url.path, self.path) == false) {
406+
// The target path has to either be /hello (it isn't)
407+
return false;
408+
} else if (url.path.len < self.path.len or (url.path.len > self.path.len and url.path[self.path.len] != '/')) {
409+
// Or it has to be something like /hello/* (it isn't)
410+
// it isn't!
411+
return false;
412+
}
413+
}
414+
}
415+
return true;
416+
}
417+
};
418+
419+
pub const PreparedUri = struct {
420+
host: []const u8, // Percent encoded, lower case
421+
path: []const u8, // Percent encoded
422+
secure: bool, // True if scheme is https
450423
};
451424

452425
fn defaultPath(allocator: Allocator, document_path: []const u8) ![]const u8 {
@@ -675,40 +648,6 @@ test "Jar: forRequest" {
675648
// the 'global2' cookie
676649
}
677650

678-
// test "CookieList: write" {
679-
// var arr: std.ArrayListUnmanaged(u8) = .{};
680-
// defer arr.deinit(testing.allocator);
681-
682-
// var cookie_list = CookieList{};
683-
// defer cookie_list.deinit(testing.allocator);
684-
685-
// const c1 = try Cookie.parse(testing.allocator, &test_uri, "cookie_name=cookie_value");
686-
// defer c1.deinit();
687-
// {
688-
// try cookie_list._cookies.append(testing.allocator, &c1);
689-
// try cookie_list.write(arr.writer(testing.allocator));
690-
// try testing.expectEqual("cookie_name=cookie_value", arr.items);
691-
// }
692-
693-
// const c2 = try Cookie.parse(testing.allocator, &test_uri, "x84");
694-
// defer c2.deinit();
695-
// {
696-
// arr.clearRetainingCapacity();
697-
// try cookie_list._cookies.append(testing.allocator, &c2);
698-
// try cookie_list.write(arr.writer(testing.allocator));
699-
// try testing.expectEqual("cookie_name=cookie_value; x84", arr.items);
700-
// }
701-
702-
// const c3 = try Cookie.parse(testing.allocator, &test_uri, "nope=");
703-
// defer c3.deinit();
704-
// {
705-
// arr.clearRetainingCapacity();
706-
// try cookie_list._cookies.append(testing.allocator, &c3);
707-
// try cookie_list.write(arr.writer(testing.allocator));
708-
// try testing.expectEqual("cookie_name=cookie_value; x84; nope=", arr.items);
709-
// }
710-
// }
711-
712651
test "Cookie: parse key=value" {
713652
try expectError(error.Empty, null, "");
714653
try expectError(error.InvalidByteSequence, null, &.{ 'a', 30, '=', 'b' });

src/cdp/domains/network.zig

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub fn processMessage(cmd: anytype) !void {
3333
clearBrowserCookies,
3434
setCookie,
3535
setCookies,
36+
getCookies,
3637
}, cmd.input.action) orelse return error.UnknownMethod;
3738

3839
switch (action) {
@@ -44,6 +45,7 @@ pub fn processMessage(cmd: anytype) !void {
4445
.clearBrowserCookies => return clearBrowserCookies(cmd),
4546
.setCookie => return setCookie(cmd),
4647
.setCookies => return setCookies(cmd),
48+
.getCookies => return getCookies(cmd),
4749
}
4850
}
4951

@@ -82,6 +84,7 @@ fn setExtraHTTPHeaders(cmd: anytype) !void {
8284

8385
const Cookie = @import("../../browser/storage/storage.zig").Cookie;
8486

87+
// Only matches the cookie on provided parameters
8588
fn cookieMatches(cookie: *const Cookie, name: []const u8, domain: ?[]const u8, path: ?[]const u8) bool {
8689
if (!std.mem.eql(u8, cookie.name, name)) return false;
8790

@@ -91,10 +94,15 @@ fn cookieMatches(cookie: *const Cookie, name: []const u8, domain: ?[]const u8, p
9194
if (path) |path_| {
9295
if (!std.mem.eql(u8, cookie.path, path_)) return false;
9396
}
94-
9597
return true;
9698
}
9799

100+
// Only matches the cookie on provided parameters
101+
fn cookieAppliesTo(cookie: *const Cookie, domain: []const u8, path: []const u8) bool {
102+
if (!std.mem.eql(u8, cookie.domain, domain)) return false;
103+
return std.mem.startsWith(u8, path, cookie.path);
104+
}
105+
98106
fn deleteCookies(cmd: anytype) !void {
99107
const params = (try cmd.params(struct {
100108
name: []const u8,
@@ -107,11 +115,13 @@ fn deleteCookies(cmd: anytype) !void {
107115
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
108116
const cookies = &bc.session.cookie_jar.cookies;
109117

118+
const uri = if (params.url) |url| std.Uri.parse(url) catch return error.InvalidParams else null;
119+
110120
var index = cookies.items.len;
111121
while (index > 0) {
112122
index -= 1;
113123
const cookie = &cookies.items[index];
114-
const domain = try CdpStorage.percentEncodedDomain(cmd.arena, params.url, params.domain);
124+
const domain = try CdpStorage.percentEncodedDomainOrHost(cmd.arena, uri, params.domain);
115125
// TBD does chrome take the path from the url as default? (unlike setCookies)
116126
if (cookieMatches(cookie, params.name, domain, params.path)) {
117127
cookies.swapRemove(index).deinit();
@@ -153,6 +163,33 @@ fn setCookies(cmd: anytype) !void {
153163
try cmd.sendResult(null, .{});
154164
}
155165

166+
fn getCookies(cmd: anytype) !void {
167+
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
168+
const params = (try cmd.params(struct {
169+
urls: []const []const u8,
170+
})) orelse return error.InvalidParams;
171+
172+
var urls = try std.ArrayListUnmanaged(CdpStorage.PreparedUri).initCapacity(cmd.arena, params.urls.len);
173+
for (params.urls) |url| {
174+
const uri = std.Uri.parse(url) catch return error.InvalidParams;
175+
176+
const host_component = uri.host orelse return error.InvalidParams;
177+
const host = CdpStorage.toLower(try CdpStorage.percentEncode(cmd.arena, host_component, CdpStorage.isHostChar));
178+
179+
var path: []const u8 = try CdpStorage.percentEncode(cmd.arena, uri.path, CdpStorage.isPathChar);
180+
if (path.len == 0) path = "/";
181+
182+
const secure = std.mem.eql(u8, uri.scheme, "https");
183+
184+
urls.appendAssumeCapacity(.{ .host = host, .path = path, .secure = secure });
185+
}
186+
187+
var jar = &bc.session.cookie_jar;
188+
jar.removeExpired(null);
189+
const writer = CdpStorage.CookieWriter{ .cookies = jar.cookies.items, .urls = urls.items };
190+
try cmd.sendResult(.{ .cookies = writer }, .{});
191+
}
192+
156193
// Upsert a header into the headers array.
157194
// returns true if the header was added, false if it was updated
158195
fn putAssumeCapacity(headers: *std.ArrayListUnmanaged(std.http.Header), extra: std.http.Header) bool {

0 commit comments

Comments
 (0)