mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
add cookie jar
This commit is contained in:
5
Makefile
5
Makefile
@@ -44,7 +44,7 @@ help:
|
|||||||
|
|
||||||
# $(ZIG) commands
|
# $(ZIG) commands
|
||||||
# ------------
|
# ------------
|
||||||
.PHONY: build build-dev run run-release shell test bench download-zig wpt unittest
|
.PHONY: build build-dev run run-release shell test bench download-zig wpt unittest data
|
||||||
|
|
||||||
zig_version = $(shell grep 'recommended_zig_version = "' "vendor/zig-js-runtime/build.zig" | cut -d'"' -f2)
|
zig_version = $(shell grep 'recommended_zig_version = "' "vendor/zig-js-runtime/build.zig" | cut -d'"' -f2)
|
||||||
|
|
||||||
@@ -199,6 +199,9 @@ install-zig-js-runtime:
|
|||||||
@cd vendor/zig-js-runtime && \
|
@cd vendor/zig-js-runtime && \
|
||||||
make install
|
make install
|
||||||
|
|
||||||
|
data:
|
||||||
|
cd src/data && go run public_suffix_list_gen.go > public_suffix_list.zig
|
||||||
|
|
||||||
.PHONY: _build_mimalloc
|
.PHONY: _build_mimalloc
|
||||||
|
|
||||||
MIMALLOC := $(BC)vendor/mimalloc/out/$(OS)-$(ARCH)
|
MIMALLOC := $(BC)vendor/mimalloc/out/$(OS)-$(ARCH)
|
||||||
|
|||||||
9742
src/data/public_suffix_list.zig
Normal file
9742
src/data/public_suffix_list.zig
Normal file
File diff suppressed because it is too large
Load Diff
42
src/data/public_suffix_list_gen.go
Normal file
42
src/data/public_suffix_list_gen.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
resp, err := http.Get("https://publicsuffix.org/list/public_suffix_list.dat")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var domains []string
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(resp.Body)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if len(line) == 0 || strings.HasPrefix(line, "//") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
domains = append(domains, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup :=
|
||||||
|
"const std = @import(\"std\");\n\n" +
|
||||||
|
"pub fn lookup(value: []const u8) bool {\n" +
|
||||||
|
" return public_suffix_list.has(value);\n" +
|
||||||
|
"}\n"
|
||||||
|
fmt.Println(lookup)
|
||||||
|
|
||||||
|
fmt.Println("const public_suffix_list = std.StaticStringMap(void).initComptime([_]struct { []const u8, void }{")
|
||||||
|
for _, domain := range domains {
|
||||||
|
fmt.Printf(` .{ "%s", {} },`, domain)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
fmt.Println("});")
|
||||||
|
}
|
||||||
@@ -4,10 +4,11 @@ const Allocator = std.mem.Allocator;
|
|||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
const DateTime = @import("../datetime.zig").DateTime;
|
const DateTime = @import("../datetime.zig").DateTime;
|
||||||
|
const public_suffix_list = @import("../data/public_suffix_list.zig").lookup;
|
||||||
|
|
||||||
pub const Jar = struct {
|
pub const Jar = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
cookies: std.ArrayListUnmanaged(u8),
|
cookies: std.ArrayListUnmanaged(Cookie),
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) Jar {
|
pub fn init(allocator: Allocator) Jar {
|
||||||
return .{
|
return .{
|
||||||
@@ -23,47 +24,174 @@ pub const Jar = struct {
|
|||||||
self.cookies.deinit(self.allocator);
|
self.cookies.deinit(self.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 forRequest(
|
pub fn forRequest(
|
||||||
self: *const Jar,
|
self: *Jar,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
request_start: i64,
|
request_time: i64,
|
||||||
origin_uri: ?Uri,
|
origin_uri: ?Uri,
|
||||||
target_uri: Uri,
|
target_uri: Uri,
|
||||||
navitation: bool,
|
navitation: bool,
|
||||||
) !CookieList {
|
) !CookieList {
|
||||||
const is_secure = std.mem.eql(u8, target_uri.scheme, "https");
|
const target_path = target_uri.path.percent_encoded;
|
||||||
const target_host = (target_uri.host orelse return error.InvalidURI).percent_encoded;
|
const target_host = (target_uri.host orelse return error.InvalidURI).percent_encoded;
|
||||||
|
|
||||||
const same_site = try areSameSite(origin_uri, target_host);
|
const same_site = try areSameSite(origin_uri, target_host);
|
||||||
|
const is_secure = std.mem.eql(u8, target_uri.scheme, "https");
|
||||||
|
|
||||||
|
var matching: std.ArrayListUnmanaged(*Cookie) = .{};
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
var cookies = self.cookies.items;
|
var cookies = self.cookies.items;
|
||||||
while (i < cookies.len) {
|
while (i < cookies.len) {
|
||||||
const cookie = &cookies[i];
|
const cookie = &cookies[i];
|
||||||
if (cookie.isExpired(request_start)) {
|
|
||||||
self.swapRemove(i);
|
if (isCookieExpired(cookie, request_time)) {
|
||||||
|
cookie.deinit();
|
||||||
|
_ = self.cookies.swapRemove(i);
|
||||||
// don't increment i !
|
// don't increment i !
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
|
|
||||||
if (is_secure == false and cookie.secure) {
|
if (is_secure == false and cookie.secure) {
|
||||||
|
// secure cookie can only be sent over HTTPs
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// www.google.com
|
if (same_site == false) {
|
||||||
|
// If we aren't on the "same site" (matching 2nd level domain
|
||||||
if (navitation == false and cookie.same_site != .strict) {
|
// taking into account public suffix list), then the cookie
|
||||||
continue;
|
// 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 (navitation == false) continue,
|
||||||
|
.none => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const domain = cookie.domain;
|
||||||
|
if (domain[0] == '.') {
|
||||||
|
// when explicitly set, the domain
|
||||||
|
// 1 - always starts with a .
|
||||||
|
// 2 - always is a suffix match (or examlpe)
|
||||||
|
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 Domain=XYX isn't specified, it's an exact match only
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const path = cookie.path;
|
||||||
|
if (path[path.len - 1] == '/') {
|
||||||
|
// If our cookie path is doc/
|
||||||
|
// Then we can only match if the target path starts with 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!
|
||||||
|
try matching.append(allocator, cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return .{ ._cookies = matching };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// abc.lightpanda.io is the same site as lightpanda.io or 123.lightpanda.io
|
pub const CookieList = struct {
|
||||||
// or spice.123.lightpanda.io
|
_cookies: std.ArrayListUnmanaged(*Cookie),
|
||||||
|
|
||||||
|
pub fn deinit(self: *CookieList, allocator: Allocator) void {
|
||||||
|
self._cookies.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cookies(self: *const CookieList) []*Cookie {
|
||||||
|
return self._cookies.items;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn isCookieExpired(cookie: *const Cookie, now: i64) bool {
|
||||||
|
const ce = cookie.expires orelse return false;
|
||||||
|
return ce <= 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_: ?std.Uri, target_host: []const u8) !bool {
|
fn areSameSite(origin_uri_: ?std.Uri, target_host: []const u8) !bool {
|
||||||
const origin_uri = origin_uri_ orelse return true;
|
const origin_uri = origin_uri_ orelse return true;
|
||||||
const origin_host = (origin_uri.host orelse return error.InvalidURI).percent_encoded;
|
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 {
|
pub const Cookie = struct {
|
||||||
@@ -275,6 +403,215 @@ fn trimRight(str: []const u8) []const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
|
test "cookie: findSecondLevelDomain" {
|
||||||
|
const cases = [_]struct { []const u8, []const u8 }{
|
||||||
|
.{ "", "" },
|
||||||
|
.{ "com", "com" },
|
||||||
|
.{ "lightpanda.io", "lightpanda.io" },
|
||||||
|
.{ "lightpanda.io", "test.lightpanda.io" },
|
||||||
|
.{ "lightpanda.io", "first.test.lightpanda.io" },
|
||||||
|
.{ "www.gov.uk", "www.gov.uk" },
|
||||||
|
.{ "stats.gov.uk", "www.stats.gov.uk" },
|
||||||
|
.{ "api.gov.uk", "api.gov.uk" },
|
||||||
|
.{ "dev.api.gov.uk", "dev.api.gov.uk" },
|
||||||
|
.{ "dev.api.gov.uk", "1.dev.api.gov.uk" },
|
||||||
|
};
|
||||||
|
for (cases) |c| {
|
||||||
|
try testing.expectEqual(c.@"0", findSecondLevelDomain(c.@"1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Jar: add" {
|
||||||
|
const expectCookies = struct {
|
||||||
|
fn expect(expected: []const struct { []const u8, []const u8 }, jar: Jar) !void {
|
||||||
|
try testing.expectEqual(expected.len, jar.cookies.items.len);
|
||||||
|
LOOP: for (expected) |e| {
|
||||||
|
for (jar.cookies.items) |c| {
|
||||||
|
if (std.mem.eql(u8, e.@"0", c.name) and std.mem.eql(u8, e.@"1", c.value)) {
|
||||||
|
continue :LOOP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std.debug.print("Cookie ({s}={s}) not found", .{ e.@"0", e.@"1" });
|
||||||
|
return error.CookieNotFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.expect;
|
||||||
|
|
||||||
|
const now = std.time.timestamp();
|
||||||
|
|
||||||
|
var jar = Jar.init(testing.allocator);
|
||||||
|
defer jar.deinit();
|
||||||
|
try expectCookies(&.{}, jar);
|
||||||
|
|
||||||
|
try jar.add(try Cookie.parse(testing.allocator, test_uri, "over=9000;Max-Age=0"), now);
|
||||||
|
try expectCookies(&.{}, jar);
|
||||||
|
|
||||||
|
try jar.add(try Cookie.parse(testing.allocator, test_uri, "over=9000"), now);
|
||||||
|
try expectCookies(&.{.{ "over", "9000" }}, jar);
|
||||||
|
|
||||||
|
try jar.add(try Cookie.parse(testing.allocator, test_uri, "over=9000!!"), now);
|
||||||
|
try expectCookies(&.{.{ "over", "9000!!" }}, jar);
|
||||||
|
|
||||||
|
try jar.add(try Cookie.parse(testing.allocator, test_uri, "spice=flow"), now);
|
||||||
|
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flow" } }, jar);
|
||||||
|
|
||||||
|
try jar.add(try Cookie.parse(testing.allocator, test_uri, "spice=flows;Path=/"), now);
|
||||||
|
try expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" } }, jar);
|
||||||
|
|
||||||
|
try jar.add(try Cookie.parse(testing.allocator, test_uri, "over=9001;Path=/other"), now);
|
||||||
|
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 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 expectCookies(&.{ .{ "over", "9000!!" }, .{ "spice", "flows" }, .{ "over", "9002" } }, jar);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Jar: forRequest" {
|
||||||
|
const expectCookies = struct {
|
||||||
|
fn expect(expected: []const []const u8, list: *CookieList) !void {
|
||||||
|
defer list.deinit(testing.allocator);
|
||||||
|
const acutal_cookies = list._cookies.items;
|
||||||
|
|
||||||
|
try testing.expectEqual(expected.len, acutal_cookies.len);
|
||||||
|
LOOP: for (expected) |e| {
|
||||||
|
for (acutal_cookies) |c| {
|
||||||
|
if (std.mem.eql(u8, e, c.name)) {
|
||||||
|
continue :LOOP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std.debug.print("Cookie '{s}' not found", .{e});
|
||||||
|
return error.CookieNotFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.expect;
|
||||||
|
|
||||||
|
const now = std.time.timestamp();
|
||||||
|
|
||||||
|
var jar = Jar.init(testing.allocator);
|
||||||
|
defer jar.deinit();
|
||||||
|
|
||||||
|
const test_uri_2 = Uri.parse("http://test.lightpanda.io/") catch unreachable;
|
||||||
|
|
||||||
|
{
|
||||||
|
// test with no cookies
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, test_uri, true);
|
||||||
|
try expectCookies(&.{}, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
try jar.add(try Cookie.parse(testing.allocator, test_uri, "global1=1"), now);
|
||||||
|
try jar.add(try Cookie.parse(testing.allocator, test_uri, "global2=2;Max-Age=30"), 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_uri, "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_uri, "sitenone=6;SameSite=None;Path=/x/"), 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_uri, "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);
|
||||||
|
|
||||||
|
{
|
||||||
|
// nothing fancy here
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, test_uri, true);
|
||||||
|
try expectCookies(&.{ "global1", "global2" }, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// matching path without trailing /
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, try std.Uri.parse("http://lightpanda.io/about"), true);
|
||||||
|
try expectCookies(&.{ "global1", "global2", "path1" }, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// incomplete prefix path
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, try std.Uri.parse("http://lightpanda.io/abou"), true);
|
||||||
|
try expectCookies(&.{ "global1", "global2" }, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// path doesn't match
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, try std.Uri.parse("http://lightpanda.io/aboutus"), true);
|
||||||
|
try expectCookies(&.{ "global1", "global2" }, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// path doesn't match cookie directory
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, try std.Uri.parse("http://lightpanda.io/docs"), true);
|
||||||
|
try expectCookies(&.{ "global1", "global2" }, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// exact directory match
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, try std.Uri.parse("http://lightpanda.io/docs/"), true);
|
||||||
|
try expectCookies(&.{ "global1", "global2", "path2" }, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// sub directory match
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, try std.Uri.parse("http://lightpanda.io/docs/more"), true);
|
||||||
|
try expectCookies(&.{ "global1", "global2", "path2" }, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// secure
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, try std.Uri.parse("https://lightpanda.io/"), true);
|
||||||
|
try expectCookies(&.{ "global1", "global2", "secure" }, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// navigational cross domain
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, try std.Uri.parse("http://example.com/"), try std.Uri.parse("http://lightpanda.io/x/"), true);
|
||||||
|
try expectCookies(&.{ "global1", "global2", "sitenone", "sitelax" }, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// non-navigational cross domain
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, try std.Uri.parse("http://example.com/"), try std.Uri.parse("http://lightpanda.io/x/"), false);
|
||||||
|
try expectCookies(&.{"sitenone"}, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// non-navigational same origin
|
||||||
|
var matches = try jar.forRequest(
|
||||||
|
testing.allocator,
|
||||||
|
now,
|
||||||
|
try std.Uri.parse("http://lightpanda.io/"),
|
||||||
|
try std.Uri.parse("http://lightpanda.io/x/"),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
try expectCookies(&.{ "global1", "global2", "sitenone", "sitelax", "sitestrict" }, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// exact domain match
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, try std.Uri.parse("http://test.lightpanda.io/"), true);
|
||||||
|
try expectCookies(&.{"domain1"}, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// domain suffix match
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, try std.Uri.parse("http://1.test.lightpanda.io/"), true);
|
||||||
|
try expectCookies(&.{"domain1"}, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// non-matching domain
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now, test_uri, try std.Uri.parse("http://other.lightpanda.io/"), true);
|
||||||
|
try expectCookies(&.{}, &matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// cookie has expired
|
||||||
|
const l = jar.cookies.items.len;
|
||||||
|
var matches = try jar.forRequest(testing.allocator, now + 100, test_uri, test_uri, true);
|
||||||
|
try expectCookies(&.{"global1"}, &matches);
|
||||||
|
try testing.expectEqual(l - 1, jar.cookies.items.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you add more cases after this point, note that the above test removes
|
||||||
|
// the 'global2' cookie
|
||||||
|
}
|
||||||
|
|
||||||
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' });
|
||||||
@@ -461,7 +798,7 @@ fn expectCookie(expected: ExpectedCookie, url: []const u8, set_cookie: []const u
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn expectAttribute(expected: anytype, url: ?[]const u8, set_cookie: []const u8) !void {
|
fn expectAttribute(expected: anytype, url: ?[]const u8, set_cookie: []const u8) !void {
|
||||||
const uri = if (url) |u| try Uri.parse(u) else dummy_test_uri;
|
const uri = if (url) |u| try Uri.parse(u) else test_uri;
|
||||||
var cookie = try Cookie.parse(testing.allocator, uri, set_cookie);
|
var cookie = try Cookie.parse(testing.allocator, uri, set_cookie);
|
||||||
defer cookie.deinit();
|
defer cookie.deinit();
|
||||||
|
|
||||||
@@ -475,8 +812,8 @@ 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: ?[]const u8, set_cookie: []const u8) !void {
|
||||||
const uri = if (url) |u| try Uri.parse(u) else dummy_test_uri;
|
const uri = if (url) |u| try Uri.parse(u) else test_uri;
|
||||||
try testing.expectError(expected, Cookie.parse(testing.allocator, uri, set_cookie));
|
try testing.expectError(expected, Cookie.parse(testing.allocator, uri, set_cookie));
|
||||||
}
|
}
|
||||||
|
|
||||||
const dummy_test_uri = Uri.parse("http://lightpanda.io/") catch unreachable;
|
const test_uri = Uri.parse("http://lightpanda.io/") catch unreachable;
|
||||||
|
|||||||
Reference in New Issue
Block a user