@@ -370,6 +370,22 @@ fn perform(self: *Client, timeout_ms: c_int) !void {
370370 const easy = msg .easy_handle .? ;
371371 const transfer = try Transfer .fromEasy (easy );
372372
373+ // In case of auth challenge
374+ if (transfer ._auth_challenge != null ) {
375+ if (transfer .client .notification ) | notification | {
376+ var wait_for_interception = false ;
377+ notification .dispatch (.http_request_auth_required , &.{ .transfer = transfer , .wait_for_interception = & wait_for_interception });
378+ if (wait_for_interception ) {
379+ // the request is put on hold to be intercepted.
380+ // In this case we ignore callbacks for now.
381+ // Note: we don't deinit transfer on purpose: we want to keep
382+ // using it for the following request.
383+ self .endTransfer (transfer );
384+ continue ;
385+ }
386+ }
387+ }
388+
373389 // release it ASAP so that it's available; some done_callbacks
374390 // will load more resources.
375391 self .endTransfer (transfer );
@@ -565,6 +581,44 @@ pub const Request = struct {
565581 };
566582};
567583
584+ pub const AuthChallenge = struct {
585+ source : enum { server , proxy },
586+ scheme : enum { basic , digest },
587+ realm : []const u8 ,
588+
589+ pub fn parse (header : []const u8 ) ! AuthChallenge {
590+ var ac : AuthChallenge = .{
591+ .source = undefined ,
592+ .realm = "TODO" , // TODO parser and set realm
593+ .scheme = undefined ,
594+ };
595+
596+ const sep = std .mem .indexOfPos (u8 , header , 0 , ": " ) orelse return error .InvalidHeader ;
597+ const hname = header [0.. sep ];
598+ const hvalue = header [sep + 2 .. ];
599+
600+ if (std .ascii .eqlIgnoreCase ("WWW-Authenticate" , hname )) {
601+ ac .source = .server ;
602+ } else if (std .ascii .eqlIgnoreCase ("Proxy-Authenticate" , hname )) {
603+ ac .source = .proxy ;
604+ } else {
605+ return error .InvalidAuthChallenge ;
606+ }
607+
608+ const pos = std .mem .indexOfPos (u8 , std .mem .trim (u8 , hvalue , std .ascii .whitespace [0.. ]), 0 , " " ) orelse hvalue .len ;
609+ const _scheme = hvalue [0.. pos ];
610+ if (std .ascii .eqlIgnoreCase (_scheme , "basic" )) {
611+ ac .scheme = .basic ;
612+ } else if (std .ascii .eqlIgnoreCase (_scheme , "digest" )) {
613+ ac .scheme = .digest ;
614+ } else {
615+ return error .UnknownAuthChallengeScheme ;
616+ }
617+
618+ return ac ;
619+ }
620+ };
621+
568622pub const Transfer = struct {
569623 arena : ArenaAllocator ,
570624 id : usize = 0 ,
@@ -588,7 +642,7 @@ pub const Transfer = struct {
588642 _handle : ? * Handle = null ,
589643
590644 _redirecting : bool = false ,
591- _forbidden : bool = false ,
645+ _auth_challenge : ? AuthChallenge = null ,
592646
593647 fn deinit (self : * Transfer ) void {
594648 self .req .headers .deinit ();
@@ -667,6 +721,14 @@ pub const Transfer = struct {
667721 self .deinit ();
668722 }
669723
724+ // abortAuthChallenge is called when an auth chanllenge interception is
725+ // abort. We don't call self.client.endTransfer here b/c it has been done
726+ // before interception process.
727+ pub fn abortAuthChallenge (self : * Transfer ) void {
728+ self .client .requestFailed (self , error .AbortAuthChallenge );
729+ self .deinit ();
730+ }
731+
670732 // redirectionCookies manages cookies during redirections handled by Curl.
671733 // It sets the cookies from the current response to the cookie jar.
672734 // It also immediately sets cookies for the following request.
@@ -792,20 +854,40 @@ pub const Transfer = struct {
792854 transfer ._redirecting = false ;
793855
794856 if (status == 401 or status == 407 ) {
795- transfer ._forbidden = true ;
857+ // The auth challenge must be parsed from a following
858+ // WWW-Authenticate or Proxy-Authenticate header.
859+ transfer ._auth_challenge = .{
860+ .source = undefined ,
861+ .scheme = undefined ,
862+ .realm = undefined ,
863+ };
796864 return buf_len ;
797865 }
798- transfer ._forbidden = false ;
866+ transfer ._auth_challenge = null ;
799867
800868 transfer .bytes_received = buf_len ;
801869 return buf_len ;
802870 }
803871
804- if (transfer ._redirecting == false and transfer ._forbidden == false ) {
872+ if (transfer ._redirecting == false and transfer ._auth_challenge != null ) {
805873 transfer .bytes_received += buf_len ;
806874 }
807875
808876 if (buf_len != 2 ) {
877+ if (transfer ._auth_challenge != null ) {
878+ // try to parse auth challenge.
879+ if (std .ascii .startsWithIgnoreCase (header , "WWW-Authenticate" ) or
880+ std .ascii .startsWithIgnoreCase (header , "Proxy-Authenticate" ))
881+ {
882+ const ac = AuthChallenge .parse (header ) catch | err | {
883+ // We can't parse the auth challenge
884+ log .err (.http , "parse auth challenge" , .{ .err = err , .header = header });
885+ // Should we cancel the request? I don't think so.
886+ return buf_len ;
887+ };
888+ transfer ._auth_challenge = ac ;
889+ }
890+ }
809891 return buf_len ;
810892 }
811893
@@ -833,7 +915,7 @@ pub const Transfer = struct {
833915 return c .CURL_WRITEFUNC_ERROR ;
834916 };
835917
836- if (transfer ._redirecting ) {
918+ if (transfer ._redirecting or transfer . _auth_challenge != null ) {
837919 return chunk_len ;
838920 }
839921
0 commit comments