mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Add URL struct
Combine uri + rawuri into single struct. Try to improve ownership around URIs and URI-like things. - cookie & request can take *const std.Uri (TODO: make them aware of the new URL struct?) - Location (web api) should own its URL (web api URL) - Window should own its Location Most of these changes result in (a) a cleaner Page and (b) not having to carry around 2 nullable objects (URI and rawuri).
This commit is contained in:
@@ -38,9 +38,7 @@ const apiweb = @import("../apiweb.zig");
|
|||||||
const Window = @import("../html/window.zig").Window;
|
const Window = @import("../html/window.zig").Window;
|
||||||
const Walker = @import("../dom/walker.zig").WalkerDepthFirst;
|
const Walker = @import("../dom/walker.zig").WalkerDepthFirst;
|
||||||
|
|
||||||
const URL = @import("../url/url.zig").URL;
|
const URL = @import("../url.zig").URL;
|
||||||
const Location = @import("../html/location.zig").Location;
|
|
||||||
|
|
||||||
const storage = @import("../storage/storage.zig");
|
const storage = @import("../storage/storage.zig");
|
||||||
|
|
||||||
const http = @import("../http/client.zig");
|
const http = @import("../http/client.zig");
|
||||||
@@ -253,7 +251,7 @@ pub const Session = struct {
|
|||||||
self.env.stop();
|
self.env.stop();
|
||||||
// TODO unload document: https://html.spec.whatwg.org/#unloading-documents
|
// TODO unload document: https://html.spec.whatwg.org/#unloading-documents
|
||||||
|
|
||||||
self.window.replaceLocation(null) catch |e| {
|
self.window.replaceLocation(.{ .url = null }) catch |e| {
|
||||||
log.err("reset window location: {any}", .{e});
|
log.err("reset window location: {any}", .{e});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -269,7 +267,7 @@ pub const Session = struct {
|
|||||||
|
|
||||||
fn contextCreated(self: *Session, page: *Page, aux_data: ?[]const u8) void {
|
fn contextCreated(self: *Session, page: *Page, aux_data: ?[]const u8) void {
|
||||||
log.debug("inspector context created", .{});
|
log.debug("inspector context created", .{});
|
||||||
self.inspector.contextCreated(&self.env, "", page.origin orelse "://", aux_data);
|
self.inspector.contextCreated(&self.env, "", (page.origin() catch "://") orelse "://", aux_data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -283,14 +281,8 @@ pub const Page = struct {
|
|||||||
session: *Session,
|
session: *Session,
|
||||||
doc: ?*parser.Document = null,
|
doc: ?*parser.Document = null,
|
||||||
|
|
||||||
// handle url
|
// The URL of the page
|
||||||
rawuri: ?[]const u8 = null,
|
|
||||||
uri: std.Uri = undefined,
|
|
||||||
origin: ?[]const u8 = null,
|
|
||||||
|
|
||||||
// html url and location
|
|
||||||
url: ?URL = null,
|
url: ?URL = null,
|
||||||
location: Location = .{},
|
|
||||||
|
|
||||||
raw_data: ?[]const u8 = null,
|
raw_data: ?[]const u8 = null,
|
||||||
|
|
||||||
@@ -342,62 +334,52 @@ pub const Page = struct {
|
|||||||
log.debug("wait: OK", .{});
|
log.debug("wait: OK", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn origin(self: *const Page) !?[]const u8 {
|
||||||
|
const url = &(self.url orelse return null);
|
||||||
|
var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||||
|
try url.origin(arr.writer(self.arena));
|
||||||
|
return arr.items;
|
||||||
|
}
|
||||||
|
|
||||||
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
|
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
|
||||||
// - aux_data: extra data forwarded to the Inspector
|
// - aux_data: extra data forwarded to the Inspector
|
||||||
// see Inspector.contextCreated
|
// see Inspector.contextCreated
|
||||||
pub fn navigate(self: *Page, uri: []const u8, aux_data: ?[]const u8) !void {
|
pub fn navigate(self: *Page, url_string: []const u8, aux_data: ?[]const u8) !void {
|
||||||
const arena = self.arena;
|
const arena = self.arena;
|
||||||
|
|
||||||
log.debug("starting GET {s}", .{uri});
|
log.debug("starting GET {s}", .{url_string});
|
||||||
|
|
||||||
// if the uri is about:blank, nothing to do.
|
// if the url is about:blank, nothing to do.
|
||||||
if (std.mem.eql(u8, "about:blank", uri)) {
|
if (std.mem.eql(u8, "about:blank", url_string)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.uri = std.Uri.parse(uri) catch try std.Uri.parseAfterScheme("", uri);
|
// we don't clone url_string, because we're going to replace self.url
|
||||||
|
// later in this function, with the final request url (since we might
|
||||||
|
// redirect)
|
||||||
|
self.url = try URL.parse(url_string, "https");
|
||||||
self.session.app.telemetry.record(.{ .navigate = .{
|
self.session.app.telemetry.record(.{ .navigate = .{
|
||||||
.proxy = false,
|
.proxy = false,
|
||||||
.tls = std.ascii.eqlIgnoreCase(self.uri.scheme, "https"),
|
.tls = std.ascii.eqlIgnoreCase(self.url.?.scheme(), "https"),
|
||||||
} });
|
} });
|
||||||
|
|
||||||
// load the data
|
// load the data
|
||||||
var request = try self.newHTTPRequest(.GET, self.uri, .{ .navigation = true });
|
var request = try self.newHTTPRequest(.GET, &self.url.?, .{ .navigation = true });
|
||||||
defer request.deinit();
|
defer request.deinit();
|
||||||
|
|
||||||
var response = try request.sendSync(.{});
|
var response = try request.sendSync(.{});
|
||||||
|
|
||||||
|
// would be different than self.url in the case of a redirect
|
||||||
|
self.url = try URL.fromURI(arena, request.uri);
|
||||||
|
|
||||||
|
const url = &self.url.?;
|
||||||
const header = response.header;
|
const header = response.header;
|
||||||
try self.session.cookie_jar.populateFromResponse(request.uri, &header);
|
try self.session.cookie_jar.populateFromResponse(&url.uri, &header);
|
||||||
|
|
||||||
// update uri after eventual redirection
|
|
||||||
var buf: std.ArrayListUnmanaged(u8) = .{};
|
|
||||||
try request.uri.writeToStream(.{
|
|
||||||
.scheme = true,
|
|
||||||
.authentication = true,
|
|
||||||
.authority = true,
|
|
||||||
.path = true,
|
|
||||||
.query = true,
|
|
||||||
.fragment = true,
|
|
||||||
}, buf.writer(arena));
|
|
||||||
self.rawuri = buf.items;
|
|
||||||
|
|
||||||
self.uri = try std.Uri.parse(self.rawuri.?);
|
|
||||||
|
|
||||||
// TODO handle fragment in url.
|
// TODO handle fragment in url.
|
||||||
self.url = try URL.constructor(arena, self.rawuri.?, null);
|
try self.session.window.replaceLocation(.{ .url = try url.toWebApi(arena) });
|
||||||
self.location.url = &self.url.?;
|
|
||||||
try self.session.window.replaceLocation(&self.location);
|
|
||||||
|
|
||||||
// prepare origin value.
|
log.info("GET {any} {d}", .{ url, header.status });
|
||||||
buf = .{};
|
|
||||||
try request.uri.writeToStream(.{
|
|
||||||
.scheme = true,
|
|
||||||
.authority = true,
|
|
||||||
}, buf.writer(arena));
|
|
||||||
self.origin = buf.items;
|
|
||||||
|
|
||||||
log.info("GET {any} {d}", .{ self.uri, header.status });
|
|
||||||
|
|
||||||
const ct = blk: {
|
const ct = blk: {
|
||||||
break :blk header.get("content-type") orelse {
|
break :blk header.get("content-type") orelse {
|
||||||
@@ -442,12 +424,12 @@ pub const Page = struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn mouseEvent(self: *Page, allocator: Allocator, me: MouseEvent) !?ClickResult {
|
pub fn mouseEvent(self: *Page, me: MouseEvent) !void {
|
||||||
if (me.type != .pressed) {
|
if (me.type != .pressed) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = self.renderer.getElementAtPosition(me.x, me.y) orelse return null;
|
const element = self.renderer.getElementAtPosition(me.x, me.y) orelse return;
|
||||||
|
|
||||||
const event = try parser.mouseEventCreate();
|
const event = try parser.mouseEventCreate();
|
||||||
defer parser.mouseEventDestroy(event);
|
defer parser.mouseEventDestroy(event);
|
||||||
@@ -458,20 +440,6 @@ pub const Page = struct {
|
|||||||
.y = me.y,
|
.y = me.y,
|
||||||
});
|
});
|
||||||
_ = try parser.elementDispatchEvent(element, @ptrCast(event));
|
_ = try parser.elementDispatchEvent(element, @ptrCast(event));
|
||||||
|
|
||||||
if ((try parser.mouseEventDefaultPrevented(event)) == true) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const node = parser.elementToNode(element);
|
|
||||||
const tag = try parser.nodeName(node);
|
|
||||||
if (std.ascii.eqlIgnoreCase(tag, "a")) {
|
|
||||||
const href = (try parser.elementGetAttribute(element, "href")) orelse return null;
|
|
||||||
var buf = try allocator.alloc(u8, 1024);
|
|
||||||
return .{ .navigate = try std.Uri.resolve_inplace(self.uri, href, &buf) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/#read-html
|
// https://html.spec.whatwg.org/#read-html
|
||||||
@@ -495,13 +463,13 @@ pub const Page = struct {
|
|||||||
// https://html.spec.whatwg.org/#reporting-document-loading-status
|
// https://html.spec.whatwg.org/#reporting-document-loading-status
|
||||||
|
|
||||||
// inject the URL to the document including the fragment.
|
// inject the URL to the document including the fragment.
|
||||||
try parser.documentSetDocumentURI(doc, self.rawuri orelse "about:blank");
|
try parser.documentSetDocumentURI(doc, if (self.url) |*url| url.raw else "about:blank");
|
||||||
|
|
||||||
const session = self.session;
|
const session = self.session;
|
||||||
// TODO set the referrer to the document.
|
// TODO set the referrer to the document.
|
||||||
try session.window.replaceDocument(html_doc);
|
try session.window.replaceDocument(html_doc);
|
||||||
session.window.setStorageShelf(
|
session.window.setStorageShelf(
|
||||||
try session.storage_shed.getOrPut(self.origin orelse "null"),
|
try session.storage_shed.getOrPut((try self.origin()) orelse "null"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/#read-html
|
// https://html.spec.whatwg.org/#read-html
|
||||||
@@ -511,7 +479,7 @@ pub const Page = struct {
|
|||||||
|
|
||||||
// replace the user context document with the new one.
|
// replace the user context document with the new one.
|
||||||
try session.env.setUserContext(.{
|
try session.env.setUserContext(.{
|
||||||
.uri = self.uri,
|
.url = @ptrCast(&self.url.?),
|
||||||
.document = html_doc,
|
.document = html_doc,
|
||||||
.renderer = @ptrCast(&self.renderer),
|
.renderer = @ptrCast(&self.renderer),
|
||||||
.cookie_jar = @ptrCast(&self.session.cookie_jar),
|
.cookie_jar = @ptrCast(&self.session.cookie_jar),
|
||||||
@@ -675,9 +643,6 @@ pub const Page = struct {
|
|||||||
fn fetchData(self: *const Page, arena: Allocator, src: []const u8, base: ?[]const u8) ![]const u8 {
|
fn fetchData(self: *const Page, arena: Allocator, src: []const u8, base: ?[]const u8) ![]const u8 {
|
||||||
log.debug("starting fetch {s}", .{src});
|
log.debug("starting fetch {s}", .{src});
|
||||||
|
|
||||||
var buffer: [1024]u8 = undefined;
|
|
||||||
var b: []u8 = buffer[0..];
|
|
||||||
|
|
||||||
var res_src = src;
|
var res_src = src;
|
||||||
|
|
||||||
// if a base path is given, we resolve src using base.
|
// if a base path is given, we resolve src using base.
|
||||||
@@ -687,19 +652,20 @@ pub const Page = struct {
|
|||||||
res_src = try std.fs.path.resolve(arena, &.{ _dir, src });
|
res_src = try std.fs.path.resolve(arena, &.{ _dir, src });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const u = try std.Uri.resolve_inplace(self.uri, res_src, &b);
|
var origin_url = &self.url.?;
|
||||||
|
const url = try origin_url.resolve(arena, res_src);
|
||||||
|
|
||||||
var request = try self.newHTTPRequest(.GET, u, .{
|
var request = try self.newHTTPRequest(.GET, &url, .{
|
||||||
.origin_uri = self.uri,
|
.origin_uri = &origin_url.uri,
|
||||||
.navigation = false,
|
.navigation = false,
|
||||||
});
|
});
|
||||||
defer request.deinit();
|
defer request.deinit();
|
||||||
|
|
||||||
var response = try request.sendSync(.{});
|
var response = try request.sendSync(.{});
|
||||||
var header = response.header;
|
var header = response.header;
|
||||||
try self.session.cookie_jar.populateFromResponse(u, &header);
|
try self.session.cookie_jar.populateFromResponse(&url.uri, &header);
|
||||||
|
|
||||||
log.info("fetch {any}: {d}", .{ u, header.status });
|
log.info("fetch {any}: {d}", .{ url, header.status });
|
||||||
|
|
||||||
if (header.status != 200) {
|
if (header.status != 200) {
|
||||||
return FetchError.BadStatusCode;
|
return FetchError.BadStatusCode;
|
||||||
@@ -726,13 +692,13 @@ pub const Page = struct {
|
|||||||
try s.eval(arena, &self.session.env, body);
|
try s.eval(arena, &self.session.env, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn newHTTPRequest(self: *const Page, method: http.Request.Method, uri: std.Uri, opts: storage.cookie.LookupOpts) !http.Request {
|
fn newHTTPRequest(self: *const Page, method: http.Request.Method, url: *const URL, opts: storage.cookie.LookupOpts) !http.Request {
|
||||||
const session = self.session;
|
const session = self.session;
|
||||||
var request = try session.http_client.request(method, uri);
|
var request = try session.http_client.request(method, &url.uri);
|
||||||
errdefer request.deinit();
|
errdefer request.deinit();
|
||||||
|
|
||||||
var arr: std.ArrayListUnmanaged(u8) = .{};
|
var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||||
try session.cookie_jar.forRequest(uri, arr.writer(self.arena), opts);
|
try session.cookie_jar.forRequest(&url.uri, arr.writer(self.arena), opts);
|
||||||
|
|
||||||
if (arr.items.len > 0) {
|
if (arr.items.len > 0) {
|
||||||
try request.addHeader("Cookie", arr.items, .{});
|
try request.addHeader("Cookie", arr.items, .{});
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
|||||||
|
|
||||||
pub fn getURL(self: *const Self) ?[]const u8 {
|
pub fn getURL(self: *const Self) ?[]const u8 {
|
||||||
const page = self.session.currentPage() orelse return null;
|
const page = self.session.currentPage() orelse return null;
|
||||||
return page.rawuri;
|
return if (page.url) |*url| url.raw else null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void {
|
pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void {
|
||||||
|
|||||||
@@ -64,11 +64,7 @@ fn dispatchMouseEvent(cmd: anytype) !void {
|
|||||||
else => unreachable,
|
else => unreachable,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const click_result = (try page.mouseEvent(cmd.arena, mouse_event)) orelse return;
|
try page.mouseEvent(mouse_event);
|
||||||
|
|
||||||
switch (click_result) {
|
|
||||||
.navigate => |uri| try clickNavigate(cmd, uri),
|
|
||||||
}
|
|
||||||
// result already sent
|
// result already sent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const Testing = @This();
|
|||||||
|
|
||||||
const main = @import("cdp.zig");
|
const main = @import("cdp.zig");
|
||||||
const parser = @import("netsurf");
|
const parser = @import("netsurf");
|
||||||
|
const URL = @import("../url.zig").URL;
|
||||||
const App = @import("../app.zig").App;
|
const App = @import("../app.zig").App;
|
||||||
|
|
||||||
const base = @import("../testing.zig");
|
const base = @import("../testing.zig");
|
||||||
@@ -85,8 +86,8 @@ const Session = struct {
|
|||||||
return error.MockBrowserPageAlreadyExists;
|
return error.MockBrowserPageAlreadyExists;
|
||||||
}
|
}
|
||||||
self.page = .{
|
self.page = .{
|
||||||
.rawuri = "",
|
|
||||||
.session = self,
|
.session = self,
|
||||||
|
.url = URL.parse("https://lightpanda.io/", null) catch unreachable,
|
||||||
.aux_data = try self.arena.dupe(u8, aux_data orelse ""),
|
.aux_data = try self.arena.dupe(u8, aux_data orelse ""),
|
||||||
};
|
};
|
||||||
return &self.page.?;
|
return &self.page.?;
|
||||||
@@ -104,7 +105,7 @@ const Session = struct {
|
|||||||
|
|
||||||
const Page = struct {
|
const Page = struct {
|
||||||
session: *Session,
|
session: *Session,
|
||||||
rawuri: []const u8,
|
url: ?URL = null,
|
||||||
aux_data: []const u8 = "",
|
aux_data: []const u8 = "",
|
||||||
doc: ?*parser.Document = null,
|
doc: ?*parser.Document = null,
|
||||||
|
|
||||||
@@ -114,10 +115,7 @@ const Page = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MouseEvent = @import("../browser/browser.zig").Page.MouseEvent;
|
const MouseEvent = @import("../browser/browser.zig").Page.MouseEvent;
|
||||||
const ClickResult = @import("../browser/browser.zig").Page.ClickResult;
|
pub fn mouseEvent(_: *Page, _: MouseEvent) !void {}
|
||||||
pub fn mouseEvent(_: *Page, _: Allocator, _: MouseEvent) !?ClickResult {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Client = struct {
|
const Client = struct {
|
||||||
|
|||||||
@@ -30,60 +30,60 @@ const checkCases = jsruntime.test_utils.checkCases;
|
|||||||
pub const Location = struct {
|
pub const Location = struct {
|
||||||
pub const mem_guarantied = true;
|
pub const mem_guarantied = true;
|
||||||
|
|
||||||
url: ?*URL = null,
|
url: ?URL = null,
|
||||||
|
|
||||||
pub fn deinit(_: *Location, _: std.mem.Allocator) void {}
|
pub fn deinit(_: *Location, _: std.mem.Allocator) void {}
|
||||||
|
|
||||||
pub fn get_href(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_href(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
||||||
if (self.url) |u| return u.get_href(alloc);
|
if (self.url) |*u| return u.get_href(alloc);
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_protocol(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_protocol(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
||||||
if (self.url) |u| return u.get_protocol(alloc);
|
if (self.url) |*u| return u.get_protocol(alloc);
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_host(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_host(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
||||||
if (self.url) |u| return u.get_host(alloc);
|
if (self.url) |*u| return u.get_host(alloc);
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_hostname(self: *Location) []const u8 {
|
pub fn get_hostname(self: *Location) []const u8 {
|
||||||
if (self.url) |u| return u.get_hostname();
|
if (self.url) |*u| return u.get_hostname();
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_port(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_port(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
||||||
if (self.url) |u| return u.get_port(alloc);
|
if (self.url) |*u| return u.get_port(alloc);
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_pathname(self: *Location) []const u8 {
|
pub fn get_pathname(self: *Location) []const u8 {
|
||||||
if (self.url) |u| return u.get_pathname();
|
if (self.url) |*u| return u.get_pathname();
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_search(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_search(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
||||||
if (self.url) |u| return u.get_search(alloc);
|
if (self.url) |*u| return u.get_search(alloc);
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_hash(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_hash(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
||||||
if (self.url) |u| return u.get_hash(alloc);
|
if (self.url) |*u| return u.get_hash(alloc);
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_origin(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
pub fn get_origin(self: *Location, alloc: std.mem.Allocator) ![]const u8 {
|
||||||
if (self.url) |u| return u.get_origin(alloc);
|
if (self.url) |*u| return u.get_origin(alloc);
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const Callback = jsruntime.Callback;
|
|||||||
const CallbackArg = jsruntime.CallbackArg;
|
const CallbackArg = jsruntime.CallbackArg;
|
||||||
const Loop = jsruntime.Loop;
|
const Loop = jsruntime.Loop;
|
||||||
|
|
||||||
|
const URL = @import("../../../url.zig").URL;
|
||||||
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||||
const Navigator = @import("navigator.zig").Navigator;
|
const Navigator = @import("navigator.zig").Navigator;
|
||||||
const History = @import("history.zig").History;
|
const History = @import("history.zig").History;
|
||||||
@@ -31,8 +32,6 @@ const Location = @import("location.zig").Location;
|
|||||||
|
|
||||||
const storage = @import("../storage/storage.zig");
|
const storage = @import("../storage/storage.zig");
|
||||||
|
|
||||||
var emptyLocation = Location{};
|
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#interface-window-extensions
|
// https://dom.spec.whatwg.org/#interface-window-extensions
|
||||||
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
|
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
|
||||||
pub const Window = struct {
|
pub const Window = struct {
|
||||||
@@ -46,9 +45,8 @@ pub const Window = struct {
|
|||||||
document: ?*parser.DocumentHTML = null,
|
document: ?*parser.DocumentHTML = null,
|
||||||
target: []const u8,
|
target: []const u8,
|
||||||
history: History = .{},
|
history: History = .{},
|
||||||
location: *Location = &emptyLocation,
|
location: Location = .{},
|
||||||
|
storage_shelf: ?*storage.Shelf = null,
|
||||||
storageShelf: ?*storage.Shelf = null,
|
|
||||||
|
|
||||||
// store a map between internal timeouts ids and pointers to uint.
|
// store a map between internal timeouts ids and pointers to uint.
|
||||||
// the maximum number of possible timeouts is fixed.
|
// the maximum number of possible timeouts is fixed.
|
||||||
@@ -64,21 +62,20 @@ pub const Window = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replaceLocation(self: *Window, loc: ?*Location) !void {
|
pub fn replaceLocation(self: *Window, loc: Location) !void {
|
||||||
self.location = loc orelse &emptyLocation;
|
self.location = loc;
|
||||||
|
|
||||||
if (self.document) |doc| {
|
if (self.document) |doc| {
|
||||||
try parser.documentHTMLSetLocation(Location, doc, self.location);
|
try parser.documentHTMLSetLocation(Location, doc, &self.location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replaceDocument(self: *Window, doc: *parser.DocumentHTML) !void {
|
pub fn replaceDocument(self: *Window, doc: *parser.DocumentHTML) !void {
|
||||||
self.document = doc;
|
self.document = doc;
|
||||||
try parser.documentHTMLSetLocation(Location, doc, self.location);
|
try parser.documentHTMLSetLocation(Location, doc, &self.location);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setStorageShelf(self: *Window, shelf: *storage.Shelf) void {
|
pub fn setStorageShelf(self: *Window, shelf: *storage.Shelf) void {
|
||||||
self.storageShelf = shelf;
|
self.storage_shelf = shelf;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_window(self: *Window) *Window {
|
pub fn get_window(self: *Window) *Window {
|
||||||
@@ -90,7 +87,7 @@ pub const Window = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_location(self: *Window) *Location {
|
pub fn get_location(self: *Window) *Location {
|
||||||
return self.location;
|
return &self.location;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_self(self: *Window) *Window {
|
pub fn get_self(self: *Window) *Window {
|
||||||
@@ -114,13 +111,13 @@ pub const Window = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_localStorage(self: *Window) !*storage.Bottle {
|
pub fn get_localStorage(self: *Window) !*storage.Bottle {
|
||||||
if (self.storageShelf == null) return parser.DOMError.NotSupported;
|
if (self.storage_shelf == null) return parser.DOMError.NotSupported;
|
||||||
return &self.storageShelf.?.bucket.local;
|
return &self.storage_shelf.?.bucket.local;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_sessionStorage(self: *Window) !*storage.Bottle {
|
pub fn get_sessionStorage(self: *Window) !*storage.Bottle {
|
||||||
if (self.storageShelf == null) return parser.DOMError.NotSupported;
|
if (self.storage_shelf == null) return parser.DOMError.NotSupported;
|
||||||
return &self.storageShelf.?.bucket.session;
|
return &self.storage_shelf.?.bucket.session;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle callback arguments.
|
// TODO handle callback arguments.
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const builtin = @import("builtin");
|
|||||||
|
|
||||||
const os = std.os;
|
const os = std.os;
|
||||||
const posix = std.posix;
|
const posix = std.posix;
|
||||||
|
const Uri = std.Uri;
|
||||||
const Thread = std.Thread;
|
const Thread = std.Thread;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const MemoryPool = std.heap.MemoryPool;
|
const MemoryPool = std.heap.MemoryPool;
|
||||||
@@ -72,7 +73,7 @@ pub const Client = struct {
|
|||||||
self.state_pool.deinit(allocator);
|
self.state_pool.deinit(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request(self: *Client, method: Request.Method, url: anytype) !Request {
|
pub fn request(self: *Client, method: Request.Method, uri: *const Uri) !Request {
|
||||||
const state = self.state_pool.acquire();
|
const state = self.state_pool.acquire();
|
||||||
|
|
||||||
errdefer {
|
errdefer {
|
||||||
@@ -80,7 +81,7 @@ pub const Client = struct {
|
|||||||
self.state_pool.release(state);
|
self.state_pool.release(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Request.init(self, state, method, url);
|
return Request.init(self, state, method, uri);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +98,11 @@ pub const Request = struct {
|
|||||||
method: Method,
|
method: Method,
|
||||||
|
|
||||||
// The URI we're requested
|
// The URI we're requested
|
||||||
uri: std.Uri,
|
uri: *const Uri,
|
||||||
|
|
||||||
|
// If we're redirecting, this is where we're redirecting to. The only reason
|
||||||
|
// we really have this is so that we can set self.uri = &self.redirect_url.?
|
||||||
|
redirect_uri: ?Uri = null,
|
||||||
|
|
||||||
// Optional body
|
// Optional body
|
||||||
body: ?[]const u8,
|
body: ?[]const u8,
|
||||||
@@ -143,19 +148,7 @@ pub const Request = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// url can either be a `[]const u8`, in which case we'll clone + parse, or a std.Uri
|
fn init(client: *Client, state: *State, method: Method, uri: *const Uri) !Request {
|
||||||
fn init(client: *Client, state: *State, method: Method, url: anytype) !Request {
|
|
||||||
var arena = state.arena.allocator();
|
|
||||||
|
|
||||||
var uri: std.Uri = undefined;
|
|
||||||
|
|
||||||
if (@TypeOf(url) == std.Uri) {
|
|
||||||
uri = url;
|
|
||||||
} else {
|
|
||||||
const owned = try arena.dupe(u8, url);
|
|
||||||
uri = try std.Uri.parse(owned);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.host == null) {
|
if (uri.host == null) {
|
||||||
return error.UriMissingHost;
|
return error.UriMissingHost;
|
||||||
}
|
}
|
||||||
@@ -166,7 +159,7 @@ pub const Request = struct {
|
|||||||
.method = method,
|
.method = method,
|
||||||
.body = null,
|
.body = null,
|
||||||
.headers = .{},
|
.headers = .{},
|
||||||
.arena = arena,
|
.arena = state.arena.allocator(),
|
||||||
._socket = null,
|
._socket = null,
|
||||||
._state = state,
|
._state = state,
|
||||||
._client = client,
|
._client = client,
|
||||||
@@ -320,7 +313,9 @@ pub const Request = struct {
|
|||||||
var buf = try self.arena.alloc(u8, 1024);
|
var buf = try self.arena.alloc(u8, 1024);
|
||||||
|
|
||||||
const previous_host = self.host();
|
const previous_host = self.host();
|
||||||
self.uri = try self.uri.resolve_inplace(redirect.location, &buf);
|
self.redirect_uri = try self.uri.resolve_inplace(redirect.location, &buf);
|
||||||
|
|
||||||
|
self.uri = &self.redirect_uri.?;
|
||||||
try self.verifyUri();
|
try self.verifyUri();
|
||||||
|
|
||||||
if (redirect.use_get) {
|
if (redirect.use_get) {
|
||||||
@@ -1753,14 +1748,16 @@ test "HttpClient Reader: fuzz" {
|
|||||||
test "HttpClient: invalid url" {
|
test "HttpClient: invalid url" {
|
||||||
var client = try testClient();
|
var client = try testClient();
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
try testing.expectError(error.UriMissingHost, client.request(.GET, "http:///"));
|
const uri = try Uri.parse("http:///");
|
||||||
|
try testing.expectError(error.UriMissingHost, client.request(.GET, &uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "HttpClient: sync connect error" {
|
test "HttpClient: sync connect error" {
|
||||||
var client = try testClient();
|
var client = try testClient();
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "HTTP://127.0.0.1:9920");
|
const uri = try Uri.parse("HTTP://127.0.0.1:9920");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
try testing.expectError(error.ConnectionRefused, req.sendSync(.{}));
|
try testing.expectError(error.ConnectionRefused, req.sendSync(.{}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1768,7 +1765,8 @@ test "HttpClient: sync no body" {
|
|||||||
var client = try testClient();
|
var client = try testClient();
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/simple");
|
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/simple");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
var res = try req.sendSync(.{});
|
var res = try req.sendSync(.{});
|
||||||
|
|
||||||
try testing.expectEqual(null, try res.next());
|
try testing.expectEqual(null, try res.next());
|
||||||
@@ -1783,7 +1781,8 @@ test "HttpClient: sync tls no body" {
|
|||||||
var client = try testClient();
|
var client = try testClient();
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/simple");
|
const uri = try Uri.parse("https://127.0.0.1:9581/http_client/simple");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
var res = try req.sendSync(.{ .tls_verify_host = false });
|
var res = try req.sendSync(.{ .tls_verify_host = false });
|
||||||
|
|
||||||
try testing.expectEqual(null, try res.next());
|
try testing.expectEqual(null, try res.next());
|
||||||
@@ -1797,7 +1796,8 @@ test "HttpClient: sync with body" {
|
|||||||
var client = try testClient();
|
var client = try testClient();
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/echo");
|
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/echo");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
var res = try req.sendSync(.{});
|
var res = try req.sendSync(.{});
|
||||||
|
|
||||||
try testing.expectEqual("over 9000!", try res.next());
|
try testing.expectEqual("over 9000!", try res.next());
|
||||||
@@ -1820,7 +1820,8 @@ test "HttpClient: sync tls with body" {
|
|||||||
var client = try testClient();
|
var client = try testClient();
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/body");
|
const uri = try Uri.parse("https://127.0.0.1:9581/http_client/body");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
var res = try req.sendSync(.{ .tls_verify_host = false });
|
var res = try req.sendSync(.{ .tls_verify_host = false });
|
||||||
|
|
||||||
while (try res.next()) |data| {
|
while (try res.next()) |data| {
|
||||||
@@ -1844,7 +1845,8 @@ test "HttpClient: sync redirect from TLS to Plaintext" {
|
|||||||
var client = try testClient();
|
var client = try testClient();
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/redirect/insecure");
|
const uri = try Uri.parse("https://127.0.0.1:9581/http_client/redirect/insecure");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
var res = try req.sendSync(.{ .tls_verify_host = false });
|
var res = try req.sendSync(.{ .tls_verify_host = false });
|
||||||
|
|
||||||
while (try res.next()) |data| {
|
while (try res.next()) |data| {
|
||||||
@@ -1871,7 +1873,8 @@ test "HttpClient: sync redirect plaintext to TLS" {
|
|||||||
var client = try testClient();
|
var client = try testClient();
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/redirect/secure");
|
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/redirect/secure");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
var res = try req.sendSync(.{ .tls_verify_host = false });
|
var res = try req.sendSync(.{ .tls_verify_host = false });
|
||||||
|
|
||||||
while (try res.next()) |data| {
|
while (try res.next()) |data| {
|
||||||
@@ -1889,7 +1892,8 @@ test "HttpClient: sync GET redirect" {
|
|||||||
var client = try testClient();
|
var client = try testClient();
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/redirect");
|
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/redirect");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
var res = try req.sendSync(.{ .tls_verify_host = false });
|
var res = try req.sendSync(.{ .tls_verify_host = false });
|
||||||
|
|
||||||
try testing.expectEqual("over 9000!", try res.next());
|
try testing.expectEqual("over 9000!", try res.next());
|
||||||
@@ -1925,7 +1929,8 @@ test "HttpClient: async connect error" {
|
|||||||
var client = try testClient();
|
var client = try testClient();
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "HTTP://127.0.0.1:9920");
|
const uri = try Uri.parse("HTTP://127.0.0.1:9920");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
try req.sendAsync(&loop, Handler{ .reset = &reset }, .{});
|
try req.sendAsync(&loop, Handler{ .reset = &reset }, .{});
|
||||||
try loop.io.run_for_ns(std.time.ns_per_ms);
|
try loop.io.run_for_ns(std.time.ns_per_ms);
|
||||||
try reset.timedWait(std.time.ns_per_s);
|
try reset.timedWait(std.time.ns_per_s);
|
||||||
@@ -1938,7 +1943,8 @@ test "HttpClient: async no body" {
|
|||||||
var handler = try CaptureHandler.init();
|
var handler = try CaptureHandler.init();
|
||||||
defer handler.deinit();
|
defer handler.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "HTTP://127.0.0.1:9582/http_client/simple");
|
const uri = try Uri.parse("HTTP://127.0.0.1:9582/http_client/simple");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
try req.sendAsync(&handler.loop, &handler, .{});
|
try req.sendAsync(&handler.loop, &handler, .{});
|
||||||
try handler.waitUntilDone();
|
try handler.waitUntilDone();
|
||||||
|
|
||||||
@@ -1955,7 +1961,8 @@ test "HttpClient: async with body" {
|
|||||||
var handler = try CaptureHandler.init();
|
var handler = try CaptureHandler.init();
|
||||||
defer handler.deinit();
|
defer handler.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "HTTP://127.0.0.1:9582/http_client/echo");
|
const uri = try Uri.parse("HTTP://127.0.0.1:9582/http_client/echo");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
try req.sendAsync(&handler.loop, &handler, .{});
|
try req.sendAsync(&handler.loop, &handler, .{});
|
||||||
try handler.waitUntilDone();
|
try handler.waitUntilDone();
|
||||||
|
|
||||||
@@ -1978,7 +1985,8 @@ test "HttpClient: async redirect" {
|
|||||||
var handler = try CaptureHandler.init();
|
var handler = try CaptureHandler.init();
|
||||||
defer handler.deinit();
|
defer handler.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "HTTP://127.0.0.1:9582/http_client/redirect");
|
const uri = try Uri.parse("HTTP://127.0.0.1:9582/http_client/redirect");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
try req.sendAsync(&handler.loop, &handler, .{});
|
try req.sendAsync(&handler.loop, &handler, .{});
|
||||||
|
|
||||||
// Called twice on purpose. The initial GET resutls in the # of pending
|
// Called twice on purpose. The initial GET resutls in the # of pending
|
||||||
@@ -2008,7 +2016,8 @@ test "HttpClient: async tls no body" {
|
|||||||
var handler = try CaptureHandler.init();
|
var handler = try CaptureHandler.init();
|
||||||
defer handler.deinit();
|
defer handler.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "HTTPs://127.0.0.1:9581/http_client/simple");
|
const uri = try Uri.parse("HTTPs://127.0.0.1:9581/http_client/simple");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
try req.sendAsync(&handler.loop, &handler, .{ .tls_verify_host = false });
|
try req.sendAsync(&handler.loop, &handler, .{ .tls_verify_host = false });
|
||||||
try handler.waitUntilDone();
|
try handler.waitUntilDone();
|
||||||
|
|
||||||
@@ -2027,7 +2036,8 @@ test "HttpClient: async tls with body" {
|
|||||||
var handler = try CaptureHandler.init();
|
var handler = try CaptureHandler.init();
|
||||||
defer handler.deinit();
|
defer handler.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "HTTPs://127.0.0.1:9581/http_client/body");
|
const uri = try Uri.parse("HTTPs://127.0.0.1:9581/http_client/body");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
try req.sendAsync(&handler.loop, &handler, .{ .tls_verify_host = false });
|
try req.sendAsync(&handler.loop, &handler, .{ .tls_verify_host = false });
|
||||||
try handler.waitUntilDone();
|
try handler.waitUntilDone();
|
||||||
|
|
||||||
@@ -2051,7 +2061,8 @@ test "HttpClient: async redirect from TLS to Plaintext" {
|
|||||||
var handler = try CaptureHandler.init();
|
var handler = try CaptureHandler.init();
|
||||||
defer handler.deinit();
|
defer handler.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "https://127.0.0.1:9581/http_client/redirect/insecure");
|
const uri = try Uri.parse("https://127.0.0.1:9581/http_client/redirect/insecure");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
try req.sendAsync(&handler.loop, &handler, .{ .tls_verify_host = false });
|
try req.sendAsync(&handler.loop, &handler, .{ .tls_verify_host = false });
|
||||||
try handler.waitUntilDone();
|
try handler.waitUntilDone();
|
||||||
|
|
||||||
@@ -2074,7 +2085,8 @@ test "HttpClient: async redirect plaintext to TLS" {
|
|||||||
var handler = try CaptureHandler.init();
|
var handler = try CaptureHandler.init();
|
||||||
defer handler.deinit();
|
defer handler.deinit();
|
||||||
|
|
||||||
var req = try client.request(.GET, "http://127.0.0.1:9582/http_client/redirect/secure");
|
const uri = try Uri.parse("http://127.0.0.1:9582/http_client/redirect/secure");
|
||||||
|
var req = try client.request(.GET, &uri);
|
||||||
try req.sendAsync(&handler.loop, &handler, .{ .tls_verify_host = false });
|
try req.sendAsync(&handler.loop, &handler, .{ .tls_verify_host = false });
|
||||||
try handler.waitUntilDone();
|
try handler.waitUntilDone();
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ const browser = @import("browser/browser.zig");
|
|||||||
const Window = @import("html/window.zig").Window;
|
const Window = @import("html/window.zig").Window;
|
||||||
const xhr = @import("xhr/xhr.zig");
|
const xhr = @import("xhr/xhr.zig");
|
||||||
const storage = @import("storage/storage.zig");
|
const storage = @import("storage/storage.zig");
|
||||||
const URL = @import("url/url.zig").URL;
|
const URL = @import("url.zig").URL;
|
||||||
const urlquery = @import("url/query.zig");
|
|
||||||
const Location = @import("html/location.zig").Location;
|
|
||||||
|
|
||||||
const documentTestExecFn = @import("dom/document.zig").testExecFn;
|
const documentTestExecFn = @import("dom/document.zig").testExecFn;
|
||||||
const HTMLDocumentTestExecFn = @import("html/document.zig").testExecFn;
|
const HTMLDocumentTestExecFn = @import("html/document.zig").testExecFn;
|
||||||
@@ -94,9 +92,7 @@ fn testExecFn(
|
|||||||
// alias global as self and window
|
// alias global as self and window
|
||||||
var window = Window.create(null, null);
|
var window = Window.create(null, null);
|
||||||
|
|
||||||
const url = "https://lightpanda.io/opensource-browser/";
|
const url = try URL.parse("https://lightpanda.io/opensource-browser/", null);
|
||||||
var u = try URL.constructor(alloc, url, null);
|
|
||||||
defer u.deinit(alloc);
|
|
||||||
|
|
||||||
var cookie_jar = storage.CookieJar.init(alloc);
|
var cookie_jar = storage.CookieJar.init(alloc);
|
||||||
defer cookie_jar.deinit();
|
defer cookie_jar.deinit();
|
||||||
@@ -106,15 +102,14 @@ fn testExecFn(
|
|||||||
defer renderer.positions.deinit(alloc);
|
defer renderer.positions.deinit(alloc);
|
||||||
|
|
||||||
try js_env.setUserContext(.{
|
try js_env.setUserContext(.{
|
||||||
.uri = try std.Uri.parse(url),
|
.url = &url,
|
||||||
.document = doc,
|
.document = doc,
|
||||||
.renderer = &renderer,
|
.renderer = &renderer,
|
||||||
.cookie_jar = &cookie_jar,
|
.cookie_jar = &cookie_jar,
|
||||||
.http_client = &http_client,
|
.http_client = &http_client,
|
||||||
});
|
});
|
||||||
|
|
||||||
var location = Location{ .url = &u };
|
try window.replaceLocation(.{ .url = try url.toWebApi(alloc) });
|
||||||
try window.replaceLocation(&location);
|
|
||||||
|
|
||||||
try window.replaceDocument(doc);
|
try window.replaceDocument(doc);
|
||||||
window.setStorageShelf(&storageShelf);
|
window.setStorageShelf(&storageShelf);
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ const public_suffix_list = @import("../data/public_suffix_list.zig").lookup;
|
|||||||
|
|
||||||
const log = std.log.scoped(.cookie);
|
const log = std.log.scoped(.cookie);
|
||||||
|
|
||||||
pub const LookupOpts = struct { request_time: ?i64 = null, origin_uri: ?Uri = null, navigation: bool = true };
|
pub const LookupOpts = struct {
|
||||||
|
request_time: ?i64 = null,
|
||||||
|
origin_uri: ?*const Uri = null,
|
||||||
|
navigation: bool = true,
|
||||||
|
};
|
||||||
|
|
||||||
pub const Jar = struct {
|
pub const Jar = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
@@ -56,7 +60,7 @@ pub const Jar = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn forRequest(self: *Jar, target_uri: 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_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;
|
||||||
|
|
||||||
@@ -147,7 +151,7 @@ pub const Jar = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn populateFromResponse(self: *Jar, uri: Uri, header: *const http.ResponseHeader) !void {
|
pub fn populateFromResponse(self: *Jar, uri: *const Uri, header: *const http.ResponseHeader) !void {
|
||||||
const now = std.time.timestamp();
|
const now = std.time.timestamp();
|
||||||
var it = header.iterate("set-cookie");
|
var it = header.iterate("set-cookie");
|
||||||
while (it.next()) |set_cookie| {
|
while (it.next()) |set_cookie| {
|
||||||
@@ -226,7 +230,7 @@ fn areCookiesEqual(a: *const Cookie, b: *const Cookie) bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn areSameSite(origin_uri_: ?std.Uri, target_host: []const u8) !bool {
|
fn areSameSite(origin_uri_: ?*const 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;
|
||||||
|
|
||||||
@@ -284,7 +288,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: std.Uri, str: []const u8) !Cookie {
|
pub fn parse(allocator: Allocator, uri: *const std.Uri, str: []const u8) !Cookie {
|
||||||
if (str.len == 0) {
|
if (str.len == 0) {
|
||||||
// this check is necessary, `std.mem.minMax` asserts len > 0
|
// this check is necessary, `std.mem.minMax` asserts len > 0
|
||||||
return error.Empty;
|
return error.Empty;
|
||||||
@@ -501,28 +505,28 @@ 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_uri, "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_uri, "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_uri, "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_uri, "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_uri, "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_uri, "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_uri, "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_uri, "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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +535,7 @@ test "Jar: forRequest" {
|
|||||||
fn expect(expected: []const u8, jar: *Jar, target_uri: Uri, opts: LookupOpts) !void {
|
fn expect(expected: []const u8, jar: *Jar, target_uri: Uri, opts: LookupOpts) !void {
|
||||||
var arr: std.ArrayListUnmanaged(u8) = .{};
|
var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||||
defer arr.deinit(testing.allocator);
|
defer arr.deinit(testing.allocator);
|
||||||
try jar.forRequest(target_uri, arr.writer(testing.allocator), opts);
|
try jar.forRequest(&target_uri, arr.writer(testing.allocator), opts);
|
||||||
try testing.expectEqual(expected, arr.items);
|
try testing.expectEqual(expected, arr.items);
|
||||||
}
|
}
|
||||||
}.expect;
|
}.expect;
|
||||||
@@ -548,108 +552,108 @@ test "Jar: forRequest" {
|
|||||||
try expectCookies("", &jar, test_uri, .{});
|
try expectCookies("", &jar, test_uri, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
try jar.add(try Cookie.parse(testing.allocator, test_uri, "global1=1"), now);
|
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;domain=lightpanda.io"), 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_uri, "path1=3;Path=/about"), 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, "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, "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_uri, "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_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, "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, &test_uri_2, "domain1=9;domain=test.lightpanda.io"), now);
|
||||||
|
|
||||||
// nothing fancy here
|
// nothing fancy here
|
||||||
try expectCookies("global1=1, global2=2", &jar, test_uri, .{});
|
try expectCookies("global1=1, global2=2", &jar, test_uri, .{});
|
||||||
try expectCookies("global1=1, global2=2", &jar, test_uri, .{ .origin_uri = test_uri, .navigation = false });
|
try expectCookies("global1=1, global2=2", &jar, test_uri, .{ .origin_uri = &test_uri, .navigation = false });
|
||||||
|
|
||||||
// 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, try std.Uri.parse("http://anothersitelightpanda.io/"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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, try std.Uri.parse("http://lightpanda.io/about"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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, try std.Uri.parse("http://lightpanda.io/abou"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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, try std.Uri.parse("http://lightpanda.io/aboutus"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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, try std.Uri.parse("http://lightpanda.io/docs"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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, try std.Uri.parse("http://lightpanda.io/docs/"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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, try std.Uri.parse("http://lightpanda.io/docs/more"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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, try std.Uri.parse("https://lightpanda.io/"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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, try std.Uri.parse("https://lightpanda.io/x/"), .{
|
||||||
.origin_uri = try std.Uri.parse("https://example.com/"),
|
.origin_uri = &(try std.Uri.parse("https://example.com/")),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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, try std.Uri.parse("http://lightpanda.io/x/"), .{
|
||||||
.origin_uri = try std.Uri.parse("https://example.com/"),
|
.origin_uri = &(try std.Uri.parse("https://example.com/")),
|
||||||
});
|
});
|
||||||
|
|
||||||
// non-navigational cross domain, insecure
|
// non-navigational cross domain, insecure
|
||||||
try expectCookies("", &jar, try std.Uri.parse("http://lightpanda.io/x/"), .{
|
try expectCookies("", &jar, try std.Uri.parse("http://lightpanda.io/x/"), .{
|
||||||
.origin_uri = try std.Uri.parse("https://example.com/"),
|
.origin_uri = &(try std.Uri.parse("https://example.com/")),
|
||||||
.navigation = false,
|
.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, try std.Uri.parse("https://lightpanda.io/x/"), .{
|
||||||
.origin_uri = try std.Uri.parse("https://example.com/"),
|
.origin_uri = &(try std.Uri.parse("https://example.com/")),
|
||||||
.navigation = false,
|
.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, try std.Uri.parse("http://lightpanda.io/x/"), .{
|
||||||
.origin_uri = try std.Uri.parse("https://lightpanda.io/"),
|
.origin_uri = &(try std.Uri.parse("https://lightpanda.io/")),
|
||||||
.navigation = false,
|
.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, try std.Uri.parse("http://test.lightpanda.io/"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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, try std.Uri.parse("http://1.test.lightpanda.io/"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
// non-matching domain
|
// non-matching domain
|
||||||
try expectCookies("global2=2", &jar, try std.Uri.parse("http://other.lightpanda.io/"), .{
|
try expectCookies("global2=2", &jar, try std.Uri.parse("http://other.lightpanda.io/"), .{
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
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_uri, .{
|
||||||
.request_time = now + 100,
|
.request_time = now + 100,
|
||||||
.origin_uri = test_uri,
|
.origin_uri = &test_uri,
|
||||||
});
|
});
|
||||||
try testing.expectEqual(l - 1, jar.cookies.items.len);
|
try testing.expectEqual(l - 1, jar.cookies.items.len);
|
||||||
|
|
||||||
@@ -664,7 +668,7 @@ test "CookieList: write" {
|
|||||||
var cookie_list = CookieList{};
|
var cookie_list = CookieList{};
|
||||||
defer cookie_list.deinit(testing.allocator);
|
defer cookie_list.deinit(testing.allocator);
|
||||||
|
|
||||||
const c1 = try Cookie.parse(testing.allocator, test_uri, "cookie_name=cookie_value");
|
const c1 = try Cookie.parse(testing.allocator, &test_uri, "cookie_name=cookie_value");
|
||||||
defer c1.deinit();
|
defer c1.deinit();
|
||||||
{
|
{
|
||||||
try cookie_list._cookies.append(testing.allocator, &c1);
|
try cookie_list._cookies.append(testing.allocator, &c1);
|
||||||
@@ -672,7 +676,7 @@ test "CookieList: write" {
|
|||||||
try testing.expectEqual("cookie_name=cookie_value", arr.items);
|
try testing.expectEqual("cookie_name=cookie_value", arr.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
const c2 = try Cookie.parse(testing.allocator, test_uri, "x84");
|
const c2 = try Cookie.parse(testing.allocator, &test_uri, "x84");
|
||||||
defer c2.deinit();
|
defer c2.deinit();
|
||||||
{
|
{
|
||||||
arr.clearRetainingCapacity();
|
arr.clearRetainingCapacity();
|
||||||
@@ -681,7 +685,7 @@ test "CookieList: write" {
|
|||||||
try testing.expectEqual("cookie_name=cookie_value; x84", arr.items);
|
try testing.expectEqual("cookie_name=cookie_value; x84", arr.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
const c3 = try Cookie.parse(testing.allocator, test_uri, "nope=");
|
const c3 = try Cookie.parse(testing.allocator, &test_uri, "nope=");
|
||||||
defer c3.deinit();
|
defer c3.deinit();
|
||||||
{
|
{
|
||||||
arr.clearRetainingCapacity();
|
arr.clearRetainingCapacity();
|
||||||
@@ -866,7 +870,7 @@ const ExpectedCookie = struct {
|
|||||||
|
|
||||||
fn expectCookie(expected: ExpectedCookie, url: []const u8, set_cookie: []const u8) !void {
|
fn expectCookie(expected: ExpectedCookie, url: []const u8, set_cookie: []const u8) !void {
|
||||||
const uri = try Uri.parse(url);
|
const uri = try Uri.parse(url);
|
||||||
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();
|
||||||
|
|
||||||
try testing.expectEqual(expected.name, cookie.name);
|
try testing.expectEqual(expected.name, cookie.name);
|
||||||
@@ -882,7 +886,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 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();
|
||||||
|
|
||||||
inline for (@typeInfo(@TypeOf(expected)).@"struct".fields) |f| {
|
inline for (@typeInfo(@TypeOf(expected)).@"struct".fields) |f| {
|
||||||
@@ -896,7 +900,7 @@ 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 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 test_uri = Uri.parse("http://lightpanda.io/") catch unreachable;
|
const test_uri = Uri.parse("http://lightpanda.io/") catch unreachable;
|
||||||
|
|||||||
75
src/url.zig
Normal file
75
src/url.zig
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Uri = std.Uri;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const WebApiURL = @import("url/url.zig").URL;
|
||||||
|
|
||||||
|
pub const URL = struct {
|
||||||
|
uri: Uri,
|
||||||
|
raw: []const u8,
|
||||||
|
|
||||||
|
// We assume str will last as long as the URL
|
||||||
|
// In some cases, this is safe to do, because we know the URL is short lived.
|
||||||
|
// In most cases though, we assume the caller will just dupe the string URL
|
||||||
|
// into an arena
|
||||||
|
pub fn parse(str: []const u8, default_scheme: ?[]const u8) !URL {
|
||||||
|
const uri = Uri.parse(str) catch try Uri.parseAfterScheme(default_scheme orelse "https", str);
|
||||||
|
if (uri.host == null) {
|
||||||
|
return error.MissingHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.assert(uri.host.? == .percent_encoded);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.uri = uri,
|
||||||
|
.raw = str,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromURI(arena: Allocator, uri: *const Uri) !URL {
|
||||||
|
// This is embarrassing.
|
||||||
|
var buf: std.ArrayListUnmanaged(u8) = .{};
|
||||||
|
try uri.writeToStream(.{
|
||||||
|
.scheme = true,
|
||||||
|
.authentication = true,
|
||||||
|
.authority = true,
|
||||||
|
.path = true,
|
||||||
|
.query = true,
|
||||||
|
.fragment = true,
|
||||||
|
}, buf.writer(arena));
|
||||||
|
|
||||||
|
return parse(buf.items, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Above, in `parse, we error if a host doesn't exist
|
||||||
|
// In other words, we can't have a URL with a null host.
|
||||||
|
pub fn host(self: *const URL) []const u8 {
|
||||||
|
return self.uri.host.?.percent_encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn port(self: *const URL) ?u16 {
|
||||||
|
return self.uri.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scheme(self: *const URL) []const u8 {
|
||||||
|
return self.uri.scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn origin(self: *const URL, writer: anytype) !void {
|
||||||
|
return self.uri.writeToStream(.{ .scheme = true, .authority = true }, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(self: *const URL, arena: Allocator, url: []const u8) !URL {
|
||||||
|
var buf = try arena.alloc(u8, 1024);
|
||||||
|
const new_uri = try self.uri.resolve_inplace(url, &buf);
|
||||||
|
return fromURI(arena, &new_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(self: *const URL, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||||
|
return writer.writeAll(self.raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toWebApi(self: *const URL, allocator: Allocator) !WebApiURL {
|
||||||
|
return WebApiURL.init(allocator, self.uri);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -44,25 +44,24 @@ pub const Interfaces = .{
|
|||||||
// 2. The other way would bu to copy the `std.Uri` code to ahve a dedicated
|
// 2. The other way would bu to copy the `std.Uri` code to ahve a dedicated
|
||||||
// parser including the characters we want for the web API.
|
// parser including the characters we want for the web API.
|
||||||
pub const URL = struct {
|
pub const URL = struct {
|
||||||
rawuri: []const u8,
|
|
||||||
uri: std.Uri,
|
uri: std.Uri,
|
||||||
search_params: URLSearchParams,
|
search_params: URLSearchParams,
|
||||||
|
|
||||||
pub const mem_guarantied = true;
|
pub const mem_guarantied = true;
|
||||||
|
|
||||||
pub fn constructor(alloc: std.mem.Allocator, url: []const u8, base: ?[]const u8) !URL {
|
pub fn constructor(arena: std.mem.Allocator, url: []const u8, base: ?[]const u8) !URL {
|
||||||
const raw = try std.mem.concat(alloc, u8, &[_][]const u8{ url, base orelse "" });
|
const raw = try std.mem.concat(arena, u8, &[_][]const u8{ url, base orelse "" });
|
||||||
errdefer alloc.free(raw);
|
errdefer arena.free(raw);
|
||||||
|
|
||||||
const uri = std.Uri.parse(raw) catch {
|
const uri = std.Uri.parse(raw) catch return error.TypeError;
|
||||||
return error.TypeError;
|
return init(arena, uri);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
pub fn init(arena: std.mem.Allocator, uri: std.Uri) !URL {
|
||||||
return .{
|
return .{
|
||||||
.rawuri = raw,
|
|
||||||
.uri = uri,
|
.uri = uri,
|
||||||
.search_params = try URLSearchParams.constructor(
|
.search_params = try URLSearchParams.constructor(
|
||||||
alloc,
|
arena,
|
||||||
uriComponentNullStr(uri.query),
|
uriComponentNullStr(uri.query),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -70,7 +69,6 @@ pub const URL = struct {
|
|||||||
|
|
||||||
pub fn deinit(self: *URL, alloc: std.mem.Allocator) void {
|
pub fn deinit(self: *URL, alloc: std.mem.Allocator) void {
|
||||||
self.search_params.deinit(alloc);
|
self.search_params.deinit(alloc);
|
||||||
alloc.free(self.rawuri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the caller must free the returned string.
|
// the caller must free the returned string.
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const parser = @import("netsurf");
|
const parser = @import("netsurf");
|
||||||
|
const URL = @import("url.zig").URL;
|
||||||
const storage = @import("storage/storage.zig");
|
const storage = @import("storage/storage.zig");
|
||||||
const Client = @import("http/client.zig").Client;
|
const Client = @import("http/client.zig").Client;
|
||||||
const Renderer = @import("browser/browser.zig").Renderer;
|
const Renderer = @import("browser/browser.zig").Renderer;
|
||||||
|
|
||||||
pub const UserContext = struct {
|
pub const UserContext = struct {
|
||||||
uri: std.Uri,
|
url: *const URL,
|
||||||
http_client: *Client,
|
http_client: *Client,
|
||||||
document: *parser.DocumentHTML,
|
document: *parser.DocumentHTML,
|
||||||
cookie_jar: *storage.CookieJar,
|
cookie_jar: *storage.CookieJar,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const XMLHttpRequestEventTarget = @import("event_target.zig").XMLHttpRequestEven
|
|||||||
const Mime = @import("../browser/mime.zig").Mime;
|
const Mime = @import("../browser/mime.zig").Mime;
|
||||||
|
|
||||||
const Loop = jsruntime.Loop;
|
const Loop = jsruntime.Loop;
|
||||||
|
const URL = @import("../url.zig").URL;
|
||||||
const http = @import("../http/client.zig");
|
const http = @import("../http/client.zig");
|
||||||
|
|
||||||
const parser = @import("netsurf");
|
const parser = @import("netsurf");
|
||||||
@@ -103,8 +104,9 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
method: http.Request.Method,
|
method: http.Request.Method,
|
||||||
state: State,
|
state: State,
|
||||||
url: ?[]const u8,
|
url: ?URL = null,
|
||||||
uri: std.Uri,
|
origin_url: *const URL,
|
||||||
|
|
||||||
// request headers
|
// request headers
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
sync: bool = true,
|
sync: bool = true,
|
||||||
@@ -113,7 +115,6 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
cookie_jar: *CookieJar,
|
cookie_jar: *CookieJar,
|
||||||
// the URI of the page where this request is originating from
|
// the URI of the page where this request is originating from
|
||||||
origin_uri: std.Uri,
|
|
||||||
|
|
||||||
// TODO uncomment this field causes casting issue with
|
// TODO uncomment this field causes casting issue with
|
||||||
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
|
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
|
||||||
@@ -291,17 +292,15 @@ pub const XMLHttpRequest = struct {
|
|||||||
.headers = Headers.init(alloc),
|
.headers = Headers.init(alloc),
|
||||||
.response_headers = Headers.init(alloc),
|
.response_headers = Headers.init(alloc),
|
||||||
.method = undefined,
|
.method = undefined,
|
||||||
.url = null,
|
|
||||||
.uri = undefined,
|
|
||||||
.state = .unsent,
|
.state = .unsent,
|
||||||
.origin_uri = userctx.uri,
|
.url = null,
|
||||||
|
.origin_url = userctx.url,
|
||||||
.client = userctx.http_client,
|
.client = userctx.http_client,
|
||||||
.cookie_jar = userctx.cookie_jar,
|
.cookie_jar = userctx.cookie_jar,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(self: *XMLHttpRequest, alloc: std.mem.Allocator) void {
|
pub fn reset(self: *XMLHttpRequest) void {
|
||||||
if (self.url) |v| alloc.free(v);
|
|
||||||
self.url = null;
|
self.url = null;
|
||||||
|
|
||||||
if (self.response_obj) |v| v.deinit();
|
if (self.response_obj) |v| v.deinit();
|
||||||
@@ -379,13 +378,9 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
self.method = try validMethod(method);
|
self.method = try validMethod(method);
|
||||||
|
|
||||||
self.reset(alloc);
|
self.reset();
|
||||||
|
|
||||||
self.url = try alloc.dupe(u8, url);
|
self.url = try self.origin_url.resolve(alloc, url);
|
||||||
self.uri = std.Uri.parse(self.url.?) catch |err| {
|
|
||||||
log.debug("parse url ({s}): {any}", .{ self.url.?, err });
|
|
||||||
return DOMError.Syntax;
|
|
||||||
};
|
|
||||||
log.debug("open url ({s})", .{self.url.?});
|
log.debug("open url ({s})", .{self.url.?});
|
||||||
self.sync = if (asyn) |b| !b else false;
|
self.sync = if (asyn) |b| !b else false;
|
||||||
|
|
||||||
@@ -475,12 +470,12 @@ pub const XMLHttpRequest = struct {
|
|||||||
if (self.state != .opened) return DOMError.InvalidState;
|
if (self.state != .opened) return DOMError.InvalidState;
|
||||||
if (self.send_flag) return DOMError.InvalidState;
|
if (self.send_flag) return DOMError.InvalidState;
|
||||||
|
|
||||||
log.debug("{any} {any}", .{ self.method, self.uri });
|
log.debug("{any} {any}", .{ self.method, self.url });
|
||||||
|
|
||||||
self.send_flag = true;
|
self.send_flag = true;
|
||||||
self.priv_state = .open;
|
self.priv_state = .open;
|
||||||
|
|
||||||
self.request = try self.client.request(self.method, self.uri);
|
self.request = try self.client.request(self.method, &self.url.?.uri);
|
||||||
var request = &self.request.?;
|
var request = &self.request.?;
|
||||||
errdefer request.deinit();
|
errdefer request.deinit();
|
||||||
|
|
||||||
@@ -490,9 +485,9 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
{
|
{
|
||||||
var arr: std.ArrayListUnmanaged(u8) = .{};
|
var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||||
try self.cookie_jar.forRequest(self.uri, arr.writer(alloc), .{
|
try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(alloc), .{
|
||||||
.navigation = false,
|
.navigation = false,
|
||||||
.origin_uri = self.origin_uri,
|
.origin_uri = &self.origin_url.uri,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (arr.items.len > 0) {
|
if (arr.items.len > 0) {
|
||||||
@@ -522,7 +517,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
if (progress.first) {
|
if (progress.first) {
|
||||||
const header = progress.header;
|
const header = progress.header;
|
||||||
log.info("{any} {any} {d}", .{ self.method, self.uri, header.status });
|
log.info("{any} {any} {d}", .{ self.method, self.url, header.status });
|
||||||
|
|
||||||
self.priv_state = .done;
|
self.priv_state = .done;
|
||||||
|
|
||||||
@@ -546,7 +541,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
self.state = .loading;
|
self.state = .loading;
|
||||||
self.dispatchEvt("readystatechange");
|
self.dispatchEvt("readystatechange");
|
||||||
|
|
||||||
try self.cookie_jar.populateFromResponse(self.uri, &header);
|
try self.cookie_jar.populateFromResponse(self.request.?.uri, &header);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress.data) |data| {
|
if (progress.data) |data| {
|
||||||
@@ -588,7 +583,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
self.dispatchProgressEvent("error", .{});
|
self.dispatchProgressEvent("error", .{});
|
||||||
self.dispatchProgressEvent("loadend", .{});
|
self.dispatchProgressEvent("loadend", .{});
|
||||||
|
|
||||||
log.debug("{any} {any} {any}", .{ self.method, self.uri, self.err });
|
log.debug("{any} {any} {any}", .{ self.method, self.url, self.err });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _abort(self: *XMLHttpRequest) void {
|
pub fn _abort(self: *XMLHttpRequest) void {
|
||||||
@@ -637,7 +632,8 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
// TODO retrieve the redirected url
|
// TODO retrieve the redirected url
|
||||||
pub fn get_responseURL(self: *XMLHttpRequest) ?[]const u8 {
|
pub fn get_responseURL(self: *XMLHttpRequest) ?[]const u8 {
|
||||||
return self.url;
|
const url = &(self.url orelse return null);
|
||||||
|
return url.raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_responseXML(self: *XMLHttpRequest, alloc: std.mem.Allocator) !?Response {
|
pub fn get_responseXML(self: *XMLHttpRequest, alloc: std.mem.Allocator) !?Response {
|
||||||
|
|||||||
2
vendor/zig-js-runtime
vendored
2
vendor/zig-js-runtime
vendored
Submodule vendor/zig-js-runtime updated: f3a9e3d448...9b87782f1e
Reference in New Issue
Block a user