@@ -379,6 +379,15 @@ fn perform(self: *Client, timeout_ms: c_int) !void {
379379 defer transfer .deinit ();
380380
381381 if (errorCheck (msg .data .result )) {
382+ // In case of request w/o data, we need to call the header done
383+ // callback now.
384+ if (! transfer ._header_done_called ) {
385+ transfer .headerDoneCallback (easy ) catch | err | {
386+ log .err (.http , "header_done_callback" , .{ .err = err });
387+ self .requestFailed (transfer , err );
388+ continue ;
389+ };
390+ }
382391 transfer .req .done_callback (transfer .ctx ) catch | err | {
383392 // transfer isn't valid at this point, don't use it.
384393 log .err (.http , "done_callback" , .{ .err = err });
@@ -572,6 +581,9 @@ pub const Transfer = struct {
572581 proxy_response_header : ? ResponseHeader = null ,
573582 response_header : ? ResponseHeader = null ,
574583
584+ // track if the header callbacks done have been called.
585+ _header_done_called : bool = false ,
586+
575587 _notified_fail : bool = false ,
576588
577589 _handle : ? * Handle = null ,
@@ -678,6 +690,48 @@ pub const Transfer = struct {
678690 try errorCheck (c .curl_easy_setopt (easy , c .CURLOPT_COOKIE , @as ([* c ]const u8 , @ptrCast (cookies .items .ptr ))));
679691 }
680692
693+ // headerDoneCallback is called once the headers have been read.
694+ // It can be called either on dataCallback or once the request for those
695+ // w/o body.
696+ fn headerDoneCallback (transfer : * Transfer , easy : * c.CURL ) ! void {
697+ std .debug .assert (transfer ._header_done_called == false );
698+ std .debug .assert (transfer .response_header != null );
699+
700+ defer transfer ._header_done_called = true ;
701+
702+ if (getResponseHeader (easy , "content-type" , 0 )) | ct | {
703+ var hdr = & transfer .response_header .? ;
704+ const value = ct .value ;
705+ const len = @min (value .len , ResponseHeader .MAX_CONTENT_TYPE_LEN );
706+ hdr ._content_type_len = len ;
707+ @memcpy (hdr ._content_type [0.. len ], value [0.. len ]);
708+ }
709+
710+ var i : usize = 0 ;
711+ while (true ) {
712+ const ct = getResponseHeader (easy , "set-cookie" , i );
713+ if (ct == null ) break ;
714+ transfer .req .cookie_jar .populateFromResponse (& transfer .uri , ct .? .value ) catch | err | {
715+ log .err (.http , "set cookie" , .{ .err = err , .req = transfer });
716+ return err ;
717+ };
718+ i += 1 ;
719+ if (i >= ct .? .amount ) break ;
720+ }
721+
722+ transfer .req .header_callback (transfer ) catch | err | {
723+ log .err (.http , "header_callback" , .{ .err = err , .req = transfer });
724+ return err ;
725+ };
726+
727+ if (transfer .client .notification ) | notification | {
728+ notification .dispatch (.http_response_header_done , &.{
729+ .transfer = transfer ,
730+ });
731+ }
732+ }
733+
734+ // headerCallback is called by curl on each request's header line read.
681735 fn headerCallback (buffer : [* ]const u8 , header_count : usize , buf_len : usize , data : * anyopaque ) callconv (.c ) usize {
682736 // libcurl should only ever emit 1 header at a time
683737 std .debug .assert (header_count == 1 );
@@ -692,20 +746,9 @@ pub const Transfer = struct {
692746
693747 const header = buffer [0 .. buf_len - 2 ];
694748
695- if (transfer .response_header == null ) {
696- if (transfer ._redirecting and buf_len == 2 ) {
697- // parse and set cookies for the redirection.
698- redirectionCookies (transfer , easy ) catch | err | {
699- log .debug (.http , "redirection cookies" , .{ .err = err });
700- return 0 ;
701- };
702- return buf_len ;
703- }
704-
705- if (buf_len < 13 or std .mem .startsWith (u8 , header , "HTTP/" ) == false ) {
706- if (transfer ._redirecting ) {
707- return buf_len ;
708- }
749+ // Is it the first header line?
750+ if (std .mem .startsWith (u8 , header , "HTTP/" )) {
751+ if (buf_len < 13 ) {
709752 log .debug (.http , "invalid response line" , .{ .line = header });
710753 return 0 ;
711754 }
@@ -741,75 +784,31 @@ pub const Transfer = struct {
741784 return buf_len ;
742785 }
743786
744- transfer .bytes_received += buf_len ;
787+ if (transfer ._redirecting == false ) {
788+ transfer .bytes_received += buf_len ;
789+ }
745790
746791 if (buf_len != 2 ) {
747792 return buf_len ;
748793 }
749794
750795 // Starting here, we get the last header line.
751796
752- // We're connecting to a proxy. Consider the first request to the
797+ // We're connecting to a proxy. Consider the first request to be the
753798 // proxy's result.
754799 if (transfer ._use_proxy and transfer .proxy_response_header == null ) {
755- // We have to cases:
756- // 1. for http://, we have one request. So both
757- // proxy_response_header and response_header will have the same
758- // value.
759- //
760- // 2. for https://, we two successive requests, a CONNECT to the
761- // proxy and a final request. So proxy_response_header and
762- // response_header may have different values.
763800 transfer .proxy_response_header = transfer .response_header ;
764-
765- // Detect if the request is a CONNECT to the proxy. There might be
766- // a better way to detect this, but I didn't find a better one.
767- // When we don't force curl to always use tunneling, it uses
768- // CONNECT tunnel only for https requests.
769- const is_connect = std .mem .startsWith (u8 , std .mem .span (transfer .proxy_response_header .? .url ), "https" );
770-
771- // If the CONNECT is successful, curl will create a following
772- // request to the final target, so we reset
773- // transfer.response_header to get the "real" data.
774- if (is_connect and transfer .proxy_response_header .? .status == 200 ) {
775- transfer .response_header = null ;
776- return buf_len ;
777- }
778-
779- // If the CONNECT fails, use the request result as it would be our
780- // final request.
781801 }
782802
783- if (getResponseHeader (easy , "content-type" , 0 )) | ct | {
784- var hdr = & transfer .response_header .? ;
785- const value = ct .value ;
786- const len = @min (value .len , ResponseHeader .MAX_CONTENT_TYPE_LEN );
787- hdr ._content_type_len = len ;
788- @memcpy (hdr ._content_type [0.. len ], value [0.. len ]);
789- }
790-
791- var i : usize = 0 ;
792- while (true ) {
793- const ct = getResponseHeader (easy , "set-cookie" , i );
794- if (ct == null ) break ;
795- transfer .req .cookie_jar .populateFromResponse (& transfer .uri , ct .? .value ) catch | err | {
796- log .err (.http , "set cookie" , .{ .err = err , .req = transfer });
803+ if (transfer ._redirecting ) {
804+ // parse and set cookies for the redirection.
805+ redirectionCookies (transfer , easy ) catch | err | {
806+ log .debug (.http , "redirection cookies" , .{ .err = err });
807+ return 0 ;
797808 };
798- i += 1 ;
799- if (i >= ct .? .amount ) break ;
809+ return buf_len ;
800810 }
801811
802- transfer .req .header_callback (transfer ) catch | err | {
803- log .err (.http , "header_callback" , .{ .err = err , .req = transfer });
804- // returning < buf_len terminates the request
805- return 0 ;
806- };
807-
808- if (transfer .client .notification ) | notification | {
809- notification .dispatch (.http_response_header_done , &.{
810- .transfer = transfer ,
811- });
812- }
813812 return buf_len ;
814813 }
815814
@@ -827,6 +826,13 @@ pub const Transfer = struct {
827826 return chunk_len ;
828827 }
829828
829+ if (! transfer ._header_done_called ) {
830+ transfer .headerDoneCallback (easy ) catch | err | {
831+ log .err (.http , "header_done_callback" , .{ .err = err , .req = transfer });
832+ return c .CURL_WRITEFUNC_ERROR ;
833+ };
834+ }
835+
830836 transfer .bytes_received += chunk_len ;
831837 const chunk = buffer [0.. chunk_len ];
832838 transfer .req .data_callback (transfer , chunk ) catch | err | {
0 commit comments