diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 36710adc..06b24508 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -197,6 +197,7 @@ pub const Accessor = struct { cache: ?[]const u8 = null, as_typed_array: bool = false, null_as_undefined: bool = false, + dom_exception: bool = false, }; fn init(comptime T: type, comptime getter: anytype, comptime setter: anytype, comptime opts: Opts) Accessor { @@ -215,12 +216,14 @@ pub const Accessor = struct { if (comptime opts.static) { caller.function(T, getter, handle.?, .{ .cache = opts.cache, + .dom_exception = opts.dom_exception, .as_typed_array = opts.as_typed_array, .null_as_undefined = opts.null_as_undefined, }); } else { caller.method(T, getter, handle.?, .{ .cache = opts.cache, + .dom_exception = opts.dom_exception, .as_typed_array = opts.as_typed_array, .null_as_undefined = opts.null_as_undefined, }); diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index 7c266e1a..d670cbc8 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -56,6 +56,7 @@ _response_type: ResponseType = .text, _ready_state: ReadyState = .unsent, _on_ready_state_change: ?js.Function.Temp = null, +_with_credentials: bool = false, const ReadyState = enum(u8) { unsent = 0, @@ -150,6 +151,17 @@ pub fn setOnReadyStateChange(self: *XMLHttpRequest, cb_: ?js.Function) !void { } } +pub fn getWithCredentials(self: *const XMLHttpRequest) bool { + return self._with_credentials; +} + +pub fn setWithCredentials(self: *XMLHttpRequest, value: bool) !void { + if (self._ready_state != .unsent and self._ready_state != .opened) { + return error.InvalidStateError; + } + self._with_credentials = value; +} + // TODO: this takes an optional 3 more parameters // TODO: url should be a union, as it can be multiple things pub fn open(self: *XMLHttpRequest, method_: []const u8, url: [:0]const u8) !void { @@ -198,8 +210,14 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void { const page = self._page; const http_client = page._session.browser.http_client; var headers = try http_client.newHeaders(); + + // Only add cookies for same-origin or when withCredentials is true + const cookie_support = self._with_credentials or try page.isSameOrigin(self._url); + try self._request_headers.populateHttpHeader(page.call_arena, &headers); - try page.headersForRequest(self._arena, self._url, &headers); + if (cookie_support) { + try page.headersForRequest(self._arena, self._url, &headers); + } try http_client.request(.{ .ctx = self, @@ -207,7 +225,7 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void { .method = self._method, .headers = headers, .body = self._request_body, - .cookie_jar = &page._session.cookie_jar, + .cookie_jar = if (cookie_support) &page._session.cookie_jar else null, .resource_type = .xhr, .notification = page._session.notification, .start_callback = httpStartCallback, @@ -541,6 +559,7 @@ pub const JsApi = struct { pub const DONE = bridge.property(@intFromEnum(XMLHttpRequest.ReadyState.done)); pub const onreadystatechange = bridge.accessor(XMLHttpRequest.getOnReadyStateChange, XMLHttpRequest.setOnReadyStateChange, .{}); + pub const withCredentials = bridge.accessor(XMLHttpRequest.getWithCredentials, XMLHttpRequest.setWithCredentials, .{ .dom_exception = true }); pub const open = bridge.function(XMLHttpRequest.open, .{}); pub const send = bridge.function(XMLHttpRequest.send, .{ .dom_exception = true }); pub const responseType = bridge.accessor(XMLHttpRequest.getResponseType, XMLHttpRequest.setResponseType, .{}); diff --git a/src/http/Client.zig b/src/http/Client.zig index 8fe5869e..22b280c1 100644 --- a/src/http/Client.zig +++ b/src/http/Client.zig @@ -783,7 +783,7 @@ pub const Request = struct { url: [:0]const u8, headers: Http.Headers, body: ?[]const u8 = null, - cookie_jar: *CookieJar, + cookie_jar: ?*CookieJar, resource_type: ResourceType, credentials: ?[:0]const u8 = null, notification: *Notification, @@ -1057,13 +1057,15 @@ pub const Transfer = struct { const arena = transfer.arena.allocator(); // retrieve cookies from the redirect's response. - var i: usize = 0; - while (true) { - const ct = getResponseHeader(easy, "set-cookie", i); - if (ct == null) break; - try req.cookie_jar.populateFromResponse(transfer.url, ct.?.value); - i += 1; - if (i >= ct.?.amount) break; + if (req.cookie_jar) |jar| { + var i: usize = 0; + while (true) { + const ct = getResponseHeader(easy, "set-cookie", i); + if (ct == null) break; + try jar.populateFromResponse(transfer.url, ct.?.value); + i += 1; + if (i >= ct.?.amount) break; + } } // set cookies for the following redirection's request. @@ -1077,15 +1079,17 @@ pub const Transfer = struct { const url = try URL.resolve(arena, std.mem.span(base_url), location.value, .{}); transfer.url = url; - var cookies: std.ArrayList(u8) = .{}; - try req.cookie_jar.forRequest(url, cookies.writer(arena), .{ - .is_http = true, - .origin_url = url, - // used to enforce samesite cookie rules - .is_navigation = req.resource_type == .document, - }); - try cookies.append(arena, 0); //null terminate - try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_COOKIE, @as([*c]const u8, @ptrCast(cookies.items.ptr)))); + if (req.cookie_jar) |jar| { + var cookies: std.ArrayList(u8) = .{}; + try jar.forRequest(url, cookies.writer(arena), .{ + .is_http = true, + .origin_url = url, + // used to enforce samesite cookie rules + .is_navigation = req.resource_type == .document, + }); + try cookies.append(arena, 0); //null terminate + try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_COOKIE, @as([*c]const u8, @ptrCast(cookies.items.ptr)))); + } } // headerDoneCallback is called once the headers have been read. @@ -1107,16 +1111,18 @@ pub const Transfer = struct { @memcpy(hdr._content_type[0..len], value[0..len]); } - var i: usize = 0; - while (true) { - const ct = getResponseHeader(easy, "set-cookie", i); - if (ct == null) break; - transfer.req.cookie_jar.populateFromResponse(transfer.url, ct.?.value) catch |err| { - log.err(.http, "set cookie", .{ .err = err, .req = transfer }); - return err; - }; - i += 1; - if (i >= ct.?.amount) break; + if (transfer.req.cookie_jar) |jar| { + var i: usize = 0; + while (true) { + const ct = getResponseHeader(easy, "set-cookie", i); + if (ct == null) break; + jar.populateFromResponse(transfer.url, ct.?.value) catch |err| { + log.err(.http, "set cookie", .{ .err = err, .req = transfer }); + return err; + }; + i += 1; + if (i >= ct.?.amount) break; + } } const proceed = transfer.req.header_callback(transfer) catch |err| {