Enable blocking auth request interception

This commit is contained in:
Karl Seguin
2025-12-24 12:19:11 +08:00
parent 67875036c5
commit df4e5d859f
3 changed files with 44 additions and 33 deletions

View File

@@ -89,7 +89,7 @@ pub fn init(page: *Page) !*XMLHttpRequest {
pub fn deinit(self: *XMLHttpRequest) void { pub fn deinit(self: *XMLHttpRequest) void {
if (self.transfer) |transfer| { if (self.transfer) |transfer| {
transfer.abort(); transfer.abort(error.Abort);
self.transfer = null; self.transfer = null;
} }
} }
@@ -115,7 +115,7 @@ pub fn setOnReadyStateChange(self: *XMLHttpRequest, cb_: ?js.Function) !void {
pub fn open(self: *XMLHttpRequest, method_: []const u8, url: [:0]const u8) !void { pub fn open(self: *XMLHttpRequest, method_: []const u8, url: [:0]const u8) !void {
// Abort any in-progress request // Abort any in-progress request
if (self._transfer) |transfer| { if (self._transfer) |transfer| {
transfer.abort(); transfer.abort(error.Abort);
self._transfer = null; self._transfer = null;
} }
@@ -373,7 +373,7 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
pub fn abort(self: *XMLHttpRequest) void { pub fn abort(self: *XMLHttpRequest) void {
self.handleError(error.Abort); self.handleError(error.Abort);
if (self._transfer) |transfer| { if (self._transfer) |transfer| {
transfer.abort(); transfer.abort(error.Abort);
self._transfer = null; self._transfer = null;
} }
} }

View File

@@ -408,7 +408,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
// abort all intercepted requests before closing the sesion/page // abort all intercepted requests before closing the sesion/page
// since some of these might callback into the page/scriptmanager // since some of these might callback into the page/scriptmanager
for (self.intercept_state.pendingTransfers()) |transfer| { for (self.intercept_state.pendingTransfers()) |transfer| {
transfer.abort(); transfer.abort(error.ClientDisconnect);
} }
// If the session has a page, we need to clear it first. The page // If the session has a page, we need to clear it first. The page

View File

@@ -241,16 +241,22 @@ pub fn request(self: *Client, req: Request) !void {
return; return;
} }
if (try self.waitForInterceptedResponse(transfer)) {
return self.process(transfer);
}
}
fn waitForInterceptedResponse(self: *Client, transfer: *Transfer) !bool {
// The request was intercepted and is blocking. This is messy, but our // The request was intercepted and is blocking. This is messy, but our
// callers, the ScriptManager -> Page, don't have a great way to stop the // callers, the ScriptManager -> Page, don't have a great way to stop the
// parser and return control to the CDP server to wait for the interception // parser and return control to the CDP server to wait for the interception
// response. We have some information on the CDPClient, so we'll do the // response. We have some information on the CDPClient, so we'll do the
// blocking here. (This is a bit of a legacy thing. Initially the Client) // blocking here. (This is a bit of a legacy thing. Initially the Client
// had a 'extra_socket' that it could monitor. It was named 'extra_socket' // had a 'extra_socket' that it could monitor. It was named 'extra_socket'
// to appear generic, but really, that 'extra_socket' was always the CDP // to appear generic, but really, that 'extra_socket' was always the CDP
// socket that we could monitor in libcurm. Because we already had // socket. Because we already had the "extra_socket" here, it was easier to
// the "extra_socket" here, it was easier just to make it even more CDP- // make it even more CDP- aware and turn `extra_socket: socket_t` into the
// aware and turn `extra_socket: socket_t` into the current CDPClient). // current CDPClient and do the blocking here).
const cdp_client = self.cdp_client.?; const cdp_client = self.cdp_client.?;
const ctx = cdp_client.ctx; const ctx = cdp_client.ctx;
@@ -258,6 +264,8 @@ pub fn request(self: *Client, req: Request) !void {
return error.BlockingInterceptFailure; return error.BlockingInterceptFailure;
} }
defer _ = cdp_client.blocking_read_end(ctx);
while (true) { while (true) {
if (cdp_client.blocking_read(ctx) == false) { if (cdp_client.blocking_read(ctx) == false) {
return error.BlockingInterceptFailure; return error.BlockingInterceptFailure;
@@ -265,23 +273,19 @@ pub fn request(self: *Client, req: Request) !void {
switch (transfer._intercept_state) { switch (transfer._intercept_state) {
.pending => continue, // keep waiting .pending => continue, // keep waiting
.@"continue" => return self.process(transfer), .@"continue" => return true,
.abort => { .abort => |err| {
transfer.abort(); transfer.abort(err);
return; return false;
}, },
.fulfilled => { .fulfilled => {
// callbacks already called, just need to cleanups // callbacks already called, just need to cleanups
transfer.deinit(); transfer.deinit();
return; return false;
}, },
.not_intercepted => unreachable, .not_intercepted => unreachable,
} }
} }
if (cdp_client.blocking_read_end(ctx) == false) {
return error.BlockingInterceptFailure;
}
} }
// Above, request will not process if there's an interception request. In such // Above, request will not process if there's an interception request. In such
@@ -303,10 +307,10 @@ pub fn continueTransfer(self: *Client, transfer: *Transfer) !void {
} }
self.intercepted -= 1; self.intercepted -= 1;
transfer._intercept_state = .@"continue";
if (!transfer.req.blocking) { if (!transfer.req.blocking) {
return self.process(transfer); return self.process(transfer);
} }
transfer._intercept_state = .@"continue";
} }
// For an intercepted request // For an intercepted request
@@ -317,10 +321,10 @@ pub fn abortTransfer(self: *Client, transfer: *Transfer) void {
} }
self.intercepted -= 1; self.intercepted -= 1;
transfer._intercept_state = .abort;
if (!transfer.req.blocking) { if (!transfer.req.blocking) {
transfer.abort(); transfer.abort(error.Abort);
} }
transfer._intercept_state = .{ .abort = error.Abort };
} }
// For an intercepted request // For an intercepted request
@@ -331,11 +335,11 @@ pub fn fulfillTransfer(self: *Client, transfer: *Transfer, status: u16, headers:
} }
self.intercepted -= 1; self.intercepted -= 1;
transfer._intercept_state = .fulfilled;
try transfer.fulfill(status, headers, body); try transfer.fulfill(status, headers, body);
if (!transfer.req.blocking) { if (!transfer.req.blocking) {
transfer.deinit(); transfer.deinit();
} }
transfer._intercept_state = .fulfilled;
} }
pub fn nextReqId(self: *Client) usize { pub fn nextReqId(self: *Client) usize {
@@ -560,17 +564,21 @@ fn processMessages(self: *Client) !bool {
var wait_for_interception = false; var wait_for_interception = false;
notification.dispatch(.http_request_auth_required, &.{ .transfer = transfer, .wait_for_interception = &wait_for_interception }); notification.dispatch(.http_request_auth_required, &.{ .transfer = transfer, .wait_for_interception = &wait_for_interception });
if (wait_for_interception) { if (wait_for_interception) {
// the request is put on hold to be intercepted.
// In this case we ignore callbacks for now.
// Note: we don't deinit transfer on purpose: we want to keep
// using it for the following request.
self.intercepted += 1; self.intercepted += 1;
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
log.debug(.http, "wait for auth interception", .{ .intercepted = self.intercepted }); log.debug(.http, "wait for auth interception", .{ .intercepted = self.intercepted });
} }
transfer._intercept_state = .pending; transfer._intercept_state = .pending;
self.endTransfer(transfer); if (!transfer.req.blocking) {
continue; // the request is put on hold to be intercepted.
// In this case we ignore callbacks for now.
// Note: we don't deinit transfer on purpose: we want to keep
// using it for the following request
self.endTransfer(transfer);
continue;
}
_ = try self.waitForInterceptedResponse(transfer);
} }
} }
} }
@@ -856,11 +864,11 @@ pub const Transfer = struct {
_node: std.DoublyLinkedList.Node = .{}, _node: std.DoublyLinkedList.Node = .{},
_intercept_state: InterceptState = .not_intercepted, _intercept_state: InterceptState = .not_intercepted,
const InterceptState = enum { const InterceptState = union(enum) {
not_intercepted, not_intercepted,
pending, pending,
@"continue", @"continue",
abort, abort: anyerror,
fulfilled, fulfilled,
}; };
@@ -952,8 +960,8 @@ pub const Transfer = struct {
self.req.headers = new_headers; self.req.headers = new_headers;
} }
pub fn abort(self: *Transfer) void { pub fn abort(self: *Transfer, err: anyerror) void {
self.client.requestFailed(self, error.Abort); self.client.requestFailed(self, err);
if (self._handle != null) { if (self._handle != null) {
self.client.endTransfer(self); self.client.endTransfer(self);
} }
@@ -978,8 +986,11 @@ pub const Transfer = struct {
log.debug(.http, "abort auth transfer", .{ .intercepted = self.client.intercepted }); log.debug(.http, "abort auth transfer", .{ .intercepted = self.client.intercepted });
} }
self.client.intercepted -= 1; self.client.intercepted -= 1;
self.client.requestFailed(self, error.AbortAuthChallenge); if (!self.req.blocking) {
self.deinit(); self.abort(error.AbortAuthChallenge);
return;
}
self._intercept_state = .{ .abort = error.AbortAuthChallenge };
} }
// redirectionCookies manages cookies during redirections handled by Curl. // redirectionCookies manages cookies during redirections handled by Curl.