mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
Network.getCookies
This commit is contained in:
@@ -66,87 +66,33 @@ pub const Jar = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn removeExpired(self: *Jar, request_time: ?i64) void {
|
||||||
|
if (self.cookies.items.len == 0) return;
|
||||||
|
const time = request_time orelse std.time.timestamp();
|
||||||
|
var i: usize = self.cookies.items.len - 1;
|
||||||
|
while (i > 0) {
|
||||||
|
defer i -= 1;
|
||||||
|
const cookie = &self.cookies.items[i];
|
||||||
|
if (isCookieExpired(cookie, time)) {
|
||||||
|
self.cookies.swapRemove(i).deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn forRequest(self: *Jar, target_uri: *const Uri, writer: anytype, opts: LookupOpts) !void {
|
pub fn forRequest(self: *Jar, target_uri: *const Uri, writer: anytype, opts: LookupOpts) !void {
|
||||||
const target_path = target_uri.path.percent_encoded;
|
const target = PreparedUri{
|
||||||
const target_host = (target_uri.host orelse return error.InvalidURI).percent_encoded;
|
.host = (target_uri.host orelse return error.InvalidURI).percent_encoded,
|
||||||
|
.path = target_uri.path.percent_encoded,
|
||||||
|
.secure = std.mem.eql(u8, target_uri.scheme, "https"),
|
||||||
|
};
|
||||||
|
const same_site = try areSameSite(opts.origin_uri, target.host);
|
||||||
|
|
||||||
const same_site = try areSameSite(opts.origin_uri, target_host);
|
removeExpired(self, opts.request_time);
|
||||||
const is_secure = std.mem.eql(u8, target_uri.scheme, "https");
|
|
||||||
|
|
||||||
var i: usize = 0;
|
|
||||||
var cookies = self.cookies.items;
|
|
||||||
const navigation = opts.navigation;
|
|
||||||
const request_time = opts.request_time orelse std.time.timestamp();
|
|
||||||
|
|
||||||
var first = true;
|
var first = true;
|
||||||
while (i < cookies.len) {
|
for (self.cookies.items) |*cookie| {
|
||||||
const cookie = &cookies[i];
|
if (!cookie.appliesTo(&target, same_site, opts.navigation)) continue;
|
||||||
|
|
||||||
if (isCookieExpired(cookie, request_time)) {
|
|
||||||
cookie.deinit();
|
|
||||||
_ = self.cookies.swapRemove(i);
|
|
||||||
// don't increment i !
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
if (is_secure == false and cookie.secure) {
|
|
||||||
// secure cookie can only be sent over HTTPs
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (same_site == false) {
|
|
||||||
// If we aren't on the "same site" (matching 2nd level domain
|
|
||||||
// taking into account public suffix list), then the cookie
|
|
||||||
// can only be sent if cookie.same_site == .none, or if
|
|
||||||
// we're navigating to (as opposed to, say, loading an image)
|
|
||||||
// and cookie.same_site == .lax
|
|
||||||
switch (cookie.same_site) {
|
|
||||||
.strict => continue,
|
|
||||||
.lax => if (navigation == false) continue,
|
|
||||||
.none => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const domain = cookie.domain;
|
|
||||||
if (domain[0] == '.') {
|
|
||||||
// When a Set-Cookie header has a Domain attribute
|
|
||||||
// Then we will _always_ prefix it with a dot, extending its
|
|
||||||
// availability to all subdomains (yes, setting the Domain
|
|
||||||
// attributes EXPANDS the domains which the cookie will be
|
|
||||||
// sent to, to always include all subdomains).
|
|
||||||
if (std.mem.eql(u8, target_host, domain[1..]) == false and std.mem.endsWith(u8, target_host, domain) == false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (std.mem.eql(u8, target_host, domain) == false) {
|
|
||||||
// When the Domain attribute isn't specific, then the cookie
|
|
||||||
// is only sent on an exact match.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const path = cookie.path;
|
|
||||||
if (path[path.len - 1] == '/') {
|
|
||||||
// If our cookie has a trailing slash, we can only match is
|
|
||||||
// the target path is a perfix. I.e., if our path is
|
|
||||||
// /doc/ we can only match /doc/*
|
|
||||||
if (std.mem.startsWith(u8, target_path, path) == false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Our cookie path is something like /hello
|
|
||||||
if (std.mem.startsWith(u8, target_path, path) == false) {
|
|
||||||
// The target path has to either be /hello (it isn't)
|
|
||||||
continue;
|
|
||||||
} else if (target_path.len < path.len or (target_path.len > path.len and target_path[path.len] != '/')) {
|
|
||||||
// Or it has to be something like /hello/* (it isn't)
|
|
||||||
// it isn't!
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// we have a match!
|
// we have a match!
|
||||||
if (first) {
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
@@ -180,44 +126,6 @@ pub const Jar = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// pub const CookieList = struct {
|
|
||||||
// _cookies: std.ArrayListUnmanaged(*const Cookie) = .{},
|
|
||||||
|
|
||||||
// pub fn deinit(self: *CookieList, allocator: Allocator) void {
|
|
||||||
// self._cookies.deinit(allocator);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn cookies(self: *const CookieList) []*const Cookie {
|
|
||||||
// return self._cookies.items;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn len(self: *const CookieList) usize {
|
|
||||||
// return self._cookies.items.len;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn write(self: *const CookieList, writer: anytype) !void {
|
|
||||||
// const all = self._cookies.items;
|
|
||||||
// if (all.len == 0) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// try writeCookie(all[0], writer);
|
|
||||||
// for (all[1..]) |cookie| {
|
|
||||||
// try writer.writeAll("; ");
|
|
||||||
// try writeCookie(cookie, writer);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn writeCookie(cookie: *const Cookie, writer: anytype) !void {
|
|
||||||
// if (cookie.name.len > 0) {
|
|
||||||
// try writer.writeAll(cookie.name);
|
|
||||||
// try writer.writeByte('=');
|
|
||||||
// }
|
|
||||||
// if (cookie.value.len > 0) {
|
|
||||||
// try writer.writeAll(cookie.value);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
fn isCookieExpired(cookie: *const Cookie, now: i64) bool {
|
fn isCookieExpired(cookie: *const Cookie, now: i64) bool {
|
||||||
const ce = cookie.expires orelse return false;
|
const ce = cookie.expires orelse return false;
|
||||||
return ce <= now;
|
return ce <= now;
|
||||||
@@ -447,6 +355,71 @@ pub const Cookie = struct {
|
|||||||
const value = trim(str[sep + 1 .. key_value_end]);
|
const value = trim(str[sep + 1 .. key_value_end]);
|
||||||
return .{ name, value, rest };
|
return .{ name, value, rest };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn appliesTo(self: *const Cookie, url: *const PreparedUri, same_site: bool, navigation: bool) bool {
|
||||||
|
if (url.secure == false and self.secure) {
|
||||||
|
// secure cookie can only be sent over HTTPs
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (same_site == false) {
|
||||||
|
// If we aren't on the "same site" (matching 2nd level domain
|
||||||
|
// taking into account public suffix list), then the cookie
|
||||||
|
// can only be sent if cookie.same_site == .none, or if
|
||||||
|
// we're navigating to (as opposed to, say, loading an image)
|
||||||
|
// and cookie.same_site == .lax
|
||||||
|
switch (self.same_site) {
|
||||||
|
.strict => return false,
|
||||||
|
.lax => if (navigation == false) return false,
|
||||||
|
.none => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if (self.domain[0] == '.') {
|
||||||
|
// When a Set-Cookie header has a Domain attribute
|
||||||
|
// Then we will _always_ prefix it with a dot, extending its
|
||||||
|
// availability to all subdomains (yes, setting the Domain
|
||||||
|
// attributes EXPANDS the domains which the cookie will be
|
||||||
|
// sent to, to always include all subdomains).
|
||||||
|
if (std.mem.eql(u8, url.host, self.domain[1..]) == false and std.mem.endsWith(u8, url.host, self.domain) == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (std.mem.eql(u8, url.host, self.domain) == false) {
|
||||||
|
// When the Domain attribute isn't specific, then the cookie
|
||||||
|
// is only sent on an exact match.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
if (self.path[self.path.len - 1] == '/') {
|
||||||
|
// If our cookie has a trailing slash, we can only match is
|
||||||
|
// the target path is a perfix. I.e., if our path is
|
||||||
|
// /doc/ we can only match /doc/*
|
||||||
|
if (std.mem.startsWith(u8, url.path, self.path) == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Our cookie path is something like /hello
|
||||||
|
if (std.mem.startsWith(u8, url.path, self.path) == false) {
|
||||||
|
// The target path has to either be /hello (it isn't)
|
||||||
|
return false;
|
||||||
|
} else if (url.path.len < self.path.len or (url.path.len > self.path.len and url.path[self.path.len] != '/')) {
|
||||||
|
// Or it has to be something like /hello/* (it isn't)
|
||||||
|
// it isn't!
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PreparedUri = struct {
|
||||||
|
host: []const u8, // Percent encoded, lower case
|
||||||
|
path: []const u8, // Percent encoded
|
||||||
|
secure: bool, // True if scheme is https
|
||||||
};
|
};
|
||||||
|
|
||||||
fn defaultPath(allocator: Allocator, document_path: []const u8) ![]const u8 {
|
fn defaultPath(allocator: Allocator, document_path: []const u8) ![]const u8 {
|
||||||
@@ -675,40 +648,6 @@ test "Jar: forRequest" {
|
|||||||
// the 'global2' cookie
|
// the 'global2' cookie
|
||||||
}
|
}
|
||||||
|
|
||||||
// test "CookieList: write" {
|
|
||||||
// var arr: std.ArrayListUnmanaged(u8) = .{};
|
|
||||||
// defer arr.deinit(testing.allocator);
|
|
||||||
|
|
||||||
// var cookie_list = CookieList{};
|
|
||||||
// defer cookie_list.deinit(testing.allocator);
|
|
||||||
|
|
||||||
// const c1 = try Cookie.parse(testing.allocator, &test_uri, "cookie_name=cookie_value");
|
|
||||||
// defer c1.deinit();
|
|
||||||
// {
|
|
||||||
// try cookie_list._cookies.append(testing.allocator, &c1);
|
|
||||||
// try cookie_list.write(arr.writer(testing.allocator));
|
|
||||||
// try testing.expectEqual("cookie_name=cookie_value", arr.items);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const c2 = try Cookie.parse(testing.allocator, &test_uri, "x84");
|
|
||||||
// defer c2.deinit();
|
|
||||||
// {
|
|
||||||
// arr.clearRetainingCapacity();
|
|
||||||
// try cookie_list._cookies.append(testing.allocator, &c2);
|
|
||||||
// try cookie_list.write(arr.writer(testing.allocator));
|
|
||||||
// try testing.expectEqual("cookie_name=cookie_value; x84", arr.items);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const c3 = try Cookie.parse(testing.allocator, &test_uri, "nope=");
|
|
||||||
// defer c3.deinit();
|
|
||||||
// {
|
|
||||||
// arr.clearRetainingCapacity();
|
|
||||||
// try cookie_list._cookies.append(testing.allocator, &c3);
|
|
||||||
// try cookie_list.write(arr.writer(testing.allocator));
|
|
||||||
// try testing.expectEqual("cookie_name=cookie_value; x84; nope=", arr.items);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
test "Cookie: parse key=value" {
|
test "Cookie: parse key=value" {
|
||||||
try expectError(error.Empty, null, "");
|
try expectError(error.Empty, null, "");
|
||||||
try expectError(error.InvalidByteSequence, null, &.{ 'a', 30, '=', 'b' });
|
try expectError(error.InvalidByteSequence, null, &.{ 'a', 30, '=', 'b' });
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ pub fn processMessage(cmd: anytype) !void {
|
|||||||
clearBrowserCookies,
|
clearBrowserCookies,
|
||||||
setCookie,
|
setCookie,
|
||||||
setCookies,
|
setCookies,
|
||||||
|
getCookies,
|
||||||
}, cmd.input.action) orelse return error.UnknownMethod;
|
}, cmd.input.action) orelse return error.UnknownMethod;
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
@@ -44,6 +45,7 @@ pub fn processMessage(cmd: anytype) !void {
|
|||||||
.clearBrowserCookies => return clearBrowserCookies(cmd),
|
.clearBrowserCookies => return clearBrowserCookies(cmd),
|
||||||
.setCookie => return setCookie(cmd),
|
.setCookie => return setCookie(cmd),
|
||||||
.setCookies => return setCookies(cmd),
|
.setCookies => return setCookies(cmd),
|
||||||
|
.getCookies => return getCookies(cmd),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +84,7 @@ fn setExtraHTTPHeaders(cmd: anytype) !void {
|
|||||||
|
|
||||||
const Cookie = @import("../../browser/storage/storage.zig").Cookie;
|
const Cookie = @import("../../browser/storage/storage.zig").Cookie;
|
||||||
|
|
||||||
|
// Only matches the cookie on provided parameters
|
||||||
fn cookieMatches(cookie: *const Cookie, name: []const u8, domain: ?[]const u8, path: ?[]const u8) bool {
|
fn cookieMatches(cookie: *const Cookie, name: []const u8, domain: ?[]const u8, path: ?[]const u8) bool {
|
||||||
if (!std.mem.eql(u8, cookie.name, name)) return false;
|
if (!std.mem.eql(u8, cookie.name, name)) return false;
|
||||||
|
|
||||||
@@ -91,10 +94,15 @@ fn cookieMatches(cookie: *const Cookie, name: []const u8, domain: ?[]const u8, p
|
|||||||
if (path) |path_| {
|
if (path) |path_| {
|
||||||
if (!std.mem.eql(u8, cookie.path, path_)) return false;
|
if (!std.mem.eql(u8, cookie.path, path_)) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only matches the cookie on provided parameters
|
||||||
|
fn cookieAppliesTo(cookie: *const Cookie, domain: []const u8, path: []const u8) bool {
|
||||||
|
if (!std.mem.eql(u8, cookie.domain, domain)) return false;
|
||||||
|
return std.mem.startsWith(u8, path, cookie.path);
|
||||||
|
}
|
||||||
|
|
||||||
fn deleteCookies(cmd: anytype) !void {
|
fn deleteCookies(cmd: anytype) !void {
|
||||||
const params = (try cmd.params(struct {
|
const params = (try cmd.params(struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
@@ -107,11 +115,13 @@ fn deleteCookies(cmd: anytype) !void {
|
|||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
const cookies = &bc.session.cookie_jar.cookies;
|
const cookies = &bc.session.cookie_jar.cookies;
|
||||||
|
|
||||||
|
const uri = if (params.url) |url| std.Uri.parse(url) catch return error.InvalidParams else null;
|
||||||
|
|
||||||
var index = cookies.items.len;
|
var index = cookies.items.len;
|
||||||
while (index > 0) {
|
while (index > 0) {
|
||||||
index -= 1;
|
index -= 1;
|
||||||
const cookie = &cookies.items[index];
|
const cookie = &cookies.items[index];
|
||||||
const domain = try CdpStorage.percentEncodedDomain(cmd.arena, params.url, params.domain);
|
const domain = try CdpStorage.percentEncodedDomainOrHost(cmd.arena, uri, params.domain);
|
||||||
// TBD does chrome take the path from the url as default? (unlike setCookies)
|
// TBD does chrome take the path from the url as default? (unlike setCookies)
|
||||||
if (cookieMatches(cookie, params.name, domain, params.path)) {
|
if (cookieMatches(cookie, params.name, domain, params.path)) {
|
||||||
cookies.swapRemove(index).deinit();
|
cookies.swapRemove(index).deinit();
|
||||||
@@ -153,6 +163,33 @@ fn setCookies(cmd: anytype) !void {
|
|||||||
try cmd.sendResult(null, .{});
|
try cmd.sendResult(null, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getCookies(cmd: anytype) !void {
|
||||||
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
const params = (try cmd.params(struct {
|
||||||
|
urls: []const []const u8,
|
||||||
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
|
var urls = try std.ArrayListUnmanaged(CdpStorage.PreparedUri).initCapacity(cmd.arena, params.urls.len);
|
||||||
|
for (params.urls) |url| {
|
||||||
|
const uri = std.Uri.parse(url) catch return error.InvalidParams;
|
||||||
|
|
||||||
|
const host_component = uri.host orelse return error.InvalidParams;
|
||||||
|
const host = CdpStorage.toLower(try CdpStorage.percentEncode(cmd.arena, host_component, CdpStorage.isHostChar));
|
||||||
|
|
||||||
|
var path: []const u8 = try CdpStorage.percentEncode(cmd.arena, uri.path, CdpStorage.isPathChar);
|
||||||
|
if (path.len == 0) path = "/";
|
||||||
|
|
||||||
|
const secure = std.mem.eql(u8, uri.scheme, "https");
|
||||||
|
|
||||||
|
urls.appendAssumeCapacity(.{ .host = host, .path = path, .secure = secure });
|
||||||
|
}
|
||||||
|
|
||||||
|
var jar = &bc.session.cookie_jar;
|
||||||
|
jar.removeExpired(null);
|
||||||
|
const writer = CdpStorage.CookieWriter{ .cookies = jar.cookies.items, .urls = urls.items };
|
||||||
|
try cmd.sendResult(.{ .cookies = writer }, .{});
|
||||||
|
}
|
||||||
|
|
||||||
// Upsert a header into the headers array.
|
// Upsert a header into the headers array.
|
||||||
// returns true if the header was added, false if it was updated
|
// returns true if the header was added, false if it was updated
|
||||||
fn putAssumeCapacity(headers: *std.ArrayListUnmanaged(std.http.Header), extra: std.http.Header) bool {
|
fn putAssumeCapacity(headers: *std.ArrayListUnmanaged(std.http.Header), extra: std.http.Header) bool {
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ const Allocator = std.mem.Allocator;
|
|||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
const Cookie = @import("../../browser/storage/storage.zig").Cookie;
|
const Cookie = @import("../../browser/storage/storage.zig").Cookie;
|
||||||
const CookieJar = @import("../../browser/storage/storage.zig").CookieJar;
|
const CookieJar = @import("../../browser/storage/storage.zig").CookieJar;
|
||||||
|
pub const PreparedUri = @import("../../browser/storage/cookie.zig").PreparedUri;
|
||||||
|
pub const toLower = @import("../../browser/storage/cookie.zig").toLower;
|
||||||
|
|
||||||
pub fn processMessage(cmd: anytype) !void {
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
const action = std.meta.stringToEnum(enum {
|
const action = std.meta.stringToEnum(enum {
|
||||||
@@ -65,6 +67,7 @@ fn getCookies(cmd: anytype) !void {
|
|||||||
return error.UnknownBrowserContextId;
|
return error.UnknownBrowserContextId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bc.session.cookie_jar.removeExpired(null);
|
||||||
const cookies = CookieWriter{ .cookies = bc.session.cookie_jar.cookies.items };
|
const cookies = CookieWriter{ .cookies = bc.session.cookie_jar.cookies.items };
|
||||||
try cmd.sendResult(.{ .cookies = cookies }, .{});
|
try cmd.sendResult(.{ .cookies = cookies }, .{});
|
||||||
}
|
}
|
||||||
@@ -139,7 +142,8 @@ pub fn setCdpCookie(cookie_jar: *CookieJar, param: CdpCookie) !void {
|
|||||||
const a = arena.allocator();
|
const a = arena.allocator();
|
||||||
|
|
||||||
// NOTE: The param.url can affect the default domain, path, source port, and source scheme.
|
// NOTE: The param.url can affect the default domain, path, source port, and source scheme.
|
||||||
const domain = try percentEncodedDomain(a, param.url, param.domain) orelse return error.InvalidParams;
|
const uri = if (param.url) |url| std.Uri.parse(url) catch return error.InvalidParams else null;
|
||||||
|
const domain = try percentEncodedDomainOrHost(a, uri, param.domain) orelse return error.InvalidParams;
|
||||||
|
|
||||||
const cookie = Cookie{
|
const cookie = Cookie{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
@@ -160,31 +164,32 @@ pub fn setCdpCookie(cookie_jar: *CookieJar, param: CdpCookie) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Note: Chrome does not apply rules like removing a leading `.` from the domain.
|
// Note: Chrome does not apply rules like removing a leading `.` from the domain.
|
||||||
pub fn percentEncodedDomain(allocator: Allocator, default_url: ?[]const u8, domain: ?[]const u8) !?[]const u8 {
|
pub fn percentEncodedDomainOrHost(allocator: Allocator, default_url: ?std.Uri, domain: ?[]const u8) !?[]const u8 {
|
||||||
const toLower = @import("../../browser/storage/cookie.zig").toLower;
|
|
||||||
if (domain) |domain_| {
|
if (domain) |domain_| {
|
||||||
const output = try allocator.dupe(u8, domain_);
|
const output = try allocator.dupe(u8, domain_);
|
||||||
return toLower(output);
|
return toLower(output);
|
||||||
} else if (default_url) |url| {
|
} else if (default_url) |url| {
|
||||||
const uri = std.Uri.parse(url) catch return error.InvalidParams;
|
const host = url.host orelse return error.InvalidParams;
|
||||||
|
const output = try percentEncode(allocator, host, isHostChar); // TODO remove subdomains
|
||||||
var output: []u8 = undefined;
|
|
||||||
switch (uri.host orelse return error.InvalidParams) {
|
|
||||||
.raw => |str| {
|
|
||||||
var list = std.ArrayList(u8).init(allocator);
|
|
||||||
try list.ensureTotalCapacity(str.len); // Expect no precents needed
|
|
||||||
try std.Uri.Component.percentEncode(list.writer(), str, isHostChar);
|
|
||||||
output = list.items; // @memory retains memory used before growing
|
|
||||||
},
|
|
||||||
.percent_encoded => |str| {
|
|
||||||
output = try allocator.dupe(u8, str);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return toLower(output);
|
return toLower(output);
|
||||||
} else return null;
|
} else return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isHostChar(c: u8) bool {
|
pub fn percentEncode(arena: Allocator, component: std.Uri.Component, comptime isValidChar: fn (u8) bool) ![]u8 {
|
||||||
|
switch (component) {
|
||||||
|
.raw => |str| {
|
||||||
|
var list = std.ArrayList(u8).init(arena);
|
||||||
|
try list.ensureTotalCapacity(str.len); // Expect no precents needed
|
||||||
|
try std.Uri.Component.percentEncode(list.writer(), str, isValidChar);
|
||||||
|
return list.items; // @memory retains memory used before growing
|
||||||
|
},
|
||||||
|
.percent_encoded => |str| {
|
||||||
|
return try arena.dupe(u8, str);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isHostChar(c: u8) bool {
|
||||||
return switch (c) {
|
return switch (c) {
|
||||||
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_', '~' => true,
|
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_', '~' => true,
|
||||||
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' => true,
|
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' => true,
|
||||||
@@ -194,8 +199,18 @@ fn isHostChar(c: u8) bool {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn isPathChar(c: u8) bool {
|
||||||
|
return switch (c) {
|
||||||
|
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_', '~' => true,
|
||||||
|
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' => true,
|
||||||
|
'/', ':', '@' => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub const CookieWriter = struct {
|
pub const CookieWriter = struct {
|
||||||
cookies: []const Cookie,
|
cookies: []const Cookie,
|
||||||
|
urls: ?[]const PreparedUri = null,
|
||||||
|
|
||||||
pub fn jsonStringify(self: *const CookieWriter, w: anytype) !void {
|
pub fn jsonStringify(self: *const CookieWriter, w: anytype) !void {
|
||||||
self.writeCookies(w) catch |err| {
|
self.writeCookies(w) catch |err| {
|
||||||
@@ -207,49 +222,59 @@ pub const CookieWriter = struct {
|
|||||||
|
|
||||||
fn writeCookies(self: CookieWriter, w: anytype) !void {
|
fn writeCookies(self: CookieWriter, w: anytype) !void {
|
||||||
try w.beginArray();
|
try w.beginArray();
|
||||||
for (self.cookies) |*cookie| {
|
if (self.urls) |urls| {
|
||||||
try writeCookie(cookie, w);
|
for (self.cookies) |*cookie| {
|
||||||
|
for (urls) |*url| {
|
||||||
|
if (cookie.appliesTo(url, false, false)) { // TBD same_site, should we compare to the pages url?
|
||||||
|
try writeCookie(cookie, w);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (self.cookies) |*cookie| {
|
||||||
|
try writeCookie(cookie, w);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try w.endArray();
|
try w.endArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeCookie(cookie: *const Cookie, w: anytype) !void {
|
|
||||||
try w.beginObject();
|
|
||||||
{
|
|
||||||
try w.objectField("name");
|
|
||||||
try w.write(cookie.name);
|
|
||||||
|
|
||||||
try w.objectField("value");
|
|
||||||
try w.write(cookie.value);
|
|
||||||
|
|
||||||
try w.objectField("domain");
|
|
||||||
try w.write(cookie.domain);
|
|
||||||
|
|
||||||
try w.objectField("path");
|
|
||||||
try w.write(cookie.path);
|
|
||||||
|
|
||||||
try w.objectField("expires");
|
|
||||||
try w.write(cookie.expires orelse -1);
|
|
||||||
|
|
||||||
// TODO size
|
|
||||||
|
|
||||||
try w.objectField("httpOnly");
|
|
||||||
try w.write(cookie.http_only);
|
|
||||||
|
|
||||||
try w.objectField("secure");
|
|
||||||
try w.write(cookie.secure);
|
|
||||||
|
|
||||||
// TODO session
|
|
||||||
|
|
||||||
try w.objectField("sameSite");
|
|
||||||
switch (cookie.same_site) {
|
|
||||||
.none => try w.write("None"),
|
|
||||||
.lax => try w.write("Lax"),
|
|
||||||
.strict => try w.write("Strict"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO experimentals
|
|
||||||
}
|
|
||||||
try w.endObject();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
pub fn writeCookie(cookie: *const Cookie, w: anytype) !void {
|
||||||
|
try w.beginObject();
|
||||||
|
{
|
||||||
|
try w.objectField("name");
|
||||||
|
try w.write(cookie.name);
|
||||||
|
|
||||||
|
try w.objectField("value");
|
||||||
|
try w.write(cookie.value);
|
||||||
|
|
||||||
|
try w.objectField("domain");
|
||||||
|
try w.write(cookie.domain);
|
||||||
|
|
||||||
|
try w.objectField("path");
|
||||||
|
try w.write(cookie.path);
|
||||||
|
|
||||||
|
try w.objectField("expires");
|
||||||
|
try w.write(cookie.expires orelse -1);
|
||||||
|
|
||||||
|
// TODO size
|
||||||
|
|
||||||
|
try w.objectField("httpOnly");
|
||||||
|
try w.write(cookie.http_only);
|
||||||
|
|
||||||
|
try w.objectField("secure");
|
||||||
|
try w.write(cookie.secure);
|
||||||
|
|
||||||
|
// TODO session
|
||||||
|
|
||||||
|
try w.objectField("sameSite");
|
||||||
|
switch (cookie.same_site) {
|
||||||
|
.none => try w.write("None"),
|
||||||
|
.lax => try w.write("Lax"),
|
||||||
|
.strict => try w.write("Strict"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO experimentals
|
||||||
|
}
|
||||||
|
try w.endObject();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user