Skip to content

Commit 14c07ee

Browse files
committed
refactor path / domain parsing
1 parent 4103a35 commit 14c07ee

File tree

3 files changed

+94
-56
lines changed

3 files changed

+94
-56
lines changed

src/browser/storage/cookie.zig

Lines changed: 92 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,6 @@ pub const Cookie = struct {
207207
// this check is necessary, `std.mem.minMax` asserts len > 0
208208
return error.Empty;
209209
}
210-
211-
const host = (uri.host orelse return error.InvalidURI).percent_encoded;
212-
213210
{
214211
const min, const max = std.mem.minMax(u8, str);
215212
if (min < 32 or max > 126) {
@@ -254,34 +251,10 @@ pub const Cookie = struct {
254251
samesite,
255252
}, std.ascii.lowerString(&scrap, key_string)) orelse continue;
256253

257-
var value = if (sep == attribute.len) "" else trim(attribute[sep + 1 ..]);
254+
const value = if (sep == attribute.len) "" else trim(attribute[sep + 1 ..]);
258255
switch (key) {
259-
.path => {
260-
// path attribute value either begins with a '/' or we
261-
// ignore it and use the "default-path" algorithm
262-
if (value.len > 0 and value[0] == '/') {
263-
path = value;
264-
}
265-
},
266-
.domain => {
267-
if (value.len == 0) {
268-
continue;
269-
}
270-
if (value[0] == '.') {
271-
// leading dot is ignored
272-
value = value[1..];
273-
}
274-
275-
if (std.mem.indexOfScalarPos(u8, value, 0, '.') == null and std.ascii.eqlIgnoreCase("localhost", value) == false) {
276-
// can't set a cookie for a TLD
277-
return error.InvalidDomain;
278-
}
279-
280-
if (std.mem.endsWith(u8, host, value) == false) {
281-
return error.InvalidDomain;
282-
}
283-
domain = value; // Domain is made lower case after it has relocated to the arena
284-
},
256+
.path => path = value,
257+
.domain => domain = value,
285258
.secure => secure = true,
286259
.@"max-age" => max_age = std.fmt.parseInt(i64, value, 10) catch continue,
287260
.expires => expires = DateTime.parse(value, .rfc822) catch continue,
@@ -301,20 +274,9 @@ pub const Cookie = struct {
301274
const aa = arena.allocator();
302275
const owned_name = try aa.dupe(u8, cookie_name);
303276
const owned_value = try aa.dupe(u8, cookie_value);
304-
const owned_path = if (path) |p|
305-
try aa.dupe(u8, p)
306-
else
307-
try defaultPath(aa, uri.path.percent_encoded);
308-
309-
const owned_domain = if (domain) |d| blk: {
310-
const s = try aa.alloc(u8, d.len + 1);
311-
s[0] = '.';
312-
@memcpy(s[1..], d);
313-
break :blk s;
314-
} else blk: {
315-
break :blk try aa.dupe(u8, host); // Sjors: Should subdomains be removed from host?
316-
};
317-
_ = toLower(owned_domain);
277+
const owned_path = try parse_path(aa, uri.path, path);
278+
const host = uri.host orelse return error.InvalidURI;
279+
const owned_domain = try parse_domain(aa, host, domain);
318280

319281
var normalized_expires: ?i64 = null;
320282
if (max_age) |ma| {
@@ -339,6 +301,92 @@ pub const Cookie = struct {
339301
};
340302
}
341303

304+
pub fn parse_path(arena: Allocator, url_path: std.Uri.Component, explicit_path: ?[]const u8) ![]const u8 {
305+
// path attribute value either begins with a '/' or we
306+
// ignore it and use the "default-path" algorithm
307+
if (explicit_path) |path| {
308+
if (path.len > 0 and path[0] == '/') {
309+
return try arena.dupe(u8, path);
310+
}
311+
}
312+
313+
// default-path
314+
const either = url_path.percent_encoded;
315+
if (either.len == 0 or (either.len == 1 and either[0] == '/')) {
316+
return "/";
317+
}
318+
319+
var owned_path: []const u8 = try percentEncode(arena, url_path, isPathChar);
320+
const last = std.mem.lastIndexOfScalar(u8, owned_path[1..], '/') orelse {
321+
return "/";
322+
};
323+
return try arena.dupe(u8, owned_path[0 .. last + 1]);
324+
}
325+
326+
pub fn parse_domain(arena: Allocator, url_host: std.Uri.Component, explicit_domain: ?[]const u8) ![]const u8 {
327+
const encoded_host = try percentEncode(arena, url_host, isHostChar);
328+
_ = toLower(encoded_host);
329+
330+
if (explicit_domain) |domain| {
331+
if (domain.len > 0) {
332+
const no_leading_dot = if (domain[0] == '.') domain[1..] else domain;
333+
334+
var list = std.ArrayList(u8).init(arena);
335+
try list.ensureTotalCapacity(no_leading_dot.len + 1); // Expect no precents needed
336+
list.appendAssumeCapacity('.');
337+
try std.Uri.Component.percentEncode(list.writer(), no_leading_dot, isHostChar);
338+
var owned_domain: []u8 = list.items; // @memory retains memory used before growing
339+
_ = toLower(owned_domain);
340+
341+
if (std.mem.indexOfScalarPos(u8, owned_domain, 1, '.') == null and std.mem.eql(u8, "localhost", owned_domain[1..]) == false) {
342+
// can't set a cookie for a TLD
343+
return error.InvalidDomain;
344+
}
345+
if (std.mem.endsWith(u8, encoded_host, owned_domain[1..]) == false) {
346+
return error.InvalidDomain;
347+
}
348+
return owned_domain;
349+
}
350+
}
351+
352+
return encoded_host; // default-domain
353+
}
354+
355+
// TODO when getting cookeis Note: Chrome does not apply rules like removing a leading `.` from the domain.
356+
357+
pub fn percentEncode(arena: Allocator, component: std.Uri.Component, comptime isValidChar: fn (u8) bool) ![]u8 {
358+
switch (component) {
359+
.raw => |str| {
360+
var list = std.ArrayList(u8).init(arena);
361+
try list.ensureTotalCapacity(str.len); // Expect no precents needed
362+
try std.Uri.Component.percentEncode(list.writer(), str, isValidChar);
363+
return list.items; // @memory retains memory used before growing
364+
},
365+
.percent_encoded => |str| {
366+
return try arena.dupe(u8, str);
367+
},
368+
}
369+
}
370+
371+
pub fn isHostChar(c: u8) bool {
372+
return switch (c) {
373+
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_', '~' => true,
374+
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' => true,
375+
':' => true,
376+
'[', ']' => true,
377+
else => false,
378+
};
379+
}
380+
381+
pub fn isPathChar(c: u8) bool {
382+
return switch (c) {
383+
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_', '~' => true,
384+
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' => true,
385+
'/', ':', '@' => true,
386+
else => false,
387+
};
388+
}
389+
342390
fn parseNameValue(str: []const u8) !struct { []const u8, []const u8, []const u8 } {
343391
const key_value_end = std.mem.indexOfScalarPos(u8, str, 0, ';') orelse str.len;
344392
const rest = if (key_value_end == str.len) "" else str[key_value_end + 1 ..];
@@ -422,16 +470,6 @@ pub const PreparedUri = struct {
422470
secure: bool, // True if scheme is https
423471
};
424472

425-
fn defaultPath(allocator: Allocator, document_path: []const u8) ![]const u8 {
426-
if (document_path.len == 0 or (document_path.len == 1 and document_path[0] == '/')) {
427-
return "/";
428-
}
429-
const last = std.mem.lastIndexOfScalar(u8, document_path[1..], '/') orelse {
430-
return "/";
431-
};
432-
return try allocator.dupe(u8, document_path[0 .. last + 1]);
433-
}
434-
435473
fn trim(str: []const u8) []const u8 {
436474
return std.mem.trim(u8, str, &std.ascii.whitespace);
437475
}

src/cdp/domains/storage.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ pub fn writeCookie(cookie: *const Cookie, w: anytype) !void {
249249
try w.write(cookie.value);
250250

251251
try w.objectField("domain");
252-
try w.write(cookie.domain);
252+
try w.write(cookie.domain); // Should we hide a leading dot?
253253

254254
try w.objectField("path");
255255
try w.write(cookie.path);

src/runtime/js.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
400400
};
401401

402402
// For a Page we only create one HandleScope, it is stored in the main World (enter==true). A page can have multple contexts, 1 for each World.
403-
// The main Context/Scope that enters and holds the HandleScope should therefore always be created first. Following other worlds for this page
403+
// The main Context that enters and holds the HandleScope should therefore always be created first. Following other worlds for this page
404404
// like isolated Worlds, will thereby place their objects on the main page's HandleScope. Note: In the furure the number of context will multiply multiple frames support
405405
var handle_scope: ?v8.HandleScope = null;
406406
if (enter) {

0 commit comments

Comments
 (0)