Skip to content

Commit 6304cf2

Browse files
committed
wip
1 parent 37d44e4 commit 6304cf2

File tree

4 files changed

+122
-6
lines changed

4 files changed

+122
-6
lines changed

src/cdp/cdp.zig

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,12 +477,16 @@ pub fn BrowserContext(comptime CDP_T: type) type {
477477
self.cdp.browser.notification.unregister(.http_response_header_done, self);
478478
}
479479

480-
pub fn fetchEnable(self: *Self) !void {
480+
pub fn fetchEnable(self: *Self, auth: bool) !void {
481481
try self.cdp.browser.notification.register(.http_request_intercept, self, onHttpRequestIntercept);
482+
if (auth) {
483+
try self.cdp.browser.notification.register(.http_request_auth_required, self, onHttpRequestAuthRequired);
484+
}
482485
}
483486

484487
pub fn fetchDisable(self: *Self) void {
485488
self.cdp.browser.notification.unregister(.http_request_intercept, self);
489+
self.cdp.browser.notification.unregister(.http_request_auth_required, self);
486490
}
487491

488492
pub fn onPageRemove(ctx: *anyopaque, _: Notification.PageRemove) !void {
@@ -548,6 +552,12 @@ pub fn BrowserContext(comptime CDP_T: type) type {
548552
try gop.value_ptr.appendSlice(arena, try arena.dupe(u8, msg.data));
549553
}
550554

555+
pub fn onHttpRequestAuthRequired(ctx: *anyopaque, data: *const Notification.RequestAuthRequired) !void {
556+
const self: *Self = @alignCast(@ptrCast(ctx));
557+
defer self.resetNotificationArena();
558+
try @import("domains/fetch.zig").requestAuthRequired(self.notification_arena, self, data);
559+
}
560+
551561
fn resetNotificationArena(self: *Self) void {
552562
defer _ = self.cdp.notification_arena.reset(.{ .retain_with_limit = 1024 * 64 });
553563
}

src/cdp/domains/fetch.zig

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,8 @@ fn enable(cmd: anytype) !void {
144144
return cmd.sendResult(null, .{});
145145
}
146146

147-
if (params.handleAuthRequests) {
148-
log.warn(.cdp, "not implemented", .{ .feature = "Fetch.enable handleAuthRequests is not supported yet" });
149-
}
150-
151147
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
152-
try bc.fetchEnable();
148+
try bc.fetchEnable(params.handleAuthRequests);
153149

154150
return cmd.sendResult(null, .{});
155151
}
@@ -346,6 +342,92 @@ fn failRequest(cmd: anytype) !void {
346342
return cmd.sendResult(null, .{});
347343
}
348344

345+
const AuthChallenge = struct {
346+
scheme: enum { basic, digest },
347+
realm: []const u8,
348+
349+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/WWW-Authenticate
350+
// Supports only basic and digest schemes.
351+
pub fn parse(header: []const u8) !AuthChallenge {
352+
var ac: AuthChallenge = .{
353+
.scheme = undefined,
354+
.realm = "",
355+
};
356+
357+
const pos = std.mem.indexOfPos(u8, std.mem.trim(u8, header, std.ascii.whitespace[0..]), 0, " ") orelse header.len;
358+
const _scheme = header[0..pos];
359+
if (std.ascii.eqlIgnoreCase(_scheme, "basic")) {
360+
ac.scheme = .basic;
361+
} else if (std.ascii.eqlIgnoreCase(_scheme, "digest")) {
362+
ac.scheme = .digest;
363+
} else {
364+
return error.UnknownAuthChallengeScheme;
365+
}
366+
367+
// TODO get the realm
368+
369+
return ac;
370+
}
371+
};
372+
373+
pub fn requestAuthRequired(arena: Allocator, bc: anytype, intercept: *const Notification.RequestAuthRequired) !void {
374+
// unreachable because we _have_ to have a page.
375+
const session_id = bc.session_id orelse unreachable;
376+
const target_id = bc.target_id orelse unreachable;
377+
const page = bc.session.currentPage() orelse unreachable;
378+
379+
// We keep it around to wait for modifications to the request.
380+
// NOTE: we assume whomever created the request created it with a lifetime of the Page.
381+
// TODO: What to do when receiving replies for a previous page's requests?
382+
383+
const transfer = intercept.transfer;
384+
try bc.intercept_state.put(transfer);
385+
386+
var challenge: AuthChallenge = undefined;
387+
var source: enum { server, proxy } = undefined;
388+
var it = transfer.responseHeaderIterator();
389+
while (it.next()) |hdr| {
390+
if (std.ascii.eqlIgnoreCase("WWW-Authenticate", hdr.name)) {
391+
source = .server;
392+
challenge = try AuthChallenge.parse(hdr.value);
393+
break;
394+
}
395+
if (std.ascii.eqlIgnoreCase("Proxy-Authenticate", hdr.name)) {
396+
source = .proxy;
397+
challenge = try AuthChallenge.parse(hdr.value);
398+
break;
399+
}
400+
}
401+
402+
try bc.cdp.sendEvent("Fetch.authRequired", .{
403+
.requestId = try std.fmt.allocPrint(arena, "INTERCEPT-{d}", .{transfer.id}),
404+
.request = network.TransferAsRequestWriter.init(transfer),
405+
.frameId = target_id,
406+
.resourceType = switch (transfer.req.resource_type) {
407+
.script => "Script",
408+
.xhr => "XHR",
409+
.document => "Document",
410+
},
411+
.authChallenge = .{
412+
.source = if (source == .server) "Server" else "Proxy",
413+
.origin = "", // TODO get origin, could be the proxy address for example.
414+
.scheme = if (challenge.scheme == .digest) "digest" else "basic",
415+
.realm = challenge.realm,
416+
},
417+
.networkId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
418+
}, .{ .session_id = session_id });
419+
420+
log.debug(.cdp, "request auth required", .{
421+
.state = "paused",
422+
.id = transfer.id,
423+
.url = transfer.uri,
424+
});
425+
// Await continueWithAuth
426+
427+
intercept.wait_for_interception.* = true;
428+
page.request_intercepted = true;
429+
}
430+
349431
// Get u64 from requestId which is formatted as: "INTERCEPT-{d}"
350432
fn idFromRequestId(request_id: []const u8) !u64 {
351433
if (!std.mem.startsWith(u8, request_id, "INTERCEPT-")) {

src/http/Client.zig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ pub const Transfer = struct {
577577
_handle: ?*Handle = null,
578578

579579
_redirecting: bool = false,
580+
_forbidden: bool = false,
580581

581582
// use_proxy is set when the transfer has been associated to a given
582583
// connection in makeRequest().
@@ -796,6 +797,22 @@ pub const Transfer = struct {
796797
if (i >= ct.?.amount) break;
797798
}
798799

800+
// If the response requires an auth challenge and the auth
801+
// interception is enable, we must create a new request and wait
802+
// for the interception's response.
803+
// We won't call the req's callbacks now, but for the following request only.
804+
const status = transfer.response_header.?.status;
805+
if (status == 401 or status == 407) {
806+
if (transfer.client.notification) |notification| {
807+
var wait_for_interception = false;
808+
notification.dispatch(.http_request_auth_required, &.{ .transfer = transfer, .wait_for_interception = &wait_for_interception });
809+
if (wait_for_interception) {
810+
// The user is send an invitation to intercept this request.
811+
return buf_len;
812+
}
813+
}
814+
}
815+
799816
transfer.req.header_callback(transfer) catch |err| {
800817
log.err(.http, "header_callback", .{ .err = err, .req = transfer });
801818
// returning < buf_len terminates the request

src/notification.zig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ pub const Notification = struct {
6464
http_request_start: List = .{},
6565
http_request_intercept: List = .{},
6666
http_request_done: List = .{},
67+
http_request_auth_required: List = .{},
6768
http_response_data: List = .{},
6869
http_response_header_done: List = .{},
6970
notification_created: List = .{},
@@ -77,6 +78,7 @@ pub const Notification = struct {
7778
http_request_fail: *const RequestFail,
7879
http_request_start: *const RequestStart,
7980
http_request_intercept: *const RequestIntercept,
81+
http_request_auth_required: *const RequestAuthRequired,
8082
http_request_done: *const RequestDone,
8183
http_response_data: *const ResponseData,
8284
http_response_header_done: *const ResponseHeaderDone,
@@ -106,6 +108,11 @@ pub const Notification = struct {
106108
wait_for_interception: *bool,
107109
};
108110

111+
pub const RequestAuthRequired = struct {
112+
transfer: *Transfer,
113+
wait_for_interception: *bool,
114+
};
115+
109116
pub const ResponseData = struct {
110117
data: []const u8,
111118
transfer: *Transfer,

0 commit comments

Comments
 (0)