mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 14:33:47 +00:00
Remove std.Uri from cookies
Everything now works on a [:0]const u8, with browser/URL.zig for parsing
This commit is contained in:
@@ -51,7 +51,7 @@ arena: Allocator,
|
|||||||
transfer_arena: Allocator,
|
transfer_arena: Allocator,
|
||||||
|
|
||||||
executor: js.ExecutionWorld,
|
executor: js.ExecutionWorld,
|
||||||
cookie_jar: storage.Jar,
|
cookie_jar: storage.Cookie.Jar,
|
||||||
storage_shed: storage.Shed,
|
storage_shed: storage.Shed,
|
||||||
|
|
||||||
page: ?*Page = null,
|
page: ?*Page = null,
|
||||||
@@ -73,7 +73,7 @@ pub fn init(self: *Session, browser: *Browser) !void {
|
|||||||
.storage_shed = .{},
|
.storage_shed = .{},
|
||||||
.queued_navigation = null,
|
.queued_navigation = null,
|
||||||
.arena = browser.session_arena.allocator(),
|
.arena = browser.session_arena.allocator(),
|
||||||
.cookie_jar = storage.Jar.init(allocator),
|
.cookie_jar = storage.Cookie.Jar.init(allocator),
|
||||||
.transfer_arena = browser.transfer_arena.allocator(),
|
.transfer_arena = browser.transfer_arena.allocator(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,10 @@ pub fn getProtocol(raw: [:0]const u8) []const u8 {
|
|||||||
return raw[0 .. pos + 1];
|
return raw[0 .. pos + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn isHTTPS(raw: [:0]const u8) bool {
|
||||||
|
return std.mem.startsWith(u8, raw, "https:");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getHostname(raw: [:0]const u8) []const u8 {
|
pub fn getHostname(raw: [:0]const u8) []const u8 {
|
||||||
const host = getHost(raw);
|
const host = getHost(raw);
|
||||||
const pos = std.mem.lastIndexOfScalar(u8, host, ':') orelse return host;
|
const pos = std.mem.lastIndexOfScalar(u8, host, ':') orelse return host;
|
||||||
|
|||||||
@@ -17,179 +17,16 @@
|
|||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Uri = std.Uri;
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
|
const URL = @import("../../URL.zig");
|
||||||
const log = @import("../../../log.zig");
|
const log = @import("../../../log.zig");
|
||||||
const DateTime = @import("../../../datetime.zig").DateTime;
|
const DateTime = @import("../../../datetime.zig").DateTime;
|
||||||
const public_suffix_list = @import("../../../data/public_suffix_list.zig").lookup;
|
const public_suffix_list = @import("../../../data/public_suffix_list.zig").lookup;
|
||||||
|
|
||||||
pub const LookupOpts = struct {
|
const Cookie = @This();
|
||||||
request_time: ?i64 = null,
|
|
||||||
origin_uri: ?*const Uri = null,
|
|
||||||
is_http: bool,
|
|
||||||
is_navigation: bool = true,
|
|
||||||
prefix: ?[]const u8 = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Jar = struct {
|
|
||||||
allocator: Allocator,
|
|
||||||
cookies: std.ArrayListUnmanaged(Cookie),
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) Jar {
|
|
||||||
return .{
|
|
||||||
.cookies = .{},
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Jar) void {
|
|
||||||
for (self.cookies.items) |c| {
|
|
||||||
c.deinit();
|
|
||||||
}
|
|
||||||
self.cookies.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clearRetainingCapacity(self: *Jar) void {
|
|
||||||
for (self.cookies.items) |c| {
|
|
||||||
c.deinit();
|
|
||||||
}
|
|
||||||
self.cookies.clearRetainingCapacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(
|
|
||||||
self: *Jar,
|
|
||||||
cookie: Cookie,
|
|
||||||
request_time: i64,
|
|
||||||
) !void {
|
|
||||||
const is_expired = isCookieExpired(&cookie, request_time);
|
|
||||||
defer if (is_expired) {
|
|
||||||
cookie.deinit();
|
|
||||||
};
|
|
||||||
|
|
||||||
for (self.cookies.items, 0..) |*c, i| {
|
|
||||||
if (areCookiesEqual(&cookie, c)) {
|
|
||||||
c.deinit();
|
|
||||||
if (is_expired) {
|
|
||||||
_ = self.cookies.swapRemove(i);
|
|
||||||
} else {
|
|
||||||
self.cookies.items[i] = cookie;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_expired) {
|
|
||||||
try self.cookies.append(self.allocator, cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 std.Uri, writer: anytype, opts: LookupOpts) !void {
|
|
||||||
const target = PreparedUri{
|
|
||||||
.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);
|
|
||||||
|
|
||||||
removeExpired(self, opts.request_time);
|
|
||||||
|
|
||||||
var first = true;
|
|
||||||
for (self.cookies.items) |*cookie| {
|
|
||||||
if (!cookie.appliesTo(&target, same_site, opts.is_navigation, opts.is_http)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have a match!
|
|
||||||
if (first) {
|
|
||||||
if (opts.prefix) |prefix| {
|
|
||||||
try writer.writeAll(prefix);
|
|
||||||
}
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
try writer.writeAll("; ");
|
|
||||||
}
|
|
||||||
try writeCookie(cookie, writer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn populateFromResponse(self: *Jar, uri: *const Uri, set_cookie: []const u8) !void {
|
|
||||||
const c = Cookie.parse(self.allocator, uri, set_cookie) catch |err| {
|
|
||||||
log.warn(.page, "cookie parse failed", .{ .raw = set_cookie, .err = err });
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
const now = std.time.timestamp();
|
|
||||||
try self.add(c, now);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
const ce = cookie.expires orelse return false;
|
|
||||||
return ce <= @as(f64, @floatFromInt(now));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn areCookiesEqual(a: *const Cookie, b: *const Cookie) bool {
|
|
||||||
if (std.mem.eql(u8, a.name, b.name) == false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, a.domain, b.domain) == false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, a.path, b.path) == false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn areSameSite(origin_uri_: ?*const std.Uri, target_host: []const u8) !bool {
|
|
||||||
const origin_uri = origin_uri_ orelse return true;
|
|
||||||
const origin_host = (origin_uri.host orelse return error.InvalidURI).percent_encoded;
|
|
||||||
|
|
||||||
// common case
|
|
||||||
if (std.mem.eql(u8, target_host, origin_host)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std.mem.eql(u8, findSecondLevelDomain(target_host), findSecondLevelDomain(origin_host));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn findSecondLevelDomain(host: []const u8) []const u8 {
|
|
||||||
var i = std.mem.lastIndexOfScalar(u8, host, '.') orelse return host;
|
|
||||||
while (true) {
|
|
||||||
i = std.mem.lastIndexOfScalar(u8, host[0..i], '.') orelse return host;
|
|
||||||
const strip = i + 1;
|
|
||||||
if (public_suffix_list(host[strip..]) == false) {
|
|
||||||
return host[strip..];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Cookie = struct {
|
|
||||||
arena: ArenaAllocator,
|
arena: ArenaAllocator,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
value: []const u8,
|
value: []const u8,
|
||||||
@@ -224,7 +61,7 @@ pub const Cookie = struct {
|
|||||||
// Invalid attribute values? Ignore.
|
// Invalid attribute values? Ignore.
|
||||||
// Duplicate attributes - use the last valid
|
// Duplicate attributes - use the last valid
|
||||||
// Value-less attributes with a value? Ignore the value
|
// Value-less attributes with a value? Ignore the value
|
||||||
pub fn parse(allocator: Allocator, uri: *const std.Uri, str: []const u8) !Cookie {
|
pub fn parse(allocator: Allocator, url: [:0]const u8, str: []const u8) !Cookie {
|
||||||
try validateCookieString(str);
|
try validateCookieString(str);
|
||||||
|
|
||||||
const cookie_name, const cookie_value, const rest = parseNameValue(str) catch {
|
const cookie_name, const cookie_value, const rest = parseNameValue(str) catch {
|
||||||
@@ -287,8 +124,8 @@ pub const Cookie = struct {
|
|||||||
const aa = arena.allocator();
|
const aa = arena.allocator();
|
||||||
const owned_name = try aa.dupe(u8, cookie_name);
|
const owned_name = try aa.dupe(u8, cookie_name);
|
||||||
const owned_value = try aa.dupe(u8, cookie_value);
|
const owned_value = try aa.dupe(u8, cookie_value);
|
||||||
const owned_path = try parsePath(aa, uri, path);
|
const owned_path = try parsePath(aa, url, path);
|
||||||
const owned_domain = try parseDomain(aa, uri, domain);
|
const owned_domain = try parseDomain(aa, url, domain);
|
||||||
|
|
||||||
var normalized_expires: ?f64 = null;
|
var normalized_expires: ?f64 = null;
|
||||||
if (max_age) |ma| {
|
if (max_age) |ma| {
|
||||||
@@ -380,7 +217,7 @@ pub const Cookie = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parsePath(arena: Allocator, uri: ?*const std.Uri, explicit_path: ?[]const u8) ![]const u8 {
|
pub fn parsePath(arena: Allocator, url_: ?[:0]const u8, explicit_path: ?[]const u8) ![]const u8 {
|
||||||
// path attribute value either begins with a '/' or we
|
// path attribute value either begins with a '/' or we
|
||||||
// ignore it and use the "default-path" algorithm
|
// ignore it and use the "default-path" algorithm
|
||||||
if (explicit_path) |path| {
|
if (explicit_path) |path| {
|
||||||
@@ -390,10 +227,9 @@ pub const Cookie = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// default-path
|
// default-path
|
||||||
const url_path = (uri orelse return "/").path;
|
const url = url_ orelse return "/";
|
||||||
|
const url_path = URL.getPathname(url);
|
||||||
const either = url_path.percent_encoded;
|
if (url_path.len == 0 or (url_path.len == 1 and url_path[0] == '/')) {
|
||||||
if (either.len == 0 or (either.len == 1 and either[0] == '/')) {
|
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,11 +240,10 @@ pub const Cookie = struct {
|
|||||||
return try arena.dupe(u8, owned_path[0 .. last + 1]);
|
return try arena.dupe(u8, owned_path[0 .. last + 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parseDomain(arena: Allocator, uri: ?*const std.Uri, explicit_domain: ?[]const u8) ![]const u8 {
|
pub fn parseDomain(arena: Allocator, url_: ?[:0]const u8, explicit_domain: ?[]const u8) ![]const u8 {
|
||||||
var encoded_host: ?[]const u8 = null;
|
var encoded_host: ?[]const u8 = null;
|
||||||
if (uri) |uri_| {
|
if (url_) |url| {
|
||||||
const uri_host = uri_.host orelse return error.InvalidURI;
|
const host = try percentEncode(arena, URL.getHostname(url), isHostChar);
|
||||||
const host = try percentEncode(arena, uri_host, isHostChar);
|
|
||||||
_ = toLower(host);
|
_ = toLower(host);
|
||||||
encoded_host = host;
|
encoded_host = host;
|
||||||
}
|
}
|
||||||
@@ -439,17 +274,10 @@ pub const Cookie = struct {
|
|||||||
return encoded_host orelse return error.InvalidDomain; // default-domain
|
return encoded_host orelse return error.InvalidDomain; // default-domain
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn percentEncode(arena: Allocator, component: std.Uri.Component, comptime isValidChar: fn (u8) bool) ![]u8 {
|
pub fn percentEncode(arena: Allocator, part: []const u8, comptime isValidChar: fn (u8) bool) ![]u8 {
|
||||||
switch (component) {
|
var aw = try std.Io.Writer.Allocating.initCapacity(arena, part.len);
|
||||||
.raw => |str| {
|
try std.Uri.Component.percentEncode(&aw.writer, part, isValidChar);
|
||||||
var aw = try std.Io.Writer.Allocating.initCapacity(arena, str.len);
|
|
||||||
try std.Uri.Component.percentEncode(&aw.writer, str, isValidChar);
|
|
||||||
return aw.written(); // @memory retains memory used before growing
|
return aw.written(); // @memory retains memory used before growing
|
||||||
},
|
|
||||||
.percent_encoded => |str| {
|
|
||||||
return try arena.dupe(u8, str);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isHostChar(c: u8) bool {
|
pub fn isHostChar(c: u8) bool {
|
||||||
@@ -551,7 +379,169 @@ pub const Cookie = struct {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const Jar = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
cookies: std.ArrayListUnmanaged(Cookie),
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) Jar {
|
||||||
|
return .{
|
||||||
|
.cookies = .{},
|
||||||
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Jar) void {
|
||||||
|
for (self.cookies.items) |c| {
|
||||||
|
c.deinit();
|
||||||
|
}
|
||||||
|
self.cookies.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearRetainingCapacity(self: *Jar) void {
|
||||||
|
for (self.cookies.items) |c| {
|
||||||
|
c.deinit();
|
||||||
|
}
|
||||||
|
self.cookies.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(
|
||||||
|
self: *Jar,
|
||||||
|
cookie: Cookie,
|
||||||
|
request_time: i64,
|
||||||
|
) !void {
|
||||||
|
const is_expired = isCookieExpired(&cookie, request_time);
|
||||||
|
defer if (is_expired) {
|
||||||
|
cookie.deinit();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (self.cookies.items, 0..) |*c, i| {
|
||||||
|
if (areCookiesEqual(&cookie, c)) {
|
||||||
|
c.deinit();
|
||||||
|
if (is_expired) {
|
||||||
|
_ = self.cookies.swapRemove(i);
|
||||||
|
} else {
|
||||||
|
self.cookies.items[i] = cookie;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_expired) {
|
||||||
|
try self.cookies.append(self.allocator, cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 const LookupOpts = struct {
|
||||||
|
is_http: bool,
|
||||||
|
request_time: ?i64 = null,
|
||||||
|
is_navigation: bool = true,
|
||||||
|
prefix: ?[]const u8 = null,
|
||||||
|
origin_url: ?[:0]const u8 = null,
|
||||||
|
};
|
||||||
|
pub fn forRequest(self: *Jar, target_url: [:0]const u8, writer: anytype, opts: LookupOpts) !void {
|
||||||
|
const target = PreparedUri{
|
||||||
|
.host = URL.getHostname(target_url),
|
||||||
|
.path = URL.getPathname(target_url),
|
||||||
|
.secure = URL.isHTTPS(target_url),
|
||||||
|
};
|
||||||
|
const same_site = try areSameSite(opts.origin_url, target.host);
|
||||||
|
|
||||||
|
removeExpired(self, opts.request_time);
|
||||||
|
|
||||||
|
var first = true;
|
||||||
|
for (self.cookies.items) |*cookie| {
|
||||||
|
if (!cookie.appliesTo(&target, same_site, opts.is_navigation, opts.is_http)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have a match!
|
||||||
|
if (first) {
|
||||||
|
if (opts.prefix) |prefix| {
|
||||||
|
try writer.writeAll(prefix);
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
try writer.writeAll("; ");
|
||||||
|
}
|
||||||
|
try writeCookie(cookie, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn populateFromResponse(self: *Jar, url: [:0]const u8, set_cookie: []const u8) !void {
|
||||||
|
const c = Cookie.parse(self.allocator, url, set_cookie) catch |err| {
|
||||||
|
log.warn(.page, "cookie parse failed", .{ .raw = set_cookie, .err = err });
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const now = std.time.timestamp();
|
||||||
|
try self.add(c, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
const ce = cookie.expires orelse return false;
|
||||||
|
return ce <= @as(f64, @floatFromInt(now));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn areCookiesEqual(a: *const Cookie, b: *const Cookie) bool {
|
||||||
|
if (std.mem.eql(u8, a.name, b.name) == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, a.domain, b.domain) == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, a.path, b.path) == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn areSameSite(origin_url_: ?[:0]const u8, target_host: []const u8) !bool {
|
||||||
|
const origin_url = origin_url_ orelse return true;
|
||||||
|
const origin_host = URL.getHostname(origin_url);
|
||||||
|
|
||||||
|
// common case
|
||||||
|
if (std.mem.eql(u8, target_host, origin_host)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std.mem.eql(u8, findSecondLevelDomain(target_host), findSecondLevelDomain(origin_host));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findSecondLevelDomain(host: []const u8) []const u8 {
|
||||||
|
var i = std.mem.lastIndexOfScalar(u8, host, '.') orelse return host;
|
||||||
|
while (true) {
|
||||||
|
i = std.mem.lastIndexOfScalar(u8, host[0..i], '.') orelse return host;
|
||||||
|
const strip = i + 1;
|
||||||
|
if (public_suffix_list(host[strip..]) == false) {
|
||||||
|
return host[strip..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const PreparedUri = struct {
|
pub const PreparedUri = struct {
|
||||||
host: []const u8, // Percent encoded, lower case
|
host: []const u8, // Percent encoded, lower case
|
||||||
@@ -571,7 +561,7 @@ fn trimRight(str: []const u8) []const u8 {
|
|||||||
return std.mem.trimLeft(u8, str, &std.ascii.whitespace);
|
return std.mem.trimLeft(u8, str, &std.ascii.whitespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toLower(str: []u8) []u8 {
|
fn toLower(str: []u8) []u8 {
|
||||||
for (str, 0..) |c, i| {
|
for (str, 0..) |c, i| {
|
||||||
str[i] = std.ascii.toLower(c);
|
str[i] = std.ascii.toLower(c);
|
||||||
}
|
}
|
||||||
@@ -579,6 +569,7 @@ pub fn toLower(str: []u8) []u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const testing = @import("../../../testing.zig");
|
const testing = @import("../../../testing.zig");
|
||||||
|
const test_url = "http://lightpanda.io/";
|
||||||
test "cookie: findSecondLevelDomain" {
|
test "cookie: findSecondLevelDomain" {
|
||||||
const cases = [_]struct { []const u8, []const u8 }{
|
const cases = [_]struct { []const u8, []const u8 }{
|
||||||
.{ "", "" },
|
.{ "", "" },
|
||||||
@@ -619,37 +610,37 @@ test "Jar: add" {
|
|||||||
defer jar.deinit();
|
defer jar.deinit();
|
||||||
try expectCookies(&.{}, jar);
|
try expectCookies(&.{}, jar);
|
||||||
|
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "over=9000;Max-Age=0"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "over=9000;Max-Age=0"), now);
|
||||||
try expectCookies(&.{}, jar);
|
try expectCookies(&.{}, jar);
|
||||||
|
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "over=9000"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "over=9000"), now);
|
||||||
try expectCookies(&.{.{ "over", "9000" }}, jar);
|
try expectCookies(&.{.{ "over", "9000" }}, jar);
|
||||||
|
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "over=9000!!"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "over=9000!!"), now);
|
||||||
try expectCookies(&.{.{ "over", "9000!!" }}, jar);
|
try expectCookies(&.{.{ "over", "9000!!" }}, jar);
|
||||||
|
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "spice=flow"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "spice=flow"), now);
|
||||||
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flow" } }, jar);
|
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flow" } }, jar);
|
||||||
|
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "spice=flows;Path=/"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "spice=flows;Path=/"), now);
|
||||||
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" } }, jar);
|
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" } }, jar);
|
||||||
|
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "over=9001;Path=/other"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "over=9001;Path=/other"), now);
|
||||||
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" }, .{ "over", "9001" } }, jar);
|
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" }, .{ "over", "9001" } }, jar);
|
||||||
|
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "over=9002;Path=/;Domain=lightpanda.io"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "over=9002;Path=/;Domain=lightpanda.io"), now);
|
||||||
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" }, .{ "over", "9001" }, .{ "over", "9002" } }, jar);
|
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" }, .{ "over", "9001" }, .{ "over", "9002" } }, jar);
|
||||||
|
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "over=x;Path=/other;Max-Age=-200"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "over=x;Path=/other;Max-Age=-200"), now);
|
||||||
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" }, .{ "over", "9002" } }, jar);
|
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" }, .{ "over", "9002" } }, jar);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Jar: forRequest" {
|
test "Jar: forRequest" {
|
||||||
const expectCookies = struct {
|
const expectCookies = struct {
|
||||||
fn expect(expected: []const u8, jar: *Jar, target_uri: Uri, opts: LookupOpts) !void {
|
fn expect(expected: []const u8, jar: *Jar, target_url: [:0]const u8, opts: Jar.LookupOpts) !void {
|
||||||
var arr: std.ArrayListUnmanaged(u8) = .empty;
|
var arr: std.ArrayListUnmanaged(u8) = .empty;
|
||||||
defer arr.deinit(testing.allocator);
|
defer arr.deinit(testing.allocator);
|
||||||
try jar.forRequest(&target_uri, arr.writer(testing.allocator), opts);
|
try jar.forRequest(target_url, arr.writer(testing.allocator), opts);
|
||||||
try testing.expectEqual(expected, arr.items);
|
try testing.expectEqual(expected, arr.items);
|
||||||
}
|
}
|
||||||
}.expect;
|
}.expect;
|
||||||
@@ -659,131 +650,131 @@ test "Jar: forRequest" {
|
|||||||
var jar = Jar.init(testing.allocator);
|
var jar = Jar.init(testing.allocator);
|
||||||
defer jar.deinit();
|
defer jar.deinit();
|
||||||
|
|
||||||
const test_uri_2 = Uri.parse("http://test.lightpanda.io/") catch unreachable;
|
const url2 = "http://test.lightpanda.io/";
|
||||||
|
|
||||||
{
|
{
|
||||||
// test with no cookies
|
// test with no cookies
|
||||||
try expectCookies("", &jar, test_uri, .{ .is_http = true });
|
try expectCookies("", &jar, test_url, .{ .is_http = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "global1=1"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "global1=1"), now);
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "global2=2;Max-Age=30;domain=lightpanda.io"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "global2=2;Max-Age=30;domain=lightpanda.io"), now);
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "path1=3;Path=/about"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "path1=3;Path=/about"), now);
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "path2=4;Path=/docs/"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "path2=4;Path=/docs/"), now);
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "secure=5;Secure"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "secure=5;Secure"), now);
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "sitenone=6;SameSite=None;Path=/x/;Secure"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "sitenone=6;SameSite=None;Path=/x/;Secure"), now);
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "sitelax=7;SameSite=Lax;Path=/x/"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "sitelax=7;SameSite=Lax;Path=/x/"), now);
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri, "sitestrict=8;SameSite=Strict;Path=/x/"), now);
|
try jar.add(try Cookie.parse(testing.allocator, test_url, "sitestrict=8;SameSite=Strict;Path=/x/"), now);
|
||||||
try jar.add(try Cookie.parse(testing.allocator, &test_uri_2, "domain1=9;domain=test.lightpanda.io"), now);
|
try jar.add(try Cookie.parse(testing.allocator, url2, "domain1=9;domain=test.lightpanda.io"), now);
|
||||||
|
|
||||||
// nothing fancy here
|
// nothing fancy here
|
||||||
try expectCookies("global1=1; global2=2", &jar, test_uri, .{ .is_http = true });
|
try expectCookies("global1=1; global2=2", &jar, test_url, .{ .is_http = true });
|
||||||
try expectCookies("global1=1; global2=2", &jar, test_uri, .{ .origin_uri = &test_uri, .is_navigation = false, .is_http = true });
|
try expectCookies("global1=1; global2=2", &jar, test_url, .{ .origin_url = test_url, .is_navigation = false, .is_http = true });
|
||||||
|
|
||||||
// We have a cookie where Domain=lightpanda.io
|
// We have a cookie where Domain=lightpanda.io
|
||||||
// This should _not_ match xyxlightpanda.io
|
// This should _not_ match xyxlightpanda.io
|
||||||
try expectCookies("", &jar, try std.Uri.parse("http://anothersitelightpanda.io/"), .{
|
try expectCookies("", &jar, "http://anothersitelightpanda.io/", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// matching path without trailing /
|
// matching path without trailing /
|
||||||
try expectCookies("global1=1; global2=2; path1=3", &jar, try std.Uri.parse("http://lightpanda.io/about"), .{
|
try expectCookies("global1=1; global2=2; path1=3", &jar, "http://lightpanda.io/about", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// incomplete prefix path
|
// incomplete prefix path
|
||||||
try expectCookies("global1=1; global2=2", &jar, try std.Uri.parse("http://lightpanda.io/abou"), .{
|
try expectCookies("global1=1; global2=2", &jar, "http://lightpanda.io/abou", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// path doesn't match
|
// path doesn't match
|
||||||
try expectCookies("global1=1; global2=2", &jar, try std.Uri.parse("http://lightpanda.io/aboutus"), .{
|
try expectCookies("global1=1; global2=2", &jar, "http://lightpanda.io/aboutus", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// path doesn't match cookie directory
|
// path doesn't match cookie directory
|
||||||
try expectCookies("global1=1; global2=2", &jar, try std.Uri.parse("http://lightpanda.io/docs"), .{
|
try expectCookies("global1=1; global2=2", &jar, "http://lightpanda.io/docs", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// exact directory match
|
// exact directory match
|
||||||
try expectCookies("global1=1; global2=2; path2=4", &jar, try std.Uri.parse("http://lightpanda.io/docs/"), .{
|
try expectCookies("global1=1; global2=2; path2=4", &jar, "http://lightpanda.io/docs/", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// sub directory match
|
// sub directory match
|
||||||
try expectCookies("global1=1; global2=2; path2=4", &jar, try std.Uri.parse("http://lightpanda.io/docs/more"), .{
|
try expectCookies("global1=1; global2=2; path2=4", &jar, "http://lightpanda.io/docs/more", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// secure
|
// secure
|
||||||
try expectCookies("global1=1; global2=2; secure=5", &jar, try std.Uri.parse("https://lightpanda.io/"), .{
|
try expectCookies("global1=1; global2=2; secure=5", &jar, "https://lightpanda.io/", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// navigational cross domain, secure
|
// navigational cross domain, secure
|
||||||
try expectCookies("global1=1; global2=2; secure=5; sitenone=6; sitelax=7", &jar, try std.Uri.parse("https://lightpanda.io/x/"), .{
|
try expectCookies("global1=1; global2=2; secure=5; sitenone=6; sitelax=7", &jar, "https://lightpanda.io/x/", .{
|
||||||
.origin_uri = &(try std.Uri.parse("https://example.com/")),
|
.origin_url = "https://example.com/",
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// navigational cross domain, insecure
|
// navigational cross domain, insecure
|
||||||
try expectCookies("global1=1; global2=2; sitelax=7", &jar, try std.Uri.parse("http://lightpanda.io/x/"), .{
|
try expectCookies("global1=1; global2=2; sitelax=7", &jar, "http://lightpanda.io/x/", .{
|
||||||
.origin_uri = &(try std.Uri.parse("https://example.com/")),
|
.origin_url = "https://example.com/",
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// non-navigational cross domain, insecure
|
// non-navigational cross domain, insecure
|
||||||
try expectCookies("", &jar, try std.Uri.parse("http://lightpanda.io/x/"), .{
|
try expectCookies("", &jar, "http://lightpanda.io/x/", .{
|
||||||
.origin_uri = &(try std.Uri.parse("https://example.com/")),
|
.origin_url = "https://example.com/",
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
.is_navigation = false,
|
.is_navigation = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// non-navigational cross domain, secure
|
// non-navigational cross domain, secure
|
||||||
try expectCookies("sitenone=6", &jar, try std.Uri.parse("https://lightpanda.io/x/"), .{
|
try expectCookies("sitenone=6", &jar, "https://lightpanda.io/x/", .{
|
||||||
.origin_uri = &(try std.Uri.parse("https://example.com/")),
|
.origin_url = "https://example.com/",
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
.is_navigation = false,
|
.is_navigation = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// non-navigational same origin
|
// non-navigational same origin
|
||||||
try expectCookies("global1=1; global2=2; sitelax=7; sitestrict=8", &jar, try std.Uri.parse("http://lightpanda.io/x/"), .{
|
try expectCookies("global1=1; global2=2; sitelax=7; sitestrict=8", &jar, "http://lightpanda.io/x/", .{
|
||||||
.origin_uri = &(try std.Uri.parse("https://lightpanda.io/")),
|
.origin_url = "https://lightpanda.io/",
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
.is_navigation = false,
|
.is_navigation = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// exact domain match + suffix
|
// exact domain match + suffix
|
||||||
try expectCookies("global2=2; domain1=9", &jar, try std.Uri.parse("http://test.lightpanda.io/"), .{
|
try expectCookies("global2=2; domain1=9", &jar, "http://test.lightpanda.io/", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// domain suffix match + suffix
|
// domain suffix match + suffix
|
||||||
try expectCookies("global2=2; domain1=9", &jar, try std.Uri.parse("http://1.test.lightpanda.io/"), .{
|
try expectCookies("global2=2; domain1=9", &jar, "http://1.test.lightpanda.io/", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// non-matching domain
|
// non-matching domain
|
||||||
try expectCookies("global2=2", &jar, try std.Uri.parse("http://other.lightpanda.io/"), .{
|
try expectCookies("global2=2", &jar, "http://other.lightpanda.io/", .{
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const l = jar.cookies.items.len;
|
const l = jar.cookies.items.len;
|
||||||
try expectCookies("global1=1", &jar, test_uri, .{
|
try expectCookies("global1=1", &jar, test_url, .{
|
||||||
.request_time = now + 100,
|
.request_time = now + 100,
|
||||||
.origin_uri = &test_uri,
|
.origin_url = test_url,
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
});
|
});
|
||||||
try testing.expectEqual(l - 1, jar.cookies.items.len);
|
try testing.expectEqual(l - 1, jar.cookies.items.len);
|
||||||
@@ -979,9 +970,8 @@ const ExpectedCookie = struct {
|
|||||||
same_site: Cookie.SameSite = .lax,
|
same_site: Cookie.SameSite = .lax,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn expectCookie(expected: ExpectedCookie, url: []const u8, set_cookie: []const u8) !void {
|
fn expectCookie(expected: ExpectedCookie, url: [:0]const u8, set_cookie: []const u8) !void {
|
||||||
const uri = try Uri.parse(url);
|
var cookie = try Cookie.parse(testing.allocator, url, set_cookie);
|
||||||
var cookie = try Cookie.parse(testing.allocator, &uri, set_cookie);
|
|
||||||
defer cookie.deinit();
|
defer cookie.deinit();
|
||||||
|
|
||||||
try testing.expectEqual(expected.name, cookie.name);
|
try testing.expectEqual(expected.name, cookie.name);
|
||||||
@@ -995,9 +985,8 @@ fn expectCookie(expected: ExpectedCookie, url: []const u8, set_cookie: []const u
|
|||||||
try testing.expectDelta(expected.expires, cookie.expires, 2.0);
|
try testing.expectDelta(expected.expires, cookie.expires, 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expectAttribute(expected: anytype, url: ?[]const u8, set_cookie: []const u8) !void {
|
fn expectAttribute(expected: anytype, url_: ?[:0]const u8, set_cookie: []const u8) !void {
|
||||||
const uri = if (url) |u| try Uri.parse(u) else test_uri;
|
var cookie = try Cookie.parse(testing.allocator, url_ orelse test_url, set_cookie);
|
||||||
var cookie = try Cookie.parse(testing.allocator, &uri, set_cookie);
|
|
||||||
defer cookie.deinit();
|
defer cookie.deinit();
|
||||||
|
|
||||||
inline for (@typeInfo(@TypeOf(expected)).@"struct".fields) |f| {
|
inline for (@typeInfo(@TypeOf(expected)).@"struct".fields) |f| {
|
||||||
@@ -1012,9 +1001,6 @@ fn expectAttribute(expected: anytype, url: ?[]const u8, set_cookie: []const u8)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expectError(expected: anyerror, url: ?[]const u8, set_cookie: []const u8) !void {
|
fn expectError(expected: anyerror, url: ?[:0]const u8, set_cookie: []const u8) !void {
|
||||||
const uri = if (url) |u| try Uri.parse(u) else test_uri;
|
try testing.expectError(expected, Cookie.parse(testing.allocator, url orelse test_url, set_cookie));
|
||||||
try testing.expectError(expected, Cookie.parse(testing.allocator, &uri, set_cookie));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const test_uri = Uri.parse("http://lightpanda.io/") catch unreachable;
|
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ pub fn registerTypes() []const type {
|
|||||||
return &.{Lookup};
|
return &.{Lookup};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Jar = @import("cookie.zig").Jar;
|
pub const Cookie = @import("Cookie.zig");
|
||||||
pub const Cookie = @import("cookie.zig").Cookie;
|
|
||||||
|
|
||||||
pub const Shed = struct {
|
pub const Shed = struct {
|
||||||
_origins: std.StringHashMapUnmanaged(*Bucket) = .empty,
|
_origins: std.StringHashMapUnmanaged(*Bucket) = .empty,
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notific
|
|||||||
log.debug(.cdp, "request intercept", .{
|
log.debug(.cdp, "request intercept", .{
|
||||||
.state = "paused",
|
.state = "paused",
|
||||||
.id = transfer.id,
|
.id = transfer.id,
|
||||||
.url = transfer.uri,
|
.url = transfer.url,
|
||||||
});
|
});
|
||||||
// Await either continueRequest, failRequest or fulfillRequest
|
// Await either continueRequest, failRequest or fulfillRequest
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ fn continueRequest(cmd: anytype) !void {
|
|||||||
log.debug(.cdp, "request intercept", .{
|
log.debug(.cdp, "request intercept", .{
|
||||||
.state = "continue",
|
.state = "continue",
|
||||||
.id = transfer.id,
|
.id = transfer.id,
|
||||||
.url = transfer.uri,
|
.url = transfer.url,
|
||||||
.new_url = params.url,
|
.new_url = params.url,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -342,7 +342,7 @@ fn fulfillRequest(cmd: anytype) !void {
|
|||||||
log.debug(.cdp, "request intercept", .{
|
log.debug(.cdp, "request intercept", .{
|
||||||
.state = "fulfilled",
|
.state = "fulfilled",
|
||||||
.id = transfer.id,
|
.id = transfer.id,
|
||||||
.url = transfer.uri,
|
.url = transfer.url,
|
||||||
.status = params.responseCode,
|
.status = params.responseCode,
|
||||||
.body = params.body != null,
|
.body = params.body != null,
|
||||||
});
|
});
|
||||||
@@ -376,7 +376,7 @@ fn failRequest(cmd: anytype) !void {
|
|||||||
log.info(.cdp, "request intercept", .{
|
log.info(.cdp, "request intercept", .{
|
||||||
.state = "fail",
|
.state = "fail",
|
||||||
.id = request_id,
|
.id = request_id,
|
||||||
.url = transfer.uri,
|
.url = transfer.url,
|
||||||
.reason = params.errorReason,
|
.reason = params.errorReason,
|
||||||
});
|
});
|
||||||
return cmd.sendResult(null, .{});
|
return cmd.sendResult(null, .{});
|
||||||
@@ -420,7 +420,7 @@ pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Noti
|
|||||||
log.debug(.cdp, "request auth required", .{
|
log.debug(.cdp, "request auth required", .{
|
||||||
.state = "paused",
|
.state = "paused",
|
||||||
.id = transfer.id,
|
.id = transfer.id,
|
||||||
.url = transfer.uri,
|
.url = transfer.url,
|
||||||
});
|
});
|
||||||
// Await continueWithAuth
|
// Await continueWithAuth
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const CdpStorage = @import("storage.zig");
|
const CdpStorage = @import("storage.zig");
|
||||||
|
const URL = @import("../../browser/URL.zig");
|
||||||
const Transfer = @import("../../http/Client.zig").Transfer;
|
const Transfer = @import("../../http/Client.zig").Transfer;
|
||||||
const Notification = @import("../../Notification.zig");
|
const Notification = @import("../../Notification.zig");
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ fn cookieMatches(cookie: *const Cookie, name: []const u8, domain: ?[]const u8, p
|
|||||||
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,
|
||||||
url: ?[]const u8 = null,
|
url: ?[:0]const u8 = null,
|
||||||
domain: ?[]const u8 = null,
|
domain: ?[]const u8 = null,
|
||||||
path: ?[]const u8 = null,
|
path: ?[]const u8 = null,
|
||||||
partitionKey: ?CdpStorage.CookiePartitionKey = null,
|
partitionKey: ?CdpStorage.CookiePartitionKey = null,
|
||||||
@@ -117,15 +118,12 @@ 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;
|
|
||||||
const uri_ptr = if (uri) |u| &u 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 Cookie.parseDomain(cmd.arena, uri_ptr, params.domain);
|
const domain = try Cookie.parseDomain(cmd.arena, params.url, params.domain);
|
||||||
const path = try Cookie.parsePath(cmd.arena, uri_ptr, params.path);
|
const path = try Cookie.parsePath(cmd.arena, params.url, params.path);
|
||||||
|
|
||||||
// We do not want to use Cookie.appliesTo here. As a Cookie with a shorter path would match.
|
// We do not want to use Cookie.appliesTo here. As a Cookie with a shorter path would match.
|
||||||
// Similar to deduplicating with areCookiesEqual, except domain and path are optional.
|
// Similar to deduplicating with areCookiesEqual, except domain and path are optional.
|
||||||
@@ -167,23 +165,23 @@ fn setCookies(cmd: anytype) !void {
|
|||||||
try cmd.sendResult(null, .{});
|
try cmd.sendResult(null, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetCookiesParam = struct { urls: ?[]const []const u8 = null };
|
const GetCookiesParam = struct {
|
||||||
|
urls: ?[]const [:0]const u8 = null,
|
||||||
|
};
|
||||||
fn getCookies(cmd: anytype) !void {
|
fn getCookies(cmd: anytype) !void {
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
const params = (try cmd.params(GetCookiesParam)) orelse GetCookiesParam{};
|
const params = (try cmd.params(GetCookiesParam)) orelse GetCookiesParam{};
|
||||||
|
|
||||||
// If not specified, use the URLs of the page and all of its subframes. TODO subframes
|
// If not specified, use the URLs of the page and all of its subframes. TODO subframes
|
||||||
const page_url = if (bc.session.page) |page| page.url else null;
|
const page_url = if (bc.session.page) |page| page.url else null;
|
||||||
const param_urls = params.urls orelse &[_][]const u8{page_url orelse return error.InvalidParams};
|
const param_urls = params.urls orelse &[_][:0]const u8{page_url orelse return error.InvalidParams};
|
||||||
|
|
||||||
var urls = try std.ArrayListUnmanaged(CdpStorage.PreparedUri).initCapacity(cmd.arena, param_urls.len);
|
var urls = try std.ArrayListUnmanaged(CdpStorage.PreparedUri).initCapacity(cmd.arena, param_urls.len);
|
||||||
for (param_urls) |url| {
|
for (param_urls) |url| {
|
||||||
const uri = std.Uri.parse(url) catch return error.InvalidParams;
|
|
||||||
|
|
||||||
urls.appendAssumeCapacity(.{
|
urls.appendAssumeCapacity(.{
|
||||||
.host = try Cookie.parseDomain(cmd.arena, &uri, null),
|
.host = try Cookie.parseDomain(cmd.arena, url, null),
|
||||||
.path = try Cookie.parsePath(cmd.arena, &uri, null),
|
.path = try Cookie.parsePath(cmd.arena, url, null),
|
||||||
.secure = std.mem.eql(u8, uri.scheme, "https"),
|
.secure = URL.isHTTPS(url),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,23 +298,19 @@ pub const TransferAsRequestWriter = struct {
|
|||||||
try jws.objectField("url");
|
try jws.objectField("url");
|
||||||
try jws.beginWriteRaw();
|
try jws.beginWriteRaw();
|
||||||
try writer.writeByte('\"');
|
try writer.writeByte('\"');
|
||||||
try transfer.uri.writeToStream(writer, .{
|
// #ZIGDOM shouldn't include the hash?
|
||||||
.scheme = true,
|
try writer.writeAll(transfer.url);
|
||||||
.authentication = true,
|
|
||||||
.authority = true,
|
|
||||||
.path = true,
|
|
||||||
.query = true,
|
|
||||||
});
|
|
||||||
try writer.writeByte('\"');
|
try writer.writeByte('\"');
|
||||||
jws.endWriteRaw();
|
jws.endWriteRaw();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
if (transfer.uri.fragment) |frag| {
|
const frag = URL.getHash(transfer.url);
|
||||||
|
if (frag.len > 0) {
|
||||||
try jws.objectField("urlFragment");
|
try jws.objectField("urlFragment");
|
||||||
try jws.beginWriteRaw();
|
try jws.beginWriteRaw();
|
||||||
try writer.writeAll("\"#");
|
try writer.writeAll("\"#");
|
||||||
try writer.writeAll(frag.percent_encoded);
|
try writer.writeAll(frag);
|
||||||
try writer.writeByte('\"');
|
try writer.writeByte('\"');
|
||||||
jws.endWriteRaw();
|
jws.endWriteRaw();
|
||||||
}
|
}
|
||||||
@@ -370,13 +364,8 @@ const TransferAsResponseWriter = struct {
|
|||||||
try jws.objectField("url");
|
try jws.objectField("url");
|
||||||
try jws.beginWriteRaw();
|
try jws.beginWriteRaw();
|
||||||
try writer.writeByte('\"');
|
try writer.writeByte('\"');
|
||||||
try transfer.uri.writeToStream(writer, .{
|
// #ZIGDOM shouldn't include the hash?
|
||||||
.scheme = true,
|
try writer.writeAll(transfer.url);
|
||||||
.authentication = true,
|
|
||||||
.authority = true,
|
|
||||||
.path = true,
|
|
||||||
.query = true,
|
|
||||||
});
|
|
||||||
try writer.writeByte('\"');
|
try writer.writeByte('\"');
|
||||||
jws.endWriteRaw();
|
jws.endWriteRaw();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,10 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
|
const URL = @import("../../browser/URL.zig");
|
||||||
const Cookie = @import("../../browser/webapi/storage/storage.zig").Cookie;
|
const Cookie = @import("../../browser/webapi/storage/storage.zig").Cookie;
|
||||||
const CookieJar = @import("../../browser/webapi/storage/storage.zig").Jar;
|
const CookieJar = Cookie.Jar;
|
||||||
pub const PreparedUri = @import("../../browser/webapi/storage/cookie.zig").PreparedUri;
|
pub const PreparedUri = Cookie.PreparedUri;
|
||||||
|
|
||||||
pub fn processMessage(cmd: anytype) !void {
|
pub fn processMessage(cmd: anytype) !void {
|
||||||
const action = std.meta.stringToEnum(enum {
|
const action = std.meta.stringToEnum(enum {
|
||||||
@@ -112,9 +113,9 @@ pub const CookiePartitionKey = struct {
|
|||||||
pub const CdpCookie = struct {
|
pub const CdpCookie = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
value: []const u8,
|
value: []const u8,
|
||||||
url: ?[]const u8 = null,
|
url: ?[:0]const u8 = null,
|
||||||
domain: ?[]const u8 = null,
|
domain: ?[]const u8 = null,
|
||||||
path: ?[]const u8 = null,
|
path: ?[:0]const u8 = null,
|
||||||
secure: ?bool = null, // default: https://www.rfc-editor.org/rfc/rfc6265#section-5.3
|
secure: ?bool = null, // default: https://www.rfc-editor.org/rfc/rfc6265#section-5.3
|
||||||
httpOnly: bool = false, // default: https://www.rfc-editor.org/rfc/rfc6265#section-5.3
|
httpOnly: bool = false, // default: https://www.rfc-editor.org/rfc/rfc6265#section-5.3
|
||||||
sameSite: SameSite = .None, // default: https://datatracker.ietf.org/doc/html/draft-west-first-party-cookies
|
sameSite: SameSite = .None, // default: https://datatracker.ietf.org/doc/html/draft-west-first-party-cookies
|
||||||
@@ -136,12 +137,10 @@ 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, (NOT path), secure, source port, and source scheme.
|
// NOTE: The param.url can affect the default domain, (NOT path), secure, source port, and source scheme.
|
||||||
const uri = if (param.url) |url| std.Uri.parse(url) catch return error.InvalidParams else null;
|
const domain = try Cookie.parseDomain(a, param.url, param.domain);
|
||||||
const uri_ptr = if (uri) |*u| u else null;
|
|
||||||
const domain = try Cookie.parseDomain(a, uri_ptr, param.domain);
|
|
||||||
const path = if (param.path == null) "/" else try Cookie.parsePath(a, null, param.path);
|
const path = if (param.path == null) "/" else try Cookie.parsePath(a, null, param.path);
|
||||||
|
|
||||||
const secure = if (param.secure) |s| s else if (uri) |uri_| std.mem.eql(u8, uri_.scheme, "https") else false;
|
const secure = if (param.secure) |s| s else if (param.url) |url| URL.isHTTPS(url) else false;
|
||||||
|
|
||||||
const cookie = Cookie{
|
const cookie = Cookie{
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const builtin = @import("builtin");
|
|||||||
const Http = @import("Http.zig");
|
const Http = @import("Http.zig");
|
||||||
const URL = @import("../browser/URL.zig");
|
const URL = @import("../browser/URL.zig");
|
||||||
const Notification = @import("../Notification.zig");
|
const Notification = @import("../Notification.zig");
|
||||||
const CookieJar = @import("../browser/webapi/storage/cookie.zig").Jar;
|
const CookieJar = @import("../browser/webapi/storage/Cookie.zig").Jar;
|
||||||
|
|
||||||
const c = Http.c;
|
const c = Http.c;
|
||||||
const posix = std.posix;
|
const posix = std.posix;
|
||||||
@@ -257,12 +257,6 @@ pub fn fulfillTransfer(self: *Client, transfer: *Transfer, status: u16, headers:
|
|||||||
fn makeTransfer(self: *Client, req: Request) !*Transfer {
|
fn makeTransfer(self: *Client, req: Request) !*Transfer {
|
||||||
errdefer req.headers.deinit();
|
errdefer req.headers.deinit();
|
||||||
|
|
||||||
// we need this for cookies
|
|
||||||
const uri = std.Uri.parse(req.url) catch |err| {
|
|
||||||
log.warn(.http, "invalid url", .{ .err = err, .url = req.url });
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
|
|
||||||
const transfer = try self.transfer_pool.create();
|
const transfer = try self.transfer_pool.create();
|
||||||
errdefer self.transfer_pool.destroy(transfer);
|
errdefer self.transfer_pool.destroy(transfer);
|
||||||
|
|
||||||
@@ -271,7 +265,7 @@ fn makeTransfer(self: *Client, req: Request) !*Transfer {
|
|||||||
transfer.* = .{
|
transfer.* = .{
|
||||||
.arena = ArenaAllocator.init(self.allocator),
|
.arena = ArenaAllocator.init(self.allocator),
|
||||||
.id = id,
|
.id = id,
|
||||||
.uri = uri,
|
.url = req.url,
|
||||||
.req = req,
|
.req = req,
|
||||||
.ctx = req.ctx,
|
.ctx = req.ctx,
|
||||||
.client = self,
|
.client = self,
|
||||||
@@ -593,26 +587,16 @@ pub const Handle = struct {
|
|||||||
|
|
||||||
pub const RequestCookie = struct {
|
pub const RequestCookie = struct {
|
||||||
is_http: bool,
|
is_http: bool,
|
||||||
|
jar: *CookieJar,
|
||||||
is_navigation: bool,
|
is_navigation: bool,
|
||||||
origin: [:0]const u8,
|
origin: [:0]const u8,
|
||||||
jar: *@import("../browser/webapi/storage/cookie.zig").Jar,
|
|
||||||
|
|
||||||
pub fn headersForRequest(self: *const RequestCookie, temp: Allocator, url: [:0]const u8, headers: *Http.Headers) !void {
|
pub fn headersForRequest(self: *const RequestCookie, temp: Allocator, url: [:0]const u8, headers: *Http.Headers) !void {
|
||||||
const uri = std.Uri.parse(url) catch |err| {
|
|
||||||
log.warn(.http, "invalid url", .{ .err = err, .url = url });
|
|
||||||
return error.InvalidUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
const origin_uri = std.Uri.parse(self.origin) catch |err| {
|
|
||||||
log.warn(.http, "invalid url", .{ .err = err, .url = self.origin });
|
|
||||||
return error.InvalidUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
var arr: std.ArrayListUnmanaged(u8) = .{};
|
var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||||
try self.jar.forRequest(&uri, arr.writer(temp), .{
|
try self.jar.forRequest(url, arr.writer(temp), .{
|
||||||
.is_http = self.is_http,
|
.is_http = self.is_http,
|
||||||
.is_navigation = self.is_navigation,
|
.is_navigation = self.is_navigation,
|
||||||
.origin_uri = &origin_uri,
|
.origin_url = self.origin,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (arr.items.len > 0) {
|
if (arr.items.len > 0) {
|
||||||
@@ -692,7 +676,7 @@ pub const Transfer = struct {
|
|||||||
arena: ArenaAllocator,
|
arena: ArenaAllocator,
|
||||||
id: usize = 0,
|
id: usize = 0,
|
||||||
req: Request,
|
req: Request,
|
||||||
uri: std.Uri, // used for setting/getting the cookie
|
url: [:0]const u8,
|
||||||
ctx: *anyopaque, // copied from req.ctx to make it easier for callback handlers
|
ctx: *anyopaque, // copied from req.ctx to make it easier for callback handlers
|
||||||
client: *Client,
|
client: *Client,
|
||||||
// total bytes received in the response, including the response status line,
|
// total bytes received in the response, including the response status line,
|
||||||
@@ -778,7 +762,7 @@ pub const Transfer = struct {
|
|||||||
|
|
||||||
pub fn updateURL(self: *Transfer, url: [:0]const u8) !void {
|
pub fn updateURL(self: *Transfer, url: [:0]const u8) !void {
|
||||||
// for cookies
|
// for cookies
|
||||||
self.uri = try std.Uri.parse(url);
|
self.url = url;
|
||||||
|
|
||||||
// for the request itself
|
// for the request itself
|
||||||
self.req.url = url;
|
self.req.url = url;
|
||||||
@@ -846,7 +830,7 @@ pub const Transfer = struct {
|
|||||||
while (true) {
|
while (true) {
|
||||||
const ct = getResponseHeader(easy, "set-cookie", i);
|
const ct = getResponseHeader(easy, "set-cookie", i);
|
||||||
if (ct == null) break;
|
if (ct == null) break;
|
||||||
try req.cookie_jar.populateFromResponse(&transfer.uri, ct.?.value);
|
try req.cookie_jar.populateFromResponse(transfer.url, ct.?.value);
|
||||||
i += 1;
|
i += 1;
|
||||||
if (i >= ct.?.amount) break;
|
if (i >= ct.?.amount) break;
|
||||||
}
|
}
|
||||||
@@ -860,13 +844,12 @@ pub const Transfer = struct {
|
|||||||
try errorCheck(c.curl_easy_getinfo(easy, c.CURLINFO_EFFECTIVE_URL, &base_url));
|
try errorCheck(c.curl_easy_getinfo(easy, c.CURLINFO_EFFECTIVE_URL, &base_url));
|
||||||
|
|
||||||
const url = try URL.resolve(arena, std.mem.span(base_url), location.value, .{});
|
const url = try URL.resolve(arena, std.mem.span(base_url), location.value, .{});
|
||||||
const uri = try std.Uri.parse(url);
|
transfer.url = url;
|
||||||
transfer.uri = uri;
|
|
||||||
|
|
||||||
var cookies: std.ArrayListUnmanaged(u8) = .{};
|
var cookies: std.ArrayListUnmanaged(u8) = .{};
|
||||||
try req.cookie_jar.forRequest(&uri, cookies.writer(arena), .{
|
try req.cookie_jar.forRequest(url, cookies.writer(arena), .{
|
||||||
.is_http = true,
|
.is_http = true,
|
||||||
.origin_uri = &transfer.uri,
|
.origin_url = url,
|
||||||
// used to enforce samesite cookie rules
|
// used to enforce samesite cookie rules
|
||||||
.is_navigation = req.resource_type == .document,
|
.is_navigation = req.resource_type == .document,
|
||||||
});
|
});
|
||||||
@@ -895,7 +878,7 @@ pub const Transfer = struct {
|
|||||||
while (true) {
|
while (true) {
|
||||||
const ct = getResponseHeader(easy, "set-cookie", i);
|
const ct = getResponseHeader(easy, "set-cookie", i);
|
||||||
if (ct == null) break;
|
if (ct == null) break;
|
||||||
transfer.req.cookie_jar.populateFromResponse(&transfer.uri, ct.?.value) catch |err| {
|
transfer.req.cookie_jar.populateFromResponse(transfer.url, ct.?.value) catch |err| {
|
||||||
log.err(.http, "set cookie", .{ .err = err, .req = transfer });
|
log.err(.http, "set cookie", .{ .err = err, .req = transfer });
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user