@@ -325,6 +325,11 @@ fn makeRequest(self: *Client, handle: *Handle, transfer: *Transfer) !void {
325325 }
326326
327327 try errorCheck (c .curl_easy_setopt (easy , c .CURLOPT_PRIVATE , transfer ));
328+
329+ // add credentials
330+ if (req .credentials ) | creds | {
331+ try errorCheck (c .curl_easy_setopt (easy , c .CURLOPT_PROXYUSERPWD , creds .ptr ));
332+ }
328333 }
329334
330335 // Once soon as this is called, our "perform" loop is responsible for
@@ -365,13 +370,30 @@ fn perform(self: *Client, timeout_ms: c_int) !void {
365370 const easy = msg .easy_handle .? ;
366371 const transfer = try Transfer .fromEasy (easy );
367372
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+
368389 // release it ASAP so that it's available; some done_callbacks
369390 // will load more resources.
370391 self .endTransfer (transfer );
371392
372393 defer transfer .deinit ();
373394
374395 if (errorCheck (msg .data .result )) {
396+
375397 // In case of request w/o data, we need to call the header done
376398 // callback now.
377399 if (! transfer ._header_done_called ) {
@@ -542,6 +564,7 @@ pub const Request = struct {
542564 body : ? []const u8 = null ,
543565 cookie_jar : * CookieJar ,
544566 resource_type : ResourceType ,
567+ credentials : ? [:0 ]const u8 = null ,
545568
546569 // arbitrary data that can be associated with this request
547570 ctx : * anyopaque = undefined ,
@@ -559,6 +582,44 @@ pub const Request = struct {
559582 };
560583};
561584
585+ pub const AuthChallenge = struct {
586+ source : enum { server , proxy },
587+ scheme : enum { basic , digest },
588+ realm : []const u8 ,
589+
590+ pub fn parse (header : []const u8 ) ! AuthChallenge {
591+ var ac : AuthChallenge = .{
592+ .source = undefined ,
593+ .realm = "TODO" , // TODO parser and set realm
594+ .scheme = undefined ,
595+ };
596+
597+ const sep = std .mem .indexOfPos (u8 , header , 0 , ": " ) orelse return error .InvalidHeader ;
598+ const hname = header [0.. sep ];
599+ const hvalue = header [sep + 2 .. ];
600+
601+ if (std .ascii .eqlIgnoreCase ("WWW-Authenticate" , hname )) {
602+ ac .source = .server ;
603+ } else if (std .ascii .eqlIgnoreCase ("Proxy-Authenticate" , hname )) {
604+ ac .source = .proxy ;
605+ } else {
606+ return error .InvalidAuthChallenge ;
607+ }
608+
609+ const pos = std .mem .indexOfPos (u8 , std .mem .trim (u8 , hvalue , std .ascii .whitespace [0.. ]), 0 , " " ) orelse hvalue .len ;
610+ const _scheme = hvalue [0.. pos ];
611+ if (std .ascii .eqlIgnoreCase (_scheme , "basic" )) {
612+ ac .scheme = .basic ;
613+ } else if (std .ascii .eqlIgnoreCase (_scheme , "digest" )) {
614+ ac .scheme = .digest ;
615+ } else {
616+ return error .UnknownAuthChallengeScheme ;
617+ }
618+
619+ return ac ;
620+ }
621+ };
622+
562623pub const Transfer = struct {
563624 arena : ArenaAllocator ,
564625 id : usize = 0 ,
@@ -582,9 +643,9 @@ pub const Transfer = struct {
582643 _handle : ? * Handle = null ,
583644
584645 _redirecting : bool = false ,
585- _forbidden : bool = false ,
646+ _auth_challenge : ? AuthChallenge = null ,
586647
587- fn deinit (self : * Transfer ) void {
648+ pub fn deinit (self : * Transfer ) void {
588649 self .req .headers .deinit ();
589650 if (self ._handle ) | handle | {
590651 self .client .handles .release (handle );
@@ -633,6 +694,10 @@ pub const Transfer = struct {
633694 self .req .url = url ;
634695 }
635696
697+ pub fn updateCredentials (self : * Transfer , userpwd : [:0 ]const u8 ) void {
698+ self .req .credentials = userpwd ;
699+ }
700+
636701 pub fn replaceRequestHeaders (self : * Transfer , allocator : Allocator , headers : []const Http.Header ) ! void {
637702 self .req .headers .deinit ();
638703
@@ -657,6 +722,14 @@ pub const Transfer = struct {
657722 self .deinit ();
658723 }
659724
725+ // abortAuthChallenge is called when an auth chanllenge interception is
726+ // abort. We don't call self.client.endTransfer here b/c it has been done
727+ // before interception process.
728+ pub fn abortAuthChallenge (self : * Transfer ) void {
729+ self .client .requestFailed (self , error .AbortAuthChallenge );
730+ self .deinit ();
731+ }
732+
660733 // redirectionCookies manages cookies during redirections handled by Curl.
661734 // It sets the cookies from the current response to the cookie jar.
662735 // It also immediately sets cookies for the following request.
@@ -782,20 +855,40 @@ pub const Transfer = struct {
782855 transfer ._redirecting = false ;
783856
784857 if (status == 401 or status == 407 ) {
785- transfer ._forbidden = true ;
858+ // The auth challenge must be parsed from a following
859+ // WWW-Authenticate or Proxy-Authenticate header.
860+ transfer ._auth_challenge = .{
861+ .source = undefined ,
862+ .scheme = undefined ,
863+ .realm = undefined ,
864+ };
786865 return buf_len ;
787866 }
788- transfer ._forbidden = false ;
867+ transfer ._auth_challenge = null ;
789868
790869 transfer .bytes_received = buf_len ;
791870 return buf_len ;
792871 }
793872
794- if (transfer ._redirecting == false and transfer ._forbidden == false ) {
873+ if (transfer ._redirecting == false and transfer ._auth_challenge != null ) {
795874 transfer .bytes_received += buf_len ;
796875 }
797876
798877 if (buf_len != 2 ) {
878+ if (transfer ._auth_challenge != null ) {
879+ // try to parse auth challenge.
880+ if (std .ascii .startsWithIgnoreCase (header , "WWW-Authenticate" ) or
881+ std .ascii .startsWithIgnoreCase (header , "Proxy-Authenticate" ))
882+ {
883+ const ac = AuthChallenge .parse (header ) catch | err | {
884+ // We can't parse the auth challenge
885+ log .err (.http , "parse auth challenge" , .{ .err = err , .header = header });
886+ // Should we cancel the request? I don't think so.
887+ return buf_len ;
888+ };
889+ transfer ._auth_challenge = ac ;
890+ }
891+ }
799892 return buf_len ;
800893 }
801894
@@ -823,7 +916,7 @@ pub const Transfer = struct {
823916 return c .CURL_WRITEFUNC_ERROR ;
824917 };
825918
826- if (transfer ._redirecting ) {
919+ if (transfer ._redirecting or transfer . _auth_challenge != null ) {
827920 return chunk_len ;
828921 }
829922
0 commit comments