diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 743aaf6c..718a360c 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -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, .{}); diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 45d4c63c..2e609075 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -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 { diff --git a/src/cdp/domains/input.zig b/src/cdp/domains/input.zig index 196ed192..e4f79ce6 100644 --- a/src/cdp/domains/input.zig +++ b/src/cdp/domains/input.zig @@ -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 } diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig index c9ce2ebb..1b51b96b 100644 --- a/src/cdp/testing.zig +++ b/src/cdp/testing.zig @@ -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 { diff --git a/src/html/location.zig b/src/html/location.zig index 70e69025..95a62dae 100644 --- a/src/html/location.zig +++ b/src/html/location.zig @@ -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 ""; } diff --git a/src/html/window.zig b/src/html/window.zig index cef16e94..36b580ba 100644 --- a/src/html/window.zig +++ b/src/html/window.zig @@ -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. diff --git a/src/http/client.zig b/src/http/client.zig index 1fe5be21..b6a4c4f4 100644 --- a/src/http/client.zig +++ b/src/http/client.zig @@ -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(); diff --git a/src/main_tests.zig b/src/main_tests.zig index 85b584c2..035e9d8f 100644 --- a/src/main_tests.zig +++ b/src/main_tests.zig @@ -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); diff --git a/src/storage/cookie.zig b/src/storage/cookie.zig index 689a9f77..63c56517 100644 --- a/src/storage/cookie.zig +++ b/src/storage/cookie.zig @@ -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; diff --git a/src/url.zig b/src/url.zig new file mode 100644 index 00000000..b3b7f821 --- /dev/null +++ b/src/url.zig @@ -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); + } +}; diff --git a/src/url/url.zig b/src/url/url.zig index 683186c2..2e28210d 100644 --- a/src/url/url.zig +++ b/src/url/url.zig @@ -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. diff --git a/src/user_context.zig b/src/user_context.zig index d9708932..21505cae 100644 --- a/src/user_context.zig +++ b/src/user_context.zig @@ -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, diff --git a/src/xhr/xhr.zig b/src/xhr/xhr.zig index 6b3ed67a..9c4e82b3 100644 --- a/src/xhr/xhr.zig +++ b/src/xhr/xhr.zig @@ -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 { diff --git a/vendor/zig-js-runtime b/vendor/zig-js-runtime index f3a9e3d4..9b87782f 160000 --- a/vendor/zig-js-runtime +++ b/vendor/zig-js-runtime @@ -1 +1 @@ -Subproject commit f3a9e3d448cd56a709ec2d101c05507e894d8e6f +Subproject commit 9b87782f1edc0a3c4541f771d4ff443820fa38ac