@@ -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
8183const Cookie = @import ("../../browser/storage/storage.zig" ).Cookie ;
8284const 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
124229fn putAssumeCapacity (headers : * std .ArrayListUnmanaged (std.http.Header ), extra : std .http .Header ) bool {
0 commit comments