@@ -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,32 @@ 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+ log .debug (.http , "TRY INTERCEPT" , .{});
377+ var wait_for_interception = false ;
378+ notification .dispatch (.http_request_auth_required , &.{ .transfer = transfer , .wait_for_interception = & wait_for_interception });
379+ if (wait_for_interception ) {
380+ log .debug (.http , "WAIT FOR INTERCEPT" , .{});
381+ // the request is put on hold to be intercepted.
382+ // In this case we ignore callbacks for now.
383+ // Note: we don't deinit transfer on purpose: we want to keep
384+ // using it for the following request.
385+ self .endTransfer (transfer );
386+ continue ;
387+ }
388+ }
389+ }
390+
368391 // release it ASAP so that it's available; some done_callbacks
369392 // will load more resources.
370393 self .endTransfer (transfer );
371394
372395 defer transfer .deinit ();
373396
374397 if (errorCheck (msg .data .result )) {
398+
375399 // In case of request w/o data, we need to call the header done
376400 // callback now.
377401 if (! transfer ._header_done_called ) {
@@ -542,6 +566,7 @@ pub const Request = struct {
542566 body : ? []const u8 = null ,
543567 cookie_jar : * CookieJar ,
544568 resource_type : ResourceType ,
569+ credentials : ? [:0 ]const u8 = null ,
545570
546571 // arbitrary data that can be associated with this request
547572 ctx : * anyopaque = undefined ,
@@ -559,6 +584,44 @@ pub const Request = struct {
559584 };
560585};
561586
587+ pub const AuthChallenge = struct {
588+ source : enum { server , proxy },
589+ scheme : enum { basic , digest },
590+ realm : []const u8 ,
591+
592+ pub fn parse (header : []const u8 ) ! AuthChallenge {
593+ var ac : AuthChallenge = .{
594+ .source = undefined ,
595+ .realm = "TODO" , // TODO parser and set realm
596+ .scheme = undefined ,
597+ };
598+
599+ const sep = std .mem .indexOfPos (u8 , header , 0 , ": " ) orelse return error .InvalidHeader ;
600+ const hname = header [0.. sep ];
601+ const hvalue = header [sep + 2 .. ];
602+
603+ if (std .ascii .eqlIgnoreCase ("WWW-Authenticate" , hname )) {
604+ ac .source = .server ;
605+ } else if (std .ascii .eqlIgnoreCase ("Proxy-Authenticate" , hname )) {
606+ ac .source = .proxy ;
607+ } else {
608+ return error .InvalidAuthChallenge ;
609+ }
610+
611+ const pos = std .mem .indexOfPos (u8 , std .mem .trim (u8 , hvalue , std .ascii .whitespace [0.. ]), 0 , " " ) orelse hvalue .len ;
612+ const _scheme = hvalue [0.. pos ];
613+ if (std .ascii .eqlIgnoreCase (_scheme , "basic" )) {
614+ ac .scheme = .basic ;
615+ } else if (std .ascii .eqlIgnoreCase (_scheme , "digest" )) {
616+ ac .scheme = .digest ;
617+ } else {
618+ return error .UnknownAuthChallengeScheme ;
619+ }
620+
621+ return ac ;
622+ }
623+ };
624+
562625pub const Transfer = struct {
563626 arena : ArenaAllocator ,
564627 id : usize = 0 ,
@@ -582,9 +645,9 @@ pub const Transfer = struct {
582645 _handle : ? * Handle = null ,
583646
584647 _redirecting : bool = false ,
585- _forbidden : bool = false ,
648+ _auth_challenge : ? AuthChallenge = null ,
586649
587- fn deinit (self : * Transfer ) void {
650+ pub fn deinit (self : * Transfer ) void {
588651 self .req .headers .deinit ();
589652 if (self ._handle ) | handle | {
590653 self .client .handles .release (handle );
@@ -633,6 +696,10 @@ pub const Transfer = struct {
633696 self .req .url = url ;
634697 }
635698
699+ pub fn updateCredentials (self : * Transfer , userpwd : [:0 ]const u8 ) void {
700+ self .req .credentials = userpwd ;
701+ }
702+
636703 pub fn replaceRequestHeaders (self : * Transfer , allocator : Allocator , headers : []const Http.Header ) ! void {
637704 self .req .headers .deinit ();
638705
@@ -782,20 +849,40 @@ pub const Transfer = struct {
782849 transfer ._redirecting = false ;
783850
784851 if (status == 401 or status == 407 ) {
785- transfer ._forbidden = true ;
852+ // The auth challenge must be parsed from a following
853+ // WWW-Authenticate or Proxy-Authenticate header.
854+ transfer ._auth_challenge = .{
855+ .source = undefined ,
856+ .scheme = undefined ,
857+ .realm = undefined ,
858+ };
786859 return buf_len ;
787860 }
788- transfer ._forbidden = false ;
861+ transfer ._auth_challenge = null ;
789862
790863 transfer .bytes_received = buf_len ;
791864 return buf_len ;
792865 }
793866
794- if (transfer ._redirecting == false and transfer ._forbidden == false ) {
867+ if (transfer ._redirecting == false and transfer ._auth_challenge != null ) {
795868 transfer .bytes_received += buf_len ;
796869 }
797870
798871 if (buf_len != 2 ) {
872+ if (transfer ._auth_challenge != null ) {
873+ // try to parse auth challenge.
874+ if (std .ascii .startsWithIgnoreCase (header , "WWW-Authenticate" ) or
875+ std .ascii .startsWithIgnoreCase (header , "Proxy-Authenticate" ))
876+ {
877+ const ac = AuthChallenge .parse (header ) catch | err | {
878+ // We can't parse the auth challenge
879+ log .err (.http , "parse auth challenge" , .{ .err = err , .header = header });
880+ // Should we cancel the request? I don't think so.
881+ return buf_len ;
882+ };
883+ transfer ._auth_challenge = ac ;
884+ }
885+ }
799886 return buf_len ;
800887 }
801888
@@ -823,7 +910,7 @@ pub const Transfer = struct {
823910 return c .CURL_WRITEFUNC_ERROR ;
824911 };
825912
826- if (transfer ._redirecting ) {
913+ if (transfer ._redirecting or transfer . _auth_challenge != null ) {
827914 return chunk_len ;
828915 }
829916
0 commit comments