Skip to content

Commit 5d4e1e0

Browse files
committed
http: use CONNECT to talk to proxies
Natively support HTTPS connections through proxies by speaking CONNECT to the proxy and then adding a TLS connection on top of the socket.
1 parent 43b592a commit 5d4e1e0

File tree

1 file changed

+224
-13
lines changed

1 file changed

+224
-13
lines changed

src/transports/http.c

Lines changed: 224 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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

7574
typedef 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

113114
typedef 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+
737920
static 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

Comments
 (0)