@@ -32,12 +32,14 @@ pub fn processMessage(cmd: anytype) !void {
3232 continueRequest ,
3333 failRequest ,
3434 fulfillRequest ,
35+ continueWithAuth ,
3536 }, cmd .input .action ) orelse return error .UnknownMethod ;
3637
3738 switch (action ) {
3839 .disable = > return disable (cmd ),
3940 .enable = > return enable (cmd ),
4041 .continueRequest = > return continueRequest (cmd ),
42+ .continueWithAuth = > return continueWithAuth (cmd ),
4143 .failRequest = > return failRequest (cmd ),
4244 .fulfillRequest = > return fulfillRequest (cmd ),
4345 }
@@ -144,12 +146,8 @@ fn enable(cmd: anytype) !void {
144146 return cmd .sendResult (null , .{});
145147 }
146148
147- if (params .handleAuthRequests ) {
148- log .warn (.cdp , "not implemented" , .{ .feature = "Fetch.enable handleAuthRequests is not supported yet" });
149- }
150-
151149 const bc = cmd .browser_context orelse return error .BrowserContextNotLoaded ;
152- try bc .fetchEnable ();
150+ try bc .fetchEnable (params . handleAuthRequests );
153151
154152 return cmd .sendResult (null , .{});
155153}
@@ -276,6 +274,60 @@ fn continueRequest(cmd: anytype) !void {
276274 return cmd .sendResult (null , .{});
277275}
278276
277+ fn continueWithAuth (cmd : anytype ) ! void {
278+ const bc = cmd .browser_context orelse return error .BrowserContextNotLoaded ;
279+ const params = (try cmd .params (struct {
280+ requestId : []const u8 , // "INTERCEPT-{d}"
281+ authChallengeResponse : struct {
282+ response : []const u8 ,
283+ username : ? []const u8 ,
284+ password : ? []const u8 ,
285+ },
286+ })) orelse return error .InvalidParams ;
287+
288+ const page = bc .session .currentPage () orelse return error .PageNotLoaded ;
289+
290+ var intercept_state = & bc .intercept_state ;
291+ const request_id = try idFromRequestId (params .requestId );
292+ const transfer = intercept_state .remove (request_id ) orelse return error .RequestNotFound ;
293+
294+ log .debug (.cdp , "request intercept" , .{
295+ .state = "continue with auth" ,
296+ .id = transfer .id ,
297+ .response = params .authChallengeResponse .response ,
298+ });
299+
300+ if (! std .mem .eql (u8 , params .authChallengeResponse .response , "ProvideCredentials" )) {
301+ transfer .abort (); // Is it the correct way to cancel the transfer?
302+ transfer .deinit ();
303+ return cmd .sendResult (null , .{});
304+ }
305+
306+ // cancel the request, deinit the transfer on error.
307+ errdefer {
308+ transfer .abort (); // Is it the correct way to cancel the transfer?
309+ transfer .deinit ();
310+ }
311+
312+ const username = params .authChallengeResponse .username orelse "" ;
313+ const password = params .authChallengeResponse .password orelse "" ;
314+
315+ // restart the request with the provided credentials.
316+ // we need to duplicate the cre
317+ const arena = transfer .arena .allocator ();
318+ transfer .updateCredentials (
319+ try std .fmt .allocPrintZ (arena , "{s}:{s}" , .{ username , password }),
320+ );
321+
322+ try bc .cdp .browser .http_client .process (transfer );
323+
324+ if (intercept_state .empty ()) {
325+ page .request_intercepted = false ;
326+ }
327+
328+ return cmd .sendResult (null , .{});
329+ }
330+
279331fn fulfillRequest (cmd : anytype ) ! void {
280332 const bc = cmd .browser_context orelse return error .BrowserContextNotLoaded ;
281333
@@ -346,6 +398,92 @@ fn failRequest(cmd: anytype) !void {
346398 return cmd .sendResult (null , .{});
347399}
348400
401+ const AuthChallenge = struct {
402+ scheme : enum { basic , digest },
403+ realm : []const u8 ,
404+
405+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/WWW-Authenticate
406+ // Supports only basic and digest schemes.
407+ pub fn parse (header : []const u8 ) ! AuthChallenge {
408+ var ac : AuthChallenge = .{
409+ .scheme = undefined ,
410+ .realm = "" ,
411+ };
412+
413+ const pos = std .mem .indexOfPos (u8 , std .mem .trim (u8 , header , std .ascii .whitespace [0.. ]), 0 , " " ) orelse header .len ;
414+ const _scheme = header [0.. pos ];
415+ if (std .ascii .eqlIgnoreCase (_scheme , "basic" )) {
416+ ac .scheme = .basic ;
417+ } else if (std .ascii .eqlIgnoreCase (_scheme , "digest" )) {
418+ ac .scheme = .digest ;
419+ } else {
420+ return error .UnknownAuthChallengeScheme ;
421+ }
422+
423+ // TODO get the realm
424+
425+ return ac ;
426+ }
427+ };
428+
429+ pub fn requestAuthRequired (arena : Allocator , bc : anytype , intercept : * const Notification.RequestAuthRequired ) ! void {
430+ // unreachable because we _have_ to have a page.
431+ const session_id = bc .session_id orelse unreachable ;
432+ const target_id = bc .target_id orelse unreachable ;
433+ const page = bc .session .currentPage () orelse unreachable ;
434+
435+ // We keep it around to wait for modifications to the request.
436+ // NOTE: we assume whomever created the request created it with a lifetime of the Page.
437+ // TODO: What to do when receiving replies for a previous page's requests?
438+
439+ const transfer = intercept .transfer ;
440+ try bc .intercept_state .put (transfer );
441+
442+ var challenge : AuthChallenge = undefined ;
443+ var source : enum { server , proxy } = undefined ;
444+ var it = transfer .responseHeaderIterator ();
445+ while (it .next ()) | hdr | {
446+ if (std .ascii .eqlIgnoreCase ("WWW-Authenticate" , hdr .name )) {
447+ source = .server ;
448+ challenge = try AuthChallenge .parse (hdr .value );
449+ break ;
450+ }
451+ if (std .ascii .eqlIgnoreCase ("Proxy-Authenticate" , hdr .name )) {
452+ source = .proxy ;
453+ challenge = try AuthChallenge .parse (hdr .value );
454+ break ;
455+ }
456+ }
457+
458+ try bc .cdp .sendEvent ("Fetch.authRequired" , .{
459+ .requestId = try std .fmt .allocPrint (arena , "INTERCEPT-{d}" , .{transfer .id }),
460+ .request = network .TransferAsRequestWriter .init (transfer ),
461+ .frameId = target_id ,
462+ .resourceType = switch (transfer .req .resource_type ) {
463+ .script = > "Script" ,
464+ .xhr = > "XHR" ,
465+ .document = > "Document" ,
466+ },
467+ .authChallenge = .{
468+ .source = if (source == .server ) "Server" else "Proxy" ,
469+ .origin = "" , // TODO get origin, could be the proxy address for example.
470+ .scheme = if (challenge .scheme == .digest ) "digest" else "basic" ,
471+ .realm = challenge .realm ,
472+ },
473+ .networkId = try std .fmt .allocPrint (arena , "REQ-{d}" , .{transfer .id }),
474+ }, .{ .session_id = session_id });
475+
476+ log .debug (.cdp , "request auth required" , .{
477+ .state = "paused" ,
478+ .id = transfer .id ,
479+ .url = transfer .uri ,
480+ });
481+ // Await continueWithAuth
482+
483+ intercept .wait_for_interception .* = true ;
484+ page .request_intercepted = true ;
485+ }
486+
349487// Get u64 from requestId which is formatted as: "INTERCEPT-{d}"
350488fn idFromRequestId (request_id : []const u8 ) ! u64 {
351489 if (! std .mem .startsWith (u8 , request_id , "INTERCEPT-" )) {
0 commit comments