From a272a2c3148a99787a179703c957ac8b3c692c7e Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 30 Mar 2026 15:08:36 +0200 Subject: [PATCH 1/3] http: add connect code into auth challenge detection --- src/browser/HttpClient.zig | 4 +++- src/network/http.zig | 9 +++++++++ src/sys/libcurl.zig | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/browser/HttpClient.zig b/src/browser/HttpClient.zig index 393b7de1..3fb5ac5a 100644 --- a/src/browser/HttpClient.zig +++ b/src/browser/HttpClient.zig @@ -1261,7 +1261,9 @@ pub const Transfer = struct { fn detectAuthChallenge(transfer: *Transfer, conn: *const http.Connection) void { const status = conn.getResponseCode() catch return; - if (status != 401 and status != 407) { + const connect_status = conn.getConnectCode() catch return; + + if (status != 401 and status != 407 and connect_status != 401 and connect_status != 407) { transfer._auth_challenge = null; return; } diff --git a/src/network/http.zig b/src/network/http.zig index 2c8e7d5a..dac76749 100644 --- a/src/network/http.zig +++ b/src/network/http.zig @@ -389,6 +389,15 @@ pub const Connection = struct { return url; } + pub fn getConnectCode(self: *const Connection) !u16 { + var status: c_long = undefined; + try libcurl.curl_easy_getinfo(self._easy, .connect_code, &status); + if (status < 0 or status > std.math.maxInt(u16)) { + return 0; + } + return @intCast(status); + } + pub fn getResponseCode(self: *const Connection) !u16 { var status: c_long = undefined; try libcurl.curl_easy_getinfo(self._easy, .response_code, &status); diff --git a/src/sys/libcurl.zig b/src/sys/libcurl.zig index b37e5142..0e2defe3 100644 --- a/src/sys/libcurl.zig +++ b/src/sys/libcurl.zig @@ -178,6 +178,7 @@ pub const CurlInfo = enum(c.CURLINFO) { private = c.CURLINFO_PRIVATE, redirect_count = c.CURLINFO_REDIRECT_COUNT, response_code = c.CURLINFO_RESPONSE_CODE, + connect_code = c.CURLINFO_HTTP_CONNECTCODE, }; pub const Error = error{ @@ -662,6 +663,7 @@ pub fn curl_easy_getinfo(easy: *Curl, comptime info: CurlInfo, out: anytype) Err break :blk c.curl_easy_getinfo(easy, inf, p); }, .response_code, + .connect_code, .redirect_count, => blk: { const p: *c_long = out; From 9ca6bf42aee5fbe8b0c8e378e872b02b3e477ca1 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 30 Mar 2026 15:17:12 +0200 Subject: [PATCH 2/3] http: add connect headers to auth challenge detection --- src/browser/HttpClient.zig | 4 ++++ src/network/http.zig | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/browser/HttpClient.zig b/src/browser/HttpClient.zig index 3fb5ac5a..c49d7e1f 100644 --- a/src/browser/HttpClient.zig +++ b/src/browser/HttpClient.zig @@ -1270,8 +1270,12 @@ pub const Transfer = struct { if (conn.getResponseHeader("WWW-Authenticate", 0)) |hdr| { transfer._auth_challenge = http.AuthChallenge.parse(status, .server, hdr.value) catch null; + } else if (conn.getConnectHeader("WWW-Authenticate", 0)) |hdr| { + transfer._auth_challenge = http.AuthChallenge.parse(status, .server, hdr.value) catch null; } else if (conn.getResponseHeader("Proxy-Authenticate", 0)) |hdr| { transfer._auth_challenge = http.AuthChallenge.parse(status, .proxy, hdr.value) catch null; + } else if (conn.getConnectHeader("Proxy-Authenticate", 0)) |hdr| { + transfer._auth_challenge = http.AuthChallenge.parse(status, .proxy, hdr.value) catch null; } else { transfer._auth_challenge = .{ .status = status, .source = null, .scheme = null, .realm = null }; } diff --git a/src/network/http.zig b/src/network/http.zig index dac76749..2bfabac0 100644 --- a/src/network/http.zig +++ b/src/network/http.zig @@ -413,6 +413,24 @@ pub const Connection = struct { return @intCast(count); } + pub fn getConnectHeader(self: *const Connection, name: [:0]const u8, index: usize) ?HeaderValue { + var hdr: ?*libcurl.CurlHeader = null; + libcurl.curl_easy_header(self._easy, name, index, .connect, -1, &hdr) catch |err| { + // ErrorHeader includes OutOfMemory — rare but real errors from curl internals. + // Logged and returned as null since callers don't expect errors. + log.err(.http, "get response header", .{ + .name = name, + .err = err, + }); + return null; + }; + const h = hdr orelse return null; + return .{ + .amount = h.amount, + .value = std.mem.span(h.value), + }; + } + pub fn getResponseHeader(self: *const Connection, name: [:0]const u8, index: usize) ?HeaderValue { var hdr: ?*libcurl.CurlHeader = null; libcurl.curl_easy_header(self._easy, name, index, .header, -1, &hdr) catch |err| { From 7606528b377c03bc7fd0f51c8f9e2ec1ee28d912 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 30 Mar 2026 15:18:22 +0200 Subject: [PATCH 3/3] ci: fix request interception proxy challenge --- .github/workflows/e2e-test.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 07f55cfe..a47829bc 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -100,14 +100,14 @@ jobs: ./proxy/proxy & echo $! > PROXY.id ./lightpanda serve --http-proxy 'http://127.0.0.1:3000' & echo $! > LPD.pid go run runner/main.go + URL=https://demo-browser.lightpanda.io/campfire-commerce/ node puppeteer/proxy_auth.js kill `cat LPD.pid` `cat PROXY.id` - - name: run request interception through proxy + - name: run request interception through proxy and playwright run: | export PROXY_USERNAME=username PROXY_PASSWORD=password ./proxy/proxy & echo $! > PROXY.id ./lightpanda serve & echo $! > LPD.pid - URL=https://demo-browser.lightpanda.io/campfire-commerce/ node puppeteer/proxy_auth.js BASE_URL=https://demo-browser.lightpanda.io/ node playwright/proxy_auth.js kill `cat LPD.pid` `cat PROXY.id` @@ -161,14 +161,18 @@ jobs: --http-proxy 'http://127.0.0.1:3000' \ & echo $! > LPD.pid go run runner/main.go + URL=https://demo-browser.lightpanda.io/campfire-commerce/ node puppeteer/proxy_auth.js kill `cat LPD.pid` `cat PROXY.id` - - name: run request interception through proxy + - name: run request interception through proxy and playwright run: | export PROXY_USERNAME=username PROXY_PASSWORD=password ./proxy/proxy & echo $! > PROXY.id - ./lightpanda serve & echo $! > LPD.pid - URL=https://demo-browser.lightpanda.io/campfire-commerce/ node puppeteer/proxy_auth.js + ./lightpanda serve \ + --web-bot-auth-key-file private_key.pem \ + --web-bot-auth-keyid ${{ vars.WBA_KEY_ID }} \ + --web-bot-auth-domain ${{ vars.WBA_DOMAIN }} \ + & echo $! > LPD.pid BASE_URL=https://demo-browser.lightpanda.io/ node playwright/proxy_auth.js kill `cat LPD.pid` `cat PROXY.id`