Merge pull request #1491 from lightpanda-io/xhr_with_credentials

Add support for XHR's withCredentials
This commit is contained in:
Pierre Tachoire
2026-02-08 10:38:02 +01:00
committed by GitHub
3 changed files with 57 additions and 29 deletions

View File

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

View File

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

View File

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