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:
Karl Seguin
2025-04-09 18:14:53 +08:00
parent 8b296534a4
commit be75b5b237
14 changed files with 279 additions and 241 deletions

View File

@@ -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, .{});

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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 "";
}

View File

@@ -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.

View File

@@ -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();

View File

@@ -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);

View File

@@ -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
View 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);
}
};

View File

@@ -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.

View File

@@ -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,

View File

@@ -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 {