From 210d5325262fb8d5ae553fcdba6f3c045db3e1de Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Mon, 23 Sep 2013 12:09:23 -0700 Subject: [PATCH 1/4] Allow redirects to use same host --- src/transports/http.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/transports/http.c b/src/transports/http.c index eca06ead2..ab42a3c08 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -303,15 +303,28 @@ static int on_headers_complete(http_parser *parser) parser->status_code == 307) && t->location) { + char *host=NULL, *port=NULL, *user=NULL, *pass=NULL; + if (s->redirect_count >= 7) { giterr_set(GITERR_NET, "Too many redirects"); return t->parse_error = PARSE_ERROR_GENERIC; } - if (t->location[0] != '/') { - giterr_set(GITERR_NET, "Only relative redirects are supported"); + if (gitno_extract_url_parts(&host, &port, &user, &pass, t->location, "") < 0) { + giterr_set(GITERR_NET, "Redirect to unparseable url '%s'", t->location); return t->parse_error = PARSE_ERROR_GENERIC; } + git__free(port); + git__free(user); + git__free(pass); + + /* Allow '/'-led urls, or a change of protocol */ + if (strcmp(t->host, host) && t->location[0] != '/') { + git__free(host); + giterr_set(GITERR_NET, "Only same-host redirects are supported"); + return t->parse_error = PARSE_ERROR_GENERIC; + } + git__free(host); /* Set the redirect URL on the stream. This is a transfer of * ownership of the memory. */ From c91444055ab76f0f638a7926d8be8418fef2db0d Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Mon, 23 Sep 2013 11:37:35 -0700 Subject: [PATCH 2/4] Properly parse urls that include protocol:// --- src/netops.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/netops.c b/src/netops.c index 803c2696a..6777ee51b 100644 --- a/src/netops.c +++ b/src/netops.c @@ -581,7 +581,7 @@ int gitno_extract_url_parts( const char *url, const char *default_port) { - char *colon, *slash, *at, *end; + char *colon, *dblslash, *slash, *at, *end; const char *start; /* @@ -589,6 +589,9 @@ int gitno_extract_url_parts( * ==> [user[:pass]@]hostname.tld[:port]/resource */ + dblslash = strstr(url, "://"); + if (dblslash) url = dblslash+3; + colon = strchr(url, ':'); slash = strchr(url, '/'); at = strchr(url, '@'); From eb0ff1307106c4fefc4042b7b7014e44e422b33e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 24 Sep 2013 14:07:08 -0700 Subject: [PATCH 3/4] Disconnect path string to preserve after redirect The subtransport path was relying on pointing to data owned by the remote which meant that after a redirect, the updated path was getting lost for future requests. This updates the http transport to strdup the path and maintain its own lifetime. This also pulls responsibility for parsing the URL back into the http transport and isolates the functions that parse and free that connection data so that they can be reused between the initial parsing and the redirect parsing. --- src/netops.c | 6 +- src/transports/http.c | 177 ++++++++++++++++++++++---------------- tests-clar/online/fetch.c | 5 ++ 3 files changed, 109 insertions(+), 79 deletions(-) diff --git a/src/netops.c b/src/netops.c index 6777ee51b..c1e74546f 100644 --- a/src/netops.c +++ b/src/netops.c @@ -581,17 +581,13 @@ int gitno_extract_url_parts( const char *url, const char *default_port) { - char *colon, *dblslash, *slash, *at, *end; + char *colon, *slash, *at, *end; const char *start; /* - * * ==> [user[:pass]@]hostname.tld[:port]/resource */ - dblslash = strstr(url, "://"); - if (dblslash) url = dblslash+3; - colon = strchr(url, ':'); slash = strchr(url, '/'); at = strchr(url, '@'); diff --git a/src/transports/http.c b/src/transports/http.c index ab42a3c08..aca8c5d8b 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -59,7 +59,7 @@ typedef struct { git_smart_subtransport parent; transport_smart *owner; gitno_socket socket; - const char *path; + char *path; char *host; char *port; char *user_from_url; @@ -125,15 +125,9 @@ static int gen_request( size_t content_length) { http_subtransport *t = OWNING_SUBTRANSPORT(s); + const char *path = t->path ? t->path : "/"; - if (!t->path) - t->path = "/"; - - /* If we were redirected, make sure to respect that here */ - if (s->redirect_url) - git_buf_printf(buf, "%s %s HTTP/1.1\r\n", s->verb, s->redirect_url); - else - git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url); + git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url); git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n"); git_buf_printf(buf, "Host: %s\r\n", t->host); @@ -209,7 +203,7 @@ static int on_header_ready(http_subtransport *t) } else if (!strcasecmp("Location", git_buf_cstr(name))) { if (!t->location) { - t->location= git__strdup(git_buf_cstr(value)); + t->location = git__strdup(git_buf_cstr(value)); GITERR_CHECK_ALLOC(t->location); } } @@ -255,6 +249,93 @@ static int on_header_value(http_parser *parser, const char *str, size_t len) return 0; } +static void free_connection_data(http_subtransport *t) +{ + if (t->host) { + git__free(t->host); + t->host = NULL; + } + + if (t->port) { + git__free(t->port); + t->port = NULL; + } + + if (t->user_from_url) { + git__free(t->user_from_url); + t->user_from_url = NULL; + } + + if (t->pass_from_url) { + git__free(t->pass_from_url); + t->pass_from_url = NULL; + } + + if (t->path) { + git__free(t->path); + t->path = NULL; + } +} + +static int set_connection_data_from_url( + http_subtransport *t, const char *url, const char *service_suffix) +{ + int error = 0; + const char *default_port = NULL; + char *original_host = NULL; + + if (!git__prefixcmp(url, prefix_http)) { + url = url + strlen(prefix_http); + default_port = "80"; + } + + if (!git__prefixcmp(url, prefix_https)) { + url += strlen(prefix_https); + default_port = "443"; + t->use_ssl = 1; + } + + if (!default_port) { + giterr_set(GITERR_NET, "Unrecognized URL prefix"); + return -1; + } + + /* preserve original host name for checking */ + original_host = t->host; + t->host = NULL; + + free_connection_data(t); + + error = gitno_extract_url_parts( + &t->host, &t->port, &t->user_from_url, &t->pass_from_url, + url, default_port); + + if (!error) { + const char *path = strchr(url, '/'); + size_t pathlen = strlen(path); + size_t suffixlen = service_suffix ? strlen(service_suffix) : 0; + + if (suffixlen && + !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) + t->path = git__strndup(path, pathlen - suffixlen); + else + t->path = git__strdup(path); + + /* Allow '/'-led urls, or a change of protocol */ + if (original_host != NULL) { + if (strcmp(original_host, t->host) && t->location[0] != '/') { + giterr_set(GITERR_NET, "Only same-host redirects are supported"); + error = -1; + } + + git__free(original_host); + } + } + + return error; +} + + static int on_headers_complete(http_parser *parser) { parser_context *ctx = (parser_context *) parser->data; @@ -303,28 +384,13 @@ static int on_headers_complete(http_parser *parser) parser->status_code == 307) && t->location) { - char *host=NULL, *port=NULL, *user=NULL, *pass=NULL; - if (s->redirect_count >= 7) { giterr_set(GITERR_NET, "Too many redirects"); return t->parse_error = PARSE_ERROR_GENERIC; } - if (gitno_extract_url_parts(&host, &port, &user, &pass, t->location, "") < 0) { - giterr_set(GITERR_NET, "Redirect to unparseable url '%s'", t->location); + if (set_connection_data_from_url(t, t->location, s->service_url) < 0) return t->parse_error = PARSE_ERROR_GENERIC; - } - git__free(port); - git__free(user); - git__free(pass); - - /* Allow '/'-led urls, or a change of protocol */ - if (strcmp(t->host, host) && t->location[0] != '/') { - git__free(host); - giterr_set(GITERR_NET, "Only same-host redirects are supported"); - return t->parse_error = PARSE_ERROR_GENERIC; - } - git__free(host); /* Set the redirect URL on the stream. This is a transfer of * ownership of the memory. */ @@ -835,50 +901,31 @@ static int http_action( git_smart_service_t action) { http_subtransport *t = (http_subtransport *)subtransport; - const char *default_port = NULL; int ret; if (!stream) return -1; if (!t->host || !t->port || !t->path) { - if (!git__prefixcmp(url, prefix_http)) { - url = url + strlen(prefix_http); - default_port = "80"; - } - - if (!git__prefixcmp(url, prefix_https)) { - url += strlen(prefix_https); - default_port = "443"; - t->use_ssl = 1; - } - - if (!default_port) - return -1; - - if ((ret = gitno_extract_url_parts(&t->host, &t->port, - &t->user_from_url, &t->pass_from_url, url, default_port)) < 0) + if ((ret = set_connection_data_from_url(t, url, NULL)) < 0) return ret; - - t->path = strchr(url, '/'); } if (http_connect(t) < 0) return -1; - switch (action) - { - case GIT_SERVICE_UPLOADPACK_LS: - return http_uploadpack_ls(t, stream); + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return http_uploadpack_ls(t, stream); - case GIT_SERVICE_UPLOADPACK: - return http_uploadpack(t, stream); + case GIT_SERVICE_UPLOADPACK: + return http_uploadpack(t, stream); - case GIT_SERVICE_RECEIVEPACK_LS: - return http_receivepack_ls(t, stream); + case GIT_SERVICE_RECEIVEPACK_LS: + return http_receivepack_ls(t, stream); - case GIT_SERVICE_RECEIVEPACK: - return http_receivepack(t, stream); + case GIT_SERVICE_RECEIVEPACK: + return http_receivepack(t, stream); } *stream = NULL; @@ -906,25 +953,7 @@ static int http_close(git_smart_subtransport *subtransport) t->url_cred = NULL; } - if (t->host) { - git__free(t->host); - t->host = NULL; - } - - if (t->port) { - git__free(t->port); - t->port = NULL; - } - - if (t->user_from_url) { - git__free(t->user_from_url); - t->user_from_url = NULL; - } - - if (t->pass_from_url) { - git__free(t->pass_from_url); - t->pass_from_url = NULL; - } + free_connection_data(t); return 0; } diff --git a/tests-clar/online/fetch.c b/tests-clar/online/fetch.c index bfa1eb972..f76c6cff9 100644 --- a/tests-clar/online/fetch.c +++ b/tests-clar/online/fetch.c @@ -64,6 +64,11 @@ void test_online_fetch__default_http(void) do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); } +void test_online_fetch__default_https(void) +{ + do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6); +} + void test_online_fetch__no_tags_git(void) { do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3); From 46fbc88ee5d4b40786ef48cb5da9c8af28577b55 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 24 Sep 2013 14:50:06 -0700 Subject: [PATCH 4/4] Prevent HTTPS to HTTP redirect --- src/transports/http.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/transports/http.c b/src/transports/http.c index aca8c5d8b..ab2f9a47f 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -287,6 +287,11 @@ static int set_connection_data_from_url( if (!git__prefixcmp(url, prefix_http)) { url = url + strlen(prefix_http); default_port = "80"; + + if (t->use_ssl) { + giterr_set(GITERR_NET, "Redirect from HTTPS to HTTP not allowed"); + return -1; + } } if (!git__prefixcmp(url, prefix_https)) { @@ -324,7 +329,7 @@ static int set_connection_data_from_url( /* Allow '/'-led urls, or a change of protocol */ if (original_host != NULL) { if (strcmp(original_host, t->host) && t->location[0] != '/') { - giterr_set(GITERR_NET, "Only same-host redirects are supported"); + giterr_set(GITERR_NET, "Cross host redirect not allowed"); error = -1; }