From 0e2a3d8009b4dc664acb01798d4598fe924a05fe Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 14 Aug 2025 17:30:45 +0200 Subject: [PATCH 1/4] handle cookies on redirection manually --- src/http/Client.zig | 62 +++++++++++++++++++++++++++++++++++---------- src/http/Http.zig | 5 ---- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/http/Client.zig b/src/http/Client.zig index d228cbec..2915cb89 100644 --- a/src/http/Client.zig +++ b/src/http/Client.zig @@ -24,6 +24,8 @@ pub const Headers = Http.Headers; const Notification = @import("../notification.zig").Notification; const storage = @import("../browser/storage/storage.zig"); +const urlStitch = @import("../url.zig").URL.stitch; + const c = Http.c; const Allocator = std.mem.Allocator; @@ -321,8 +323,6 @@ fn makeRequest(self: *Client, handle: *Handle, transfer: *Transfer) !void { try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_HTTPHEADER, header_list.headers)); // Add cookies. - // Clear cookies from Curl's engine. - try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_COOKIELIST, "ALL")); if (header_list.cookies) |cookies| { try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_COOKIE, cookies)); } @@ -595,6 +595,42 @@ pub const Transfer = struct { self.deinit(); } + // redirectionCookies manages cookies during redirections handled by Curl. + // It sets the cookies from the current response to the cookie jar. + // It also immediately sets cookies for the following request. + fn redirectionCookies(arena: std.mem.Allocator, easy: *c.CURL, cookie_jar: *storage.CookieJar, origin: *const std.Uri) !void { + // 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 cookie_jar.populateFromResponse(origin, ct.?.value); + i += 1; + if (i >= ct.?.amount) break; + } + + // set cookies for the following redirection's request. + const hlocation = getResponseHeader(easy, "location", 0); + if (hlocation == null) { + return error.LocationNotFound; + } + + var baseurl: [*c]u8 = undefined; + try errorCheck(c.curl_easy_getinfo(easy, c.CURLINFO_EFFECTIVE_URL, &baseurl)); + + const url = try urlStitch(arena, hlocation.?.value, std.mem.span(baseurl), .{}); + const uri = try std.Uri.parse(url); + + var cookies: std.ArrayListUnmanaged(u8) = .{}; + try cookie_jar.forRequest(&uri, cookies.writer(arena), .{ + .is_http = true, + .is_navigation = true, + .origin_uri = origin, + }); + 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)))); + } + fn headerCallback(buffer: [*]const u8, header_count: usize, buf_len: usize, data: *anyopaque) callconv(.c) usize { // libcurl should only ever emit 1 header at a time std.debug.assert(header_count == 1); @@ -611,18 +647,16 @@ pub const Transfer = struct { if (transfer.response_header == null) { if (transfer._redirecting and buf_len == 2) { - // retrieve cookies from the redirect's response. - var i: usize = 0; - while (true) { - const ct = getResponseHeader(easy, "set-cookie", i); - if (ct == null) break; - transfer.req.cookie_jar.populateFromResponse(&transfer.uri, ct.?.value) catch |err| { - log.err(.http, "set cookie", .{ .err = err, .req = transfer }); - }; - i += 1; - if (i >= ct.?.amount) break; - } - + // parse and set cookies for the redirection. + redirectionCookies( + transfer.client.arena.allocator(), + easy, + transfer.req.cookie_jar, + &transfer.uri, + ) catch |err| { + log.debug(.http, "redirection cookies", .{ .err = err }); + return 0; + }; return buf_len; } diff --git a/src/http/Http.zig b/src/http/Http.zig index 1d8cde58..162d7fea 100644 --- a/src/http/Http.zig +++ b/src/http/Http.zig @@ -110,9 +110,6 @@ pub const Connection = struct { try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_FOLLOWLOCATION, @as(c_long, 2))); try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_REDIR_PROTOCOLS_STR, "HTTP,HTTPS")); // remove FTP and FTPS from the default - // enable cookie engine for redirections handled directly by Curl. - try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_COOKIEFILE, "")); - // proxy if (opts.http_proxy) |proxy| { try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY, proxy.ptr)); @@ -205,8 +202,6 @@ pub const Connection = struct { try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_HTTPHEADER, header_list.headers)); // Add cookies. - // Clear cookies from Curl's engine. - try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_COOKIELIST, "ALL")); if (header_list.cookies) |cookies| { try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_COOKIE, cookies)); } From 7795916c08c245c737eb20da942053b750bff1fc Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 19 Aug 2025 10:01:35 +0200 Subject: [PATCH 2/4] apply review comments --- src/http/Client.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/http/Client.zig b/src/http/Client.zig index 2915cb89..9d314a93 100644 --- a/src/http/Client.zig +++ b/src/http/Client.zig @@ -24,7 +24,7 @@ pub const Headers = Http.Headers; const Notification = @import("../notification.zig").Notification; const storage = @import("../browser/storage/storage.zig"); -const urlStitch = @import("../url.zig").URL.stitch; +const urlStitch = @import("../url.zig").stitch; const c = Http.c; @@ -598,7 +598,7 @@ pub const Transfer = struct { // redirectionCookies manages cookies during redirections handled by Curl. // It sets the cookies from the current response to the cookie jar. // It also immediately sets cookies for the following request. - fn redirectionCookies(arena: std.mem.Allocator, easy: *c.CURL, cookie_jar: *storage.CookieJar, origin: *const std.Uri) !void { + fn redirectionCookies(arena: Allocator, easy: *c.CURL, cookie_jar: *storage.CookieJar, origin: *const std.Uri) !void { // retrieve cookies from the redirect's response. var i: usize = 0; while (true) { From 39178d8d2b4a1cd9841eb468922eda9739615f4c Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 19 Aug 2025 11:10:25 +0200 Subject: [PATCH 3/4] http: remove uselesss Client.arena --- src/http/Client.zig | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/http/Client.zig b/src/http/Client.zig index 9d314a93..7c66bd2b 100644 --- a/src/http/Client.zig +++ b/src/http/Client.zig @@ -85,12 +85,6 @@ blocking: Handle, // To notify registered subscribers of events, the browser sets/nulls this for us. notification: ?*Notification = null, -// The only place this is meant to be used is in `makeRequest` BEFORE `perform` -// is called. It is used to generate our Cookie header. It can be used for other -// purposes, but keep in mind that, while single-threaded, calls like makeRequest -// can result in makeRequest being re-called (from a doneCallback). -arena: ArenaAllocator, - // only needed for CDP which can change the proxy and then restore it. When // restoring, this originally-configured value is what it goes to. http_proxy: ?[:0]const u8 = null, @@ -128,7 +122,6 @@ pub fn init(allocator: Allocator, ca_blob: ?c.curl_blob, opts: Http.Opts) !*Clie .http_proxy = opts.http_proxy, .transfer_pool = transfer_pool, .queue_node_pool = queue_node_pool, - .arena = ArenaAllocator.init(allocator), }; return client; @@ -143,7 +136,6 @@ pub fn deinit(self: *Client) void { self.transfer_pool.deinit(); self.queue_node_pool.deinit(); - self.arena.deinit(); self.allocator.destroy(self); } From f7eee0d461b8983626e530ce20af4167ddd208b9 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 19 Aug 2025 11:10:52 +0200 Subject: [PATCH 4/4] http: add an arena to Transfer --- src/http/Client.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/http/Client.zig b/src/http/Client.zig index 7c66bd2b..deebd84d 100644 --- a/src/http/Client.zig +++ b/src/http/Client.zig @@ -236,6 +236,7 @@ fn makeTransfer(self: *Client, req: Request) !*Transfer { const id = self.next_request_id + 1; self.next_request_id = id; transfer.* = .{ + .arena = ArenaAllocator.init(self.allocator), .id = id, .uri = uri, .req = req, @@ -534,6 +535,7 @@ pub const Request = struct { }; pub const Transfer = struct { + arena: ArenaAllocator, id: usize = 0, req: Request, uri: std.Uri, // used for setting/getting the cookie @@ -553,6 +555,7 @@ pub const Transfer = struct { if (self._handle) |handle| { self.client.handles.release(handle); } + self.arena.deinit(); self.client.transfer_pool.destroy(self); } @@ -641,7 +644,7 @@ pub const Transfer = struct { if (transfer._redirecting and buf_len == 2) { // parse and set cookies for the redirection. redirectionCookies( - transfer.client.arena.allocator(), + transfer.arena.allocator(), easy, transfer.req.cookie_jar, &transfer.uri,