mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 22:53:28 +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 Walker = @import("../dom/walker.zig").WalkerDepthFirst;
|
||||
|
||||
const URL = @import("../url/url.zig").URL;
|
||||
const Location = @import("../html/location.zig").Location;
|
||||
|
||||
const URL = @import("../url.zig").URL;
|
||||
const storage = @import("../storage/storage.zig");
|
||||
|
||||
const http = @import("../http/client.zig");
|
||||
@@ -253,7 +251,7 @@ pub const Session = struct {
|
||||
self.env.stop();
|
||||
// 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});
|
||||
};
|
||||
|
||||
@@ -269,7 +267,7 @@ pub const Session = struct {
|
||||
|
||||
fn contextCreated(self: *Session, page: *Page, aux_data: ?[]const u8) void {
|
||||
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,
|
||||
doc: ?*parser.Document = null,
|
||||
|
||||
// handle url
|
||||
rawuri: ?[]const u8 = null,
|
||||
uri: std.Uri = undefined,
|
||||
origin: ?[]const u8 = null,
|
||||
|
||||
// html url and location
|
||||
// The URL of the page
|
||||
url: ?URL = null,
|
||||
location: Location = .{},
|
||||
|
||||
raw_data: ?[]const u8 = null,
|
||||
|
||||
@@ -342,62 +334,52 @@ pub const Page = struct {
|
||||
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
|
||||
// - aux_data: extra data forwarded to the Inspector
|
||||
// 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;
|
||||
|
||||
log.debug("starting GET {s}", .{uri});
|
||||
log.debug("starting GET {s}", .{url_string});
|
||||
|
||||
// if the uri is about:blank, nothing to do.
|
||||
if (std.mem.eql(u8, "about:blank", uri)) {
|
||||
// if the url is about:blank, nothing to do.
|
||||
if (std.mem.eql(u8, "about:blank", url_string)) {
|
||||
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 = .{
|
||||
.proxy = false,
|
||||
.tls = std.ascii.eqlIgnoreCase(self.uri.scheme, "https"),
|
||||
.tls = std.ascii.eqlIgnoreCase(self.url.?.scheme(), "https"),
|
||||
} });
|
||||
|
||||
// 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();
|
||||
|
||||
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;
|
||||
try self.session.cookie_jar.populateFromResponse(request.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.?);
|
||||
try self.session.cookie_jar.populateFromResponse(&url.uri, &header);
|
||||
|
||||
// TODO handle fragment in url.
|
||||
self.url = try URL.constructor(arena, self.rawuri.?, null);
|
||||
self.location.url = &self.url.?;
|
||||
try self.session.window.replaceLocation(&self.location);
|
||||
try self.session.window.replaceLocation(.{ .url = try url.toWebApi(arena) });
|
||||
|
||||
// prepare origin value.
|
||||
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 });
|
||||
log.info("GET {any} {d}", .{ url, header.status });
|
||||
|
||||
const ct = blk: {
|
||||
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) {
|
||||
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();
|
||||
defer parser.mouseEventDestroy(event);
|
||||
@@ -458,20 +440,6 @@ pub const Page = struct {
|
||||
.y = me.y,
|
||||
});
|
||||
_ = 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
|
||||
@@ -495,13 +463,13 @@ pub const Page = struct {
|
||||
// https://html.spec.whatwg.org/#reporting-document-loading-status
|
||||
|
||||
// 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;
|
||||
// TODO set the referrer to the document.
|
||||
try session.window.replaceDocument(html_doc);
|
||||
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
|
||||
@@ -511,7 +479,7 @@ pub const Page = struct {
|
||||
|
||||
// replace the user context document with the new one.
|
||||
try session.env.setUserContext(.{
|
||||
.uri = self.uri,
|
||||
.url = @ptrCast(&self.url.?),
|
||||
.document = html_doc,
|
||||
.renderer = @ptrCast(&self.renderer),
|
||||
.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 {
|
||||
log.debug("starting fetch {s}", .{src});
|
||||
|
||||
var buffer: [1024]u8 = undefined;
|
||||
var b: []u8 = buffer[0..];
|
||||
|
||||
var res_src = src;
|
||||
|
||||
// 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 });
|
||||
}
|
||||
}
|
||||
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, .{
|
||||
.origin_uri = self.uri,
|
||||
var request = try self.newHTTPRequest(.GET, &url, .{
|
||||
.origin_uri = &origin_url.uri,
|
||||
.navigation = false,
|
||||
});
|
||||
defer request.deinit();
|
||||
|
||||
var response = try request.sendSync(.{});
|
||||
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) {
|
||||
return FetchError.BadStatusCode;
|
||||
@@ -726,13 +692,13 @@ pub const Page = struct {
|
||||
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;
|
||||
var request = try session.http_client.request(method, uri);
|
||||
var request = try session.http_client.request(method, &url.uri);
|
||||
errdefer request.deinit();
|
||||
|
||||
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) {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
@@ -64,11 +64,7 @@ fn dispatchMouseEvent(cmd: anytype) !void {
|
||||
else => unreachable,
|
||||
},
|
||||
};
|
||||
const click_result = (try page.mouseEvent(cmd.arena, mouse_event)) orelse return;
|
||||
|
||||
switch (click_result) {
|
||||
.navigate => |uri| try clickNavigate(cmd, uri),
|
||||
}
|
||||
try page.mouseEvent(mouse_event);
|
||||
// result already sent
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ const Testing = @This();
|
||||
|
||||
const main = @import("cdp.zig");
|
||||
const parser = @import("netsurf");
|
||||
const URL = @import("../url.zig").URL;
|
||||
const App = @import("../app.zig").App;
|
||||
|
||||
const base = @import("../testing.zig");
|
||||
@@ -85,8 +86,8 @@ const Session = struct {
|
||||
return error.MockBrowserPageAlreadyExists;
|
||||
}
|
||||
self.page = .{
|
||||
.rawuri = "",
|
||||
.session = self,
|
||||
.url = URL.parse("https://lightpanda.io/", null) catch unreachable,
|
||||
.aux_data = try self.arena.dupe(u8, aux_data orelse ""),
|
||||
};
|
||||
return &self.page.?;
|
||||
@@ -104,7 +105,7 @@ const Session = struct {
|
||||
|
||||
const Page = struct {
|
||||
session: *Session,
|
||||
rawuri: []const u8,
|
||||
url: ?URL = null,
|
||||
aux_data: []const u8 = "",
|
||||
doc: ?*parser.Document = null,
|
||||
|
||||
@@ -114,10 +115,7 @@ const Page = struct {
|
||||
}
|
||||
|
||||
const MouseEvent = @import("../browser/browser.zig").Page.MouseEvent;
|
||||
const ClickResult = @import("../browser/browser.zig").Page.ClickResult;
|
||||
pub fn mouseEvent(_: *Page, _: Allocator, _: MouseEvent) !?ClickResult {
|
||||
return null;
|
||||
}
|
||||
pub fn mouseEvent(_: *Page, _: MouseEvent) !void {}
|
||||
};
|
||||
|
||||
const Client = struct {
|
||||
|
||||
@@ -30,60 +30,60 @@ const checkCases = jsruntime.test_utils.checkCases;
|
||||
pub const Location = struct {
|
||||
pub const mem_guarantied = true;
|
||||
|
||||
url: ?*URL = null,
|
||||
url: ?URL = null,
|
||||
|
||||
pub fn deinit(_: *Location, _: std.mem.Allocator) void {}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ const Callback = jsruntime.Callback;
|
||||
const CallbackArg = jsruntime.CallbackArg;
|
||||
const Loop = jsruntime.Loop;
|
||||
|
||||
const URL = @import("../../../url.zig").URL;
|
||||
const EventTarget = @import("../dom/event_target.zig").EventTarget;
|
||||
const Navigator = @import("navigator.zig").Navigator;
|
||||
const History = @import("history.zig").History;
|
||||
@@ -31,8 +32,6 @@ const Location = @import("location.zig").Location;
|
||||
|
||||
const storage = @import("../storage/storage.zig");
|
||||
|
||||
var emptyLocation = Location{};
|
||||
|
||||
// https://dom.spec.whatwg.org/#interface-window-extensions
|
||||
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
|
||||
pub const Window = struct {
|
||||
@@ -46,9 +45,8 @@ pub const Window = struct {
|
||||
document: ?*parser.DocumentHTML = null,
|
||||
target: []const u8,
|
||||
history: History = .{},
|
||||
location: *Location = &emptyLocation,
|
||||
|
||||
storageShelf: ?*storage.Shelf = null,
|
||||
location: Location = .{},
|
||||
storage_shelf: ?*storage.Shelf = null,
|
||||
|
||||
// store a map between internal timeouts ids and pointers to uint.
|
||||
// the maximum number of possible timeouts is fixed.
|
||||
@@ -64,21 +62,20 @@ pub const Window = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn replaceLocation(self: *Window, loc: ?*Location) !void {
|
||||
self.location = loc orelse &emptyLocation;
|
||||
|
||||
pub fn replaceLocation(self: *Window, loc: Location) !void {
|
||||
self.location = loc;
|
||||
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 {
|
||||
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 {
|
||||
self.storageShelf = shelf;
|
||||
self.storage_shelf = shelf;
|
||||
}
|
||||
|
||||
pub fn get_window(self: *Window) *Window {
|
||||
@@ -90,7 +87,7 @@ pub const Window = struct {
|
||||
}
|
||||
|
||||
pub fn get_location(self: *Window) *Location {
|
||||
return self.location;
|
||||
return &self.location;
|
||||
}
|
||||
|
||||
pub fn get_self(self: *Window) *Window {
|
||||
@@ -114,13 +111,13 @@ pub const Window = struct {
|
||||
}
|
||||
|
||||
pub fn get_localStorage(self: *Window) !*storage.Bottle {
|
||||
if (self.storageShelf == null) return parser.DOMError.NotSupported;
|
||||
return &self.storageShelf.?.bucket.local;
|
||||
if (self.storage_shelf == null) return parser.DOMError.NotSupported;
|
||||
return &self.storage_shelf.?.bucket.local;
|
||||
}
|
||||
|
||||
pub fn get_sessionStorage(self: *Window) !*storage.Bottle {
|
||||
if (self.storageShelf == null) return parser.DOMError.NotSupported;
|
||||
return &self.storageShelf.?.bucket.session;
|
||||
if (self.storage_shelf == null) return parser.DOMError.NotSupported;
|
||||
return &self.storage_shelf.?.bucket.session;
|
||||
}
|
||||
|
||||
// TODO handle callback arguments.
|
||||
|
||||
@@ -20,6 +20,7 @@ const builtin = @import("builtin");
|
||||
|
||||
const os = std.os;
|
||||
const posix = std.posix;
|
||||
const Uri = std.Uri;
|
||||
const Thread = std.Thread;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const MemoryPool = std.heap.MemoryPool;
|
||||
@@ -72,7 +73,7 @@ pub const Client = struct {
|
||||
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();
|
||||
|
||||
errdefer {
|
||||
@@ -80,7 +81,7 @@ pub const Client = struct {
|
||||
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,
|
||||
|
||||
// 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
|
||||
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, 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);
|
||||
}
|
||||
|
||||
fn init(client: *Client, state: *State, method: Method, uri: *const Uri) !Request {
|
||||
if (uri.host == null) {
|
||||
return error.UriMissingHost;
|
||||
}
|
||||
@@ -166,7 +159,7 @@ pub const Request = struct {
|
||||
.method = method,
|
||||
.body = null,
|
||||
.headers = .{},
|
||||
.arena = arena,
|
||||
.arena = state.arena.allocator(),
|
||||
._socket = null,
|
||||
._state = state,
|
||||
._client = client,
|
||||
@@ -320,7 +313,9 @@ pub const Request = struct {
|
||||
var buf = try self.arena.alloc(u8, 1024);
|
||||
|
||||
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();
|
||||
|
||||
if (redirect.use_get) {
|
||||
@@ -1753,14 +1748,16 @@ test "HttpClient Reader: fuzz" {
|
||||
test "HttpClient: invalid url" {
|
||||
var client = try testClient();
|
||||
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" {
|
||||
var client = try testClient();
|
||||
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(.{}));
|
||||
}
|
||||
|
||||
@@ -1768,7 +1765,8 @@ test "HttpClient: sync no body" {
|
||||
var client = try testClient();
|
||||
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(.{});
|
||||
|
||||
try testing.expectEqual(null, try res.next());
|
||||
@@ -1783,7 +1781,8 @@ test "HttpClient: sync tls no body" {
|
||||
var client = try testClient();
|
||||
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 });
|
||||
|
||||
try testing.expectEqual(null, try res.next());
|
||||
@@ -1797,7 +1796,8 @@ test "HttpClient: sync with body" {
|
||||
var client = try testClient();
|
||||
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(.{});
|
||||
|
||||
try testing.expectEqual("over 9000!", try res.next());
|
||||
@@ -1820,7 +1820,8 @@ test "HttpClient: sync tls with body" {
|
||||
var client = try testClient();
|
||||
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 });
|
||||
|
||||
while (try res.next()) |data| {
|
||||
@@ -1844,7 +1845,8 @@ test "HttpClient: sync redirect from TLS to Plaintext" {
|
||||
var client = try testClient();
|
||||
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 });
|
||||
|
||||
while (try res.next()) |data| {
|
||||
@@ -1871,7 +1873,8 @@ test "HttpClient: sync redirect plaintext to TLS" {
|
||||
var client = try testClient();
|
||||
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 });
|
||||
|
||||
while (try res.next()) |data| {
|
||||
@@ -1889,7 +1892,8 @@ test "HttpClient: sync GET redirect" {
|
||||
var client = try testClient();
|
||||
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 });
|
||||
|
||||
try testing.expectEqual("over 9000!", try res.next());
|
||||
@@ -1925,7 +1929,8 @@ test "HttpClient: async connect error" {
|
||||
var client = try testClient();
|
||||
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 loop.io.run_for_ns(std.time.ns_per_ms);
|
||||
try reset.timedWait(std.time.ns_per_s);
|
||||
@@ -1938,7 +1943,8 @@ test "HttpClient: async no body" {
|
||||
var handler = try CaptureHandler.init();
|
||||
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 handler.waitUntilDone();
|
||||
|
||||
@@ -1955,7 +1961,8 @@ test "HttpClient: async with body" {
|
||||
var handler = try CaptureHandler.init();
|
||||
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 handler.waitUntilDone();
|
||||
|
||||
@@ -1978,7 +1985,8 @@ test "HttpClient: async redirect" {
|
||||
var handler = try CaptureHandler.init();
|
||||
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, .{});
|
||||
|
||||
// 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();
|
||||
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 handler.waitUntilDone();
|
||||
|
||||
@@ -2027,7 +2036,8 @@ test "HttpClient: async tls with body" {
|
||||
var handler = try CaptureHandler.init();
|
||||
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 handler.waitUntilDone();
|
||||
|
||||
@@ -2051,7 +2061,8 @@ test "HttpClient: async redirect from TLS to Plaintext" {
|
||||
var handler = try CaptureHandler.init();
|
||||
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 handler.waitUntilDone();
|
||||
|
||||
@@ -2074,7 +2085,8 @@ test "HttpClient: async redirect plaintext to TLS" {
|
||||
var handler = try CaptureHandler.init();
|
||||
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 handler.waitUntilDone();
|
||||
|
||||
|
||||
@@ -29,9 +29,7 @@ const browser = @import("browser/browser.zig");
|
||||
const Window = @import("html/window.zig").Window;
|
||||
const xhr = @import("xhr/xhr.zig");
|
||||
const storage = @import("storage/storage.zig");
|
||||
const URL = @import("url/url.zig").URL;
|
||||
const urlquery = @import("url/query.zig");
|
||||
const Location = @import("html/location.zig").Location;
|
||||
const URL = @import("url.zig").URL;
|
||||
|
||||
const documentTestExecFn = @import("dom/document.zig").testExecFn;
|
||||
const HTMLDocumentTestExecFn = @import("html/document.zig").testExecFn;
|
||||
@@ -94,9 +92,7 @@ fn testExecFn(
|
||||
// alias global as self and window
|
||||
var window = Window.create(null, null);
|
||||
|
||||
const url = "https://lightpanda.io/opensource-browser/";
|
||||
var u = try URL.constructor(alloc, url, null);
|
||||
defer u.deinit(alloc);
|
||||
const url = try URL.parse("https://lightpanda.io/opensource-browser/", null);
|
||||
|
||||
var cookie_jar = storage.CookieJar.init(alloc);
|
||||
defer cookie_jar.deinit();
|
||||
@@ -106,15 +102,14 @@ fn testExecFn(
|
||||
defer renderer.positions.deinit(alloc);
|
||||
|
||||
try js_env.setUserContext(.{
|
||||
.uri = try std.Uri.parse(url),
|
||||
.url = &url,
|
||||
.document = doc,
|
||||
.renderer = &renderer,
|
||||
.cookie_jar = &cookie_jar,
|
||||
.http_client = &http_client,
|
||||
});
|
||||
|
||||
var location = Location{ .url = &u };
|
||||
try window.replaceLocation(&location);
|
||||
try window.replaceLocation(.{ .url = try url.toWebApi(alloc) });
|
||||
|
||||
try window.replaceDocument(doc);
|
||||
window.setStorageShelf(&storageShelf);
|
||||
|
||||
@@ -9,7 +9,11 @@ const public_suffix_list = @import("../data/public_suffix_list.zig").lookup;
|
||||
|
||||
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 {
|
||||
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_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();
|
||||
var it = header.iterate("set-cookie");
|
||||
while (it.next()) |set_cookie| {
|
||||
@@ -226,7 +230,7 @@ fn areCookiesEqual(a: *const Cookie, b: *const Cookie) bool {
|
||||
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_host = (origin_uri.host orelse return error.InvalidURI).percent_encoded;
|
||||
|
||||
@@ -284,7 +288,7 @@ pub const Cookie = struct {
|
||||
// Invalid attribute values? Ignore.
|
||||
// Duplicate attributes - use the last valid
|
||||
// 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) {
|
||||
// this check is necessary, `std.mem.minMax` asserts len > 0
|
||||
return error.Empty;
|
||||
@@ -501,28 +505,28 @@ test "Jar: add" {
|
||||
defer jar.deinit();
|
||||
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 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 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 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 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 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 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 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);
|
||||
}
|
||||
|
||||
@@ -531,7 +535,7 @@ test "Jar: forRequest" {
|
||||
fn expect(expected: []const u8, jar: *Jar, target_uri: Uri, opts: LookupOpts) !void {
|
||||
var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||
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);
|
||||
}
|
||||
}.expect;
|
||||
@@ -548,108 +552,108 @@ test "Jar: forRequest" {
|
||||
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, "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, "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/;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, "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, "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, "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/;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, "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
|
||||
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
|
||||
// This should _not_ match xyxlightpanda.io
|
||||
try expectCookies("", &jar, try std.Uri.parse("http://anothersitelightpanda.io/"), .{
|
||||
.origin_uri = test_uri,
|
||||
.origin_uri = &test_uri,
|
||||
});
|
||||
|
||||
// matching path without trailing /
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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,
|
||||
});
|
||||
|
||||
// non-navigational cross domain, secure
|
||||
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,
|
||||
});
|
||||
|
||||
// non-navigational same origin
|
||||
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,
|
||||
});
|
||||
|
||||
// exact domain match + suffix
|
||||
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
|
||||
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
|
||||
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;
|
||||
try expectCookies("global1=1", &jar, test_uri, .{
|
||||
.request_time = now + 100,
|
||||
.origin_uri = test_uri,
|
||||
.origin_uri = &test_uri,
|
||||
});
|
||||
try testing.expectEqual(l - 1, jar.cookies.items.len);
|
||||
|
||||
@@ -664,7 +668,7 @@ test "CookieList: write" {
|
||||
var cookie_list = CookieList{};
|
||||
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();
|
||||
{
|
||||
try cookie_list._cookies.append(testing.allocator, &c1);
|
||||
@@ -672,7 +676,7 @@ test "CookieList: write" {
|
||||
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();
|
||||
{
|
||||
arr.clearRetainingCapacity();
|
||||
@@ -681,7 +685,7 @@ test "CookieList: write" {
|
||||
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();
|
||||
{
|
||||
arr.clearRetainingCapacity();
|
||||
@@ -866,7 +870,7 @@ const ExpectedCookie = struct {
|
||||
|
||||
fn expectCookie(expected: ExpectedCookie, url: []const u8, set_cookie: []const u8) !void {
|
||||
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();
|
||||
|
||||
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 {
|
||||
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();
|
||||
|
||||
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 {
|
||||
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;
|
||||
|
||||
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
|
||||
// parser including the characters we want for the web API.
|
||||
pub const URL = struct {
|
||||
rawuri: []const u8,
|
||||
uri: std.Uri,
|
||||
search_params: URLSearchParams,
|
||||
|
||||
pub const mem_guarantied = true;
|
||||
|
||||
pub fn constructor(alloc: std.mem.Allocator, url: []const u8, base: ?[]const u8) !URL {
|
||||
const raw = try std.mem.concat(alloc, u8, &[_][]const u8{ url, base orelse "" });
|
||||
errdefer alloc.free(raw);
|
||||
pub fn constructor(arena: std.mem.Allocator, url: []const u8, base: ?[]const u8) !URL {
|
||||
const raw = try std.mem.concat(arena, u8, &[_][]const u8{ url, base orelse "" });
|
||||
errdefer arena.free(raw);
|
||||
|
||||
const uri = std.Uri.parse(raw) catch {
|
||||
return error.TypeError;
|
||||
};
|
||||
const uri = std.Uri.parse(raw) catch return error.TypeError;
|
||||
return init(arena, uri);
|
||||
}
|
||||
|
||||
pub fn init(arena: std.mem.Allocator, uri: std.Uri) !URL {
|
||||
return .{
|
||||
.rawuri = raw,
|
||||
.uri = uri,
|
||||
.search_params = try URLSearchParams.constructor(
|
||||
alloc,
|
||||
arena,
|
||||
uriComponentNullStr(uri.query),
|
||||
),
|
||||
};
|
||||
@@ -70,7 +69,6 @@ pub const URL = struct {
|
||||
|
||||
pub fn deinit(self: *URL, alloc: std.mem.Allocator) void {
|
||||
self.search_params.deinit(alloc);
|
||||
alloc.free(self.rawuri);
|
||||
}
|
||||
|
||||
// the caller must free the returned string.
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
const std = @import("std");
|
||||
const parser = @import("netsurf");
|
||||
const URL = @import("url.zig").URL;
|
||||
const storage = @import("storage/storage.zig");
|
||||
const Client = @import("http/client.zig").Client;
|
||||
const Renderer = @import("browser/browser.zig").Renderer;
|
||||
|
||||
pub const UserContext = struct {
|
||||
uri: std.Uri,
|
||||
url: *const URL,
|
||||
http_client: *Client,
|
||||
document: *parser.DocumentHTML,
|
||||
cookie_jar: *storage.CookieJar,
|
||||
|
||||
@@ -31,6 +31,7 @@ const XMLHttpRequestEventTarget = @import("event_target.zig").XMLHttpRequestEven
|
||||
const Mime = @import("../browser/mime.zig").Mime;
|
||||
|
||||
const Loop = jsruntime.Loop;
|
||||
const URL = @import("../url.zig").URL;
|
||||
const http = @import("../http/client.zig");
|
||||
|
||||
const parser = @import("netsurf");
|
||||
@@ -103,8 +104,9 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
method: http.Request.Method,
|
||||
state: State,
|
||||
url: ?[]const u8,
|
||||
uri: std.Uri,
|
||||
url: ?URL = null,
|
||||
origin_url: *const URL,
|
||||
|
||||
// request headers
|
||||
headers: Headers,
|
||||
sync: bool = true,
|
||||
@@ -113,7 +115,6 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
cookie_jar: *CookieJar,
|
||||
// the URI of the page where this request is originating from
|
||||
origin_uri: std.Uri,
|
||||
|
||||
// TODO uncomment this field causes casting issue with
|
||||
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
|
||||
@@ -291,17 +292,15 @@ pub const XMLHttpRequest = struct {
|
||||
.headers = Headers.init(alloc),
|
||||
.response_headers = Headers.init(alloc),
|
||||
.method = undefined,
|
||||
.url = null,
|
||||
.uri = undefined,
|
||||
.state = .unsent,
|
||||
.origin_uri = userctx.uri,
|
||||
.url = null,
|
||||
.origin_url = userctx.url,
|
||||
.client = userctx.http_client,
|
||||
.cookie_jar = userctx.cookie_jar,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reset(self: *XMLHttpRequest, alloc: std.mem.Allocator) void {
|
||||
if (self.url) |v| alloc.free(v);
|
||||
pub fn reset(self: *XMLHttpRequest) void {
|
||||
self.url = null;
|
||||
|
||||
if (self.response_obj) |v| v.deinit();
|
||||
@@ -379,13 +378,9 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
self.method = try validMethod(method);
|
||||
|
||||
self.reset(alloc);
|
||||
self.reset();
|
||||
|
||||
self.url = try alloc.dupe(u8, url);
|
||||
self.uri = std.Uri.parse(self.url.?) catch |err| {
|
||||
log.debug("parse url ({s}): {any}", .{ self.url.?, err });
|
||||
return DOMError.Syntax;
|
||||
};
|
||||
self.url = try self.origin_url.resolve(alloc, url);
|
||||
log.debug("open url ({s})", .{self.url.?});
|
||||
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.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.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.?;
|
||||
errdefer request.deinit();
|
||||
|
||||
@@ -490,9 +485,9 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
{
|
||||
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,
|
||||
.origin_uri = self.origin_uri,
|
||||
.origin_uri = &self.origin_url.uri,
|
||||
});
|
||||
|
||||
if (arr.items.len > 0) {
|
||||
@@ -522,7 +517,7 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
if (progress.first) {
|
||||
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;
|
||||
|
||||
@@ -546,7 +541,7 @@ pub const XMLHttpRequest = struct {
|
||||
self.state = .loading;
|
||||
self.dispatchEvt("readystatechange");
|
||||
|
||||
try self.cookie_jar.populateFromResponse(self.uri, &header);
|
||||
try self.cookie_jar.populateFromResponse(self.request.?.uri, &header);
|
||||
}
|
||||
|
||||
if (progress.data) |data| {
|
||||
@@ -588,7 +583,7 @@ pub const XMLHttpRequest = struct {
|
||||
self.dispatchProgressEvent("error", .{});
|
||||
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 {
|
||||
@@ -637,7 +632,8 @@ pub const XMLHttpRequest = struct {
|
||||
|
||||
// TODO retrieve the redirected url
|
||||
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 {
|
||||
|
||||
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