@@ -68,8 +68,7 @@ typedef struct {
6868 unsigned chunk_buffer_len ;
6969 unsigned sent_request : 1 ,
7070 received_response : 1 ,
71- chunked : 1 ,
72- replay_count : 3 ;
71+ chunked : 1 ;
7372} http_stream ;
7473
7574typedef struct {
@@ -103,11 +102,13 @@ typedef struct {
103102 git_buf parse_header_value ;
104103 char parse_buffer_data [NETIO_BUFSIZE ];
105104 char * content_type ;
105+ char * content_length ;
106106 char * location ;
107107 enum last_cb last_cb ;
108108 int parse_error ;
109109 int error ;
110- unsigned parse_finished : 1 ;
110+ unsigned parse_finished : 1 ,
111+ replay_count : 3 ;
111112} http_subtransport ;
112113
113114typedef struct {
@@ -260,8 +261,11 @@ static int gen_request(
260261 }
261262
262263 /* Apply proxy and server credentials to the request */
263- if (apply_credentials (buf , & t -> proxy , AUTH_HEADER_PROXY ) < 0 ||
264- apply_credentials (buf , & t -> server , AUTH_HEADER_SERVER ) < 0 )
264+ if (t -> proxy_opts .type != GIT_PROXY_NONE &&
265+ apply_credentials (buf , & t -> proxy , AUTH_HEADER_PROXY ) < 0 )
266+ return -1 ;
267+
268+ if (apply_credentials (buf , & t -> server , AUTH_HEADER_SERVER ) < 0 )
265269 return -1 ;
266270
267271 git_buf_puts (buf , "\r\n" );
@@ -308,6 +312,12 @@ static int on_header_ready(http_subtransport *t)
308312 GITERR_CHECK_ALLOC (t -> content_type );
309313 }
310314 }
315+ else if (!strcasecmp ("Content-Length" , git_buf_cstr (name ))) {
316+ if (!t -> content_length ) {
317+ t -> content_length = git__strdup (git_buf_cstr (value ));
318+ GITERR_CHECK_ALLOC (t -> content_length );
319+ }
320+ }
311321 else if (!strcasecmp ("Proxy-Authenticate" , git_buf_cstr (name ))) {
312322 char * dup = git__strdup (git_buf_cstr (value ));
313323 GITERR_CHECK_ALLOC (dup );
@@ -438,7 +448,7 @@ static int on_headers_complete(http_parser *parser)
438448 int proxy_auth_types = 0 , server_auth_types = 0 ;
439449
440450 /* Enforce a reasonable cap on the number of replays */
441- if (s -> replay_count ++ >= GIT_HTTP_REPLAY_MAX ) {
451+ if (t -> replay_count ++ >= GIT_HTTP_REPLAY_MAX ) {
442452 giterr_set (GITERR_NET , "too many redirects or authentication replays" );
443453 return t -> parse_error = PARSE_ERROR_GENERIC ;
444454 }
@@ -567,15 +577,19 @@ static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
567577 if (t -> parse_error == PARSE_ERROR_REPLAY )
568578 return 0 ;
569579
570- if (ctx -> buf_size < len ) {
571- giterr_set (GITERR_NET , "can't fit data in the buffer" );
572- return t -> parse_error = PARSE_ERROR_GENERIC ;
580+ /* If there's no buffer set, we're explicitly ignoring the body. */
581+ if (ctx -> buffer ) {
582+ if (ctx -> buf_size < len ) {
583+ giterr_set (GITERR_NET , "can't fit data in the buffer" );
584+ return t -> parse_error = PARSE_ERROR_GENERIC ;
585+ }
586+
587+ memcpy (ctx -> buffer , str , len );
588+ ctx -> buffer += len ;
589+ ctx -> buf_size -= len ;
573590 }
574591
575- memcpy (ctx -> buffer , str , len );
576592 * (ctx -> bytes_read ) += len ;
577- ctx -> buffer += len ;
578- ctx -> buf_size -= len ;
579593
580594 return 0 ;
581595}
@@ -601,6 +615,9 @@ static void clear_parser_state(http_subtransport *t)
601615 git__free (t -> content_type );
602616 t -> content_type = NULL ;
603617
618+ git__free (t -> content_length );
619+ t -> content_length = NULL ;
620+
604621 git__free (t -> location );
605622 t -> location = NULL ;
606623
@@ -734,6 +751,172 @@ static int stream_connect(
734751 return error ;
735752}
736753
754+ static int gen_connect_req (git_buf * buf , http_subtransport * t )
755+ {
756+ git_buf_printf (buf , "CONNECT %s:%s HTTP/1.1\r\n" ,
757+ t -> server .url .host , t -> server .url .port );
758+
759+ git_buf_puts (buf , "User-Agent: " );
760+ git_http__user_agent (buf );
761+ git_buf_puts (buf , "\r\n" );
762+
763+ git_buf_printf (buf , "Host: %s\r\n" , t -> proxy .url .host );
764+
765+ if (apply_credentials (buf , & t -> proxy , AUTH_HEADER_PROXY ) < 0 )
766+ return -1 ;
767+
768+ git_buf_puts (buf , "\r\n" );
769+
770+ return git_buf_oom (buf ) ? -1 : 0 ;
771+ }
772+
773+ static int proxy_headers_complete (http_parser * parser )
774+ {
775+ parser_context * ctx = (parser_context * ) parser -> data ;
776+ http_subtransport * t = ctx -> t ;
777+ int proxy_auth_types = 0 ;
778+
779+ /* Enforce a reasonable cap on the number of replays */
780+ if (t -> replay_count ++ >= GIT_HTTP_REPLAY_MAX ) {
781+ giterr_set (GITERR_NET , "too many redirects or authentication replays" );
782+ return t -> parse_error = PARSE_ERROR_GENERIC ;
783+ }
784+
785+ /* Both parse_header_name and parse_header_value are populated
786+ * and ready for consumption. */
787+ if (VALUE == t -> last_cb )
788+ if (on_header_ready (t ) < 0 )
789+ return t -> parse_error = PARSE_ERROR_GENERIC ;
790+
791+ /*
792+ * Capture authentication headers for the proxy or final endpoint,
793+ * these may be 407/401 (authentication is not complete) or a 200
794+ * (informing us that auth has completed).
795+ */
796+ if (parse_authenticate_response (& t -> proxy , & proxy_auth_types ) < 0 )
797+ return t -> parse_error = PARSE_ERROR_GENERIC ;
798+
799+ /* Check for a proxy authentication failure. */
800+ if (parser -> status_code == 407 )
801+ return on_auth_required (& t -> proxy .cred ,
802+ parser ,
803+ t -> proxy_opts .url ,
804+ SERVER_TYPE_PROXY ,
805+ t -> proxy_opts .credentials ,
806+ t -> proxy_opts .payload ,
807+ t -> proxy .url .user ,
808+ proxy_auth_types );
809+
810+ if (parser -> status_code != 200 ) {
811+ giterr_set (GITERR_NET , "unexpected status code from proxy: %d" ,
812+ parser -> status_code );
813+ return t -> parse_error = PARSE_ERROR_GENERIC ;
814+ }
815+
816+ if (!t -> content_length || strcmp (t -> content_length , "0" ) == 0 )
817+ t -> parse_finished = 1 ;
818+
819+ return 0 ;
820+ }
821+
822+ static int proxy_connect (
823+ git_stream * * out , git_stream * proxy_stream , http_subtransport * t )
824+ {
825+ git_buf request = GIT_BUF_INIT ;
826+ static http_parser_settings proxy_parser_settings = {0 };
827+ size_t bytes_read = 0 , bytes_parsed ;
828+ parser_context ctx ;
829+ int error ;
830+
831+ /* Use the parser settings only to parser headers. */
832+ proxy_parser_settings .on_header_field = on_header_field ;
833+ proxy_parser_settings .on_header_value = on_header_value ;
834+ proxy_parser_settings .on_headers_complete = proxy_headers_complete ;
835+ proxy_parser_settings .on_message_complete = on_message_complete ;
836+
837+ replay :
838+ clear_parser_state (t );
839+
840+ gitno_buffer_setup_fromstream (proxy_stream ,
841+ & t -> parse_buffer ,
842+ t -> parse_buffer_data ,
843+ sizeof (t -> parse_buffer_data ));
844+
845+ if ((error = gen_connect_req (& request , t )) < 0 )
846+ goto done ;
847+
848+ if ((error = git_stream_write (proxy_stream ,
849+ request .ptr , request .size , 0 )) < 0 )
850+ goto done ;
851+
852+ git_buf_dispose (& request );
853+
854+ while (!bytes_read && !t -> parse_finished ) {
855+ t -> parse_buffer .offset = 0 ;
856+
857+ if ((error = gitno_recv (& t -> parse_buffer )) < 0 )
858+ goto done ;
859+
860+ /*
861+ * This call to http_parser_execute will invoke the on_*
862+ * callbacks. Since we don't care about the body of the response,
863+ * we can set our buffer to NULL.
864+ */
865+ ctx .t = t ;
866+ ctx .s = NULL ;
867+ ctx .buffer = NULL ;
868+ ctx .buf_size = 0 ;
869+ ctx .bytes_read = & bytes_read ;
870+
871+ /* Set the context, call the parser, then unset the context. */
872+ t -> parser .data = & ctx ;
873+
874+ bytes_parsed = http_parser_execute (& t -> parser ,
875+ & proxy_parser_settings , t -> parse_buffer .data , t -> parse_buffer .offset );
876+
877+ t -> parser .data = NULL ;
878+
879+ /* Ensure that we didn't get a redirect; unsupported. */
880+ if (t -> location ) {
881+ giterr_set (GITERR_NET , "proxy server sent unsupported redirect during CONNECT" );
882+ error = -1 ;
883+ goto done ;
884+ }
885+
886+ /* Replay the request with authentication headers. */
887+ if (PARSE_ERROR_REPLAY == t -> parse_error )
888+ goto replay ;
889+
890+ if (t -> parse_error < 0 ) {
891+ error = t -> parse_error == PARSE_ERROR_EXT ? PARSE_ERROR_EXT : -1 ;
892+ goto done ;
893+ }
894+
895+ if (bytes_parsed != t -> parse_buffer .offset ) {
896+ giterr_set (GITERR_NET ,
897+ "HTTP parser error: %s" ,
898+ http_errno_description ((enum http_errno )t -> parser .http_errno ));
899+ error = -1 ;
900+ goto done ;
901+ }
902+ }
903+
904+ if ((error = git_tls_stream_wrap (out , proxy_stream , t -> server .url .host )) == 0 )
905+ error = stream_connect (* out , & t -> server .url ,
906+ t -> owner -> certificate_check_cb ,
907+ t -> owner -> message_cb_payload );
908+
909+ /*
910+ * Since we've connected via a HTTPS proxy tunnel, we don't behave
911+ * as if we have an HTTP proxy.
912+ */
913+ t -> proxy_opts .type = GIT_PROXY_NONE ;
914+ t -> replay_count = 0 ;
915+
916+ done :
917+ return error ;
918+ }
919+
737920static int http_connect (http_subtransport * t )
738921{
739922 gitno_connection_data * url ;
@@ -751,9 +934,16 @@ static int http_connect(http_subtransport *t)
751934 git_stream_close (t -> server .stream );
752935 git_stream_free (t -> server .stream );
753936 t -> server .stream = NULL ;
754- t -> connected = 0 ;
755937 }
756938
939+ if (t -> proxy .stream ) {
940+ git_stream_close (t -> proxy .stream );
941+ git_stream_free (t -> proxy .stream );
942+ t -> proxy .stream = NULL ;
943+ }
944+
945+ t -> connected = 0 ;
946+
757947 if (t -> proxy_opts .type == GIT_PROXY_SPECIFIED ) {
758948 url = & t -> proxy .url ;
759949 cert_cb = t -> proxy_opts .certificate_check ;
@@ -786,6 +976,21 @@ static int http_connect(http_subtransport *t)
786976 if ((error = stream_connect (stream , url , cert_cb , cb_payload )) < 0 )
787977 goto on_error ;
788978
979+ /*
980+ * At this point we have a connection to the remote server or to
981+ * a proxy. If it's a proxy and the remote server is actually
982+ * an HTTPS connection, then we need to build a CONNECT tunnel.
983+ */
984+ if (t -> proxy_opts .type == GIT_PROXY_SPECIFIED &&
985+ t -> server .url .use_ssl ) {
986+ proxy_stream = stream ;
987+ stream = NULL ;
988+
989+ if ((error = proxy_connect (& stream , proxy_stream , t )) < 0 )
990+ goto on_error ;
991+ }
992+
993+ t -> proxy .stream = proxy_stream ;
789994 t -> server .stream = stream ;
790995 t -> connected = 1 ;
791996 return 0 ;
@@ -1226,6 +1431,12 @@ static int http_close(git_smart_subtransport *subtransport)
12261431 t -> server .stream = NULL ;
12271432 }
12281433
1434+ if (t -> proxy .stream ) {
1435+ git_stream_close (t -> proxy .stream );
1436+ git_stream_free (t -> proxy .stream );
1437+ t -> proxy .stream = NULL ;
1438+ }
1439+
12291440 free_cred (& t -> server .cred );
12301441 free_cred (& t -> server .url_cred );
12311442 free_cred (& t -> proxy .cred );
0 commit comments