@@ -278,7 +278,7 @@ fn requestFailed(self: *Client, transfer: *Transfer, err: anyerror) void {
278278pub fn changeProxy (self : * Client , proxy : [:0 ]const u8 ) ! void {
279279 try self .ensureNoActiveConnection ();
280280
281- for (self .handles .handles ) | h | {
281+ for (self .handles .handles ) | * h | {
282282 try errorCheck (c .curl_easy_setopt (h .conn .easy , c .CURLOPT_PROXY , proxy .ptr ));
283283 }
284284 try errorCheck (c .curl_easy_setopt (self .blocking .conn .easy , c .CURLOPT_PROXY , proxy .ptr ));
@@ -290,7 +290,7 @@ pub fn restoreOriginalProxy(self: *Client) !void {
290290 try self .ensureNoActiveConnection ();
291291
292292 const proxy = if (self .http_proxy ) | p | p .ptr else null ;
293- for (self .handles .handles ) | h | {
293+ for (self .handles .handles ) | * h | {
294294 try errorCheck (c .curl_easy_setopt (h .conn .easy , c .CURLOPT_PROXY , proxy ));
295295 }
296296 try errorCheck (c .curl_easy_setopt (self .blocking .conn .easy , c .CURLOPT_PROXY , proxy ));
@@ -359,7 +359,7 @@ fn perform(self: *Client, timeout_ms: c_int) !void {
359359 var messages_count : c_int = 0 ;
360360 while (c .curl_multi_info_read (multi , & messages_count )) | msg_ | {
361361 const msg : * c.CURLMsg = @ptrCast (msg_ );
362- // This is the only possible mesage type from CURL for now.
362+ // This is the only possible message type from CURL for now.
363363 std .debug .assert (msg .msg == c .CURLMSG_DONE );
364364
365365 const easy = msg .easy_handle .? ;
@@ -372,6 +372,15 @@ fn perform(self: *Client, timeout_ms: c_int) !void {
372372 defer transfer .deinit ();
373373
374374 if (errorCheck (msg .data .result )) {
375+ // In case of request w/o data, we need to call the header done
376+ // callback now.
377+ if (! transfer ._header_done_called ) {
378+ transfer .headerDoneCallback (easy ) catch | err | {
379+ log .err (.http , "header_done_callback" , .{ .err = err });
380+ self .requestFailed (transfer , err );
381+ continue ;
382+ };
383+ }
375384 transfer .req .done_callback (transfer .ctx ) catch | err | {
376385 // transfer isn't valid at this point, don't use it.
377386 log .err (.http , "done_callback" , .{ .err = err });
@@ -562,13 +571,18 @@ pub const Transfer = struct {
562571 bytes_received : usize = 0 ,
563572
564573 // We'll store the response header here
574+ proxy_response_header : ? ResponseHeader = null ,
565575 response_header : ? ResponseHeader = null ,
566576
577+ // track if the header callbacks done have been called.
578+ _header_done_called : bool = false ,
579+
567580 _notified_fail : bool = false ,
568581
569582 _handle : ? * Handle = null ,
570583
571584 _redirecting : bool = false ,
585+ _forbidden : bool = false ,
572586
573587 fn deinit (self : * Transfer ) void {
574588 self .req .headers .deinit ();
@@ -579,17 +593,34 @@ pub const Transfer = struct {
579593 self .client .transfer_pool .destroy (self );
580594 }
581595
596+ fn buildResponseHeader (self : * Transfer , easy : * c.CURL ) ! void {
597+ std .debug .assert (self .response_header == null );
598+
599+ var url : [* c ]u8 = undefined ;
600+ try errorCheck (c .curl_easy_getinfo (easy , c .CURLINFO_EFFECTIVE_URL , & url ));
601+
602+ var status : c_long = undefined ;
603+ try errorCheck (c .curl_easy_getinfo (easy , c .CURLINFO_RESPONSE_CODE , & status ));
604+
605+ self .response_header = .{
606+ .url = url ,
607+ .status = @intCast (status ),
608+ };
609+
610+ if (getResponseHeader (easy , "content-type" , 0 )) | ct | {
611+ var hdr = & self .response_header .? ;
612+ const value = ct .value ;
613+ const len = @min (value .len , ResponseHeader .MAX_CONTENT_TYPE_LEN );
614+ hdr ._content_type_len = len ;
615+ @memcpy (hdr ._content_type [0.. len ], value [0.. len ]);
616+ }
617+ }
618+
582619 pub fn format (self : * const Transfer , comptime _ : []const u8 , _ : std.fmt.FormatOptions , writer : anytype ) ! void {
583620 const req = self .req ;
584621 return writer .print ("{s} {s}" , .{ @tagName (req .method ), req .url });
585622 }
586623
587- pub fn setBody (self : * Transfer , body : []const u8 ) ! void {
588- const easy = self .handle .easy ;
589- try errorCheck (c .curl_easy_setopt (easy , c .CURLOPT_POSTFIELDS , body .ptr ));
590- try errorCheck (c .curl_easy_setopt (easy , c .CURLOPT_POSTFIELDSIZE , @as (c_long , @intCast (body .len ))));
591- }
592-
593624 pub fn addHeader (self : * Transfer , value : [:0 ]const u8 ) ! void {
594625 self ._request_header_list = c .curl_slist_append (self ._request_header_list , value );
595626 }
@@ -666,6 +697,48 @@ pub const Transfer = struct {
666697 try errorCheck (c .curl_easy_setopt (easy , c .CURLOPT_COOKIE , @as ([* c ]const u8 , @ptrCast (cookies .items .ptr ))));
667698 }
668699
700+ // headerDoneCallback is called once the headers have been read.
701+ // It can be called either on dataCallback or once the request for those
702+ // w/o body.
703+ fn headerDoneCallback (transfer : * Transfer , easy : * c.CURL ) ! void {
704+ std .debug .assert (transfer ._header_done_called == false );
705+ defer transfer ._header_done_called = true ;
706+
707+ try transfer .buildResponseHeader (easy );
708+
709+ if (getResponseHeader (easy , "content-type" , 0 )) | ct | {
710+ var hdr = & transfer .response_header .? ;
711+ const value = ct .value ;
712+ const len = @min (value .len , ResponseHeader .MAX_CONTENT_TYPE_LEN );
713+ hdr ._content_type_len = len ;
714+ @memcpy (hdr ._content_type [0.. len ], value [0.. len ]);
715+ }
716+
717+ var i : usize = 0 ;
718+ while (true ) {
719+ const ct = getResponseHeader (easy , "set-cookie" , i );
720+ if (ct == null ) break ;
721+ transfer .req .cookie_jar .populateFromResponse (& transfer .uri , ct .? .value ) catch | err | {
722+ log .err (.http , "set cookie" , .{ .err = err , .req = transfer });
723+ return err ;
724+ };
725+ i += 1 ;
726+ if (i >= ct .? .amount ) break ;
727+ }
728+
729+ transfer .req .header_callback (transfer ) catch | err | {
730+ log .err (.http , "header_callback" , .{ .err = err , .req = transfer });
731+ return err ;
732+ };
733+
734+ if (transfer .client .notification ) | notification | {
735+ notification .dispatch (.http_response_header_done , &.{
736+ .transfer = transfer ,
737+ });
738+ }
739+ }
740+
741+ // headerCallback is called by curl on each request's header line read.
669742 fn headerCallback (buffer : [* ]const u8 , header_count : usize , buf_len : usize , data : * anyopaque ) callconv (.c ) usize {
670743 // libcurl should only ever emit 1 header at a time
671744 std .debug .assert (header_count == 1 );
@@ -680,20 +753,13 @@ pub const Transfer = struct {
680753
681754 const header = buffer [0 .. buf_len - 2 ];
682755
683- if (transfer .response_header == null ) {
684- if (transfer ._redirecting and buf_len == 2 ) {
685- // parse and set cookies for the redirection.
686- redirectionCookies (transfer , easy ) catch | err | {
687- log .debug (.http , "redirection cookies" , .{ .err = err });
688- return 0 ;
689- };
690- return buf_len ;
691- }
692-
693- if (buf_len < 13 or std .mem .startsWith (u8 , header , "HTTP/" ) == false ) {
694- if (transfer ._redirecting ) {
695- return buf_len ;
696- }
756+ // We need to parse the first line headers for each request b/c curl's
757+ // CURLINFO_RESPONSE_CODE returns the status code of the final request.
758+ // If a redirection or a proxy's CONNECT forbidden happens, we won't
759+ // get this intermediary status code.
760+ if (std .mem .startsWith (u8 , header , "HTTP/" )) {
761+ // Is it the first header line.
762+ if (buf_len < 13 ) {
697763 log .debug (.http , "invalid response line" , .{ .line = header });
698764 return 0 ;
699765 }
@@ -715,53 +781,35 @@ pub const Transfer = struct {
715781 }
716782 transfer ._redirecting = false ;
717783
718- var url : [ * c ] u8 = undefined ;
719- errorCheck ( c . curl_easy_getinfo ( easy , c . CURLINFO_EFFECTIVE_URL , & url )) catch | err | {
720- log . err ( .http , "failed to get URL" , .{ . err = err }) ;
721- return 0 ;
722- } ;
784+ if ( status == 401 or status == 407 ) {
785+ transfer . _forbidden = true ;
786+ return buf_len ;
787+ }
788+ transfer . _forbidden = false ;
723789
724- transfer .response_header = .{
725- .url = url ,
726- .status = status ,
727- };
728790 transfer .bytes_received = buf_len ;
729791 return buf_len ;
730792 }
731793
732- transfer .bytes_received += buf_len ;
733- if (buf_len == 2 ) {
734- if (getResponseHeader (easy , "content-type" , 0 )) | ct | {
735- var hdr = & transfer .response_header .? ;
736- const value = ct .value ;
737- const len = @min (value .len , ResponseHeader .MAX_CONTENT_TYPE_LEN );
738- hdr ._content_type_len = len ;
739- @memcpy (hdr ._content_type [0.. len ], value [0.. len ]);
740- }
794+ if (transfer ._redirecting == false and transfer ._forbidden == false ) {
795+ transfer .bytes_received += buf_len ;
796+ }
741797
742- var i : usize = 0 ;
743- while (true ) {
744- const ct = getResponseHeader (easy , "set-cookie" , i );
745- if (ct == null ) break ;
746- transfer .req .cookie_jar .populateFromResponse (& transfer .uri , ct .? .value ) catch | err | {
747- log .err (.http , "set cookie" , .{ .err = err , .req = transfer });
748- };
749- i += 1 ;
750- if (i >= ct .? .amount ) break ;
751- }
798+ if (buf_len != 2 ) {
799+ return buf_len ;
800+ }
801+
802+ // Starting here, we get the last header line.
752803
753- transfer .req .header_callback (transfer ) catch | err | {
754- log .err (.http , "header_callback" , .{ .err = err , .req = transfer });
755- // returning < buf_len terminates the request
804+ if (transfer ._redirecting ) {
805+ // parse and set cookies for the redirection.
806+ redirectionCookies (transfer , easy ) catch | err | {
807+ log .debug (.http , "redirection cookies" , .{ .err = err });
756808 return 0 ;
757809 };
758-
759- if (transfer .client .notification ) | notification | {
760- notification .dispatch (.http_response_header_done , &.{
761- .transfer = transfer ,
762- });
763- }
810+ return buf_len ;
764811 }
812+
765813 return buf_len ;
766814 }
767815
@@ -779,6 +827,13 @@ pub const Transfer = struct {
779827 return chunk_len ;
780828 }
781829
830+ if (! transfer ._header_done_called ) {
831+ transfer .headerDoneCallback (easy ) catch | err | {
832+ log .err (.http , "header_done_callback" , .{ .err = err , .req = transfer });
833+ return c .CURL_WRITEFUNC_ERROR ;
834+ };
835+ }
836+
782837 transfer .bytes_received += chunk_len ;
783838 const chunk = buffer [0.. chunk_len ];
784839 transfer .req .data_callback (transfer , chunk ) catch | err | {
0 commit comments