Commit 342e55ac9104346d925154b5d472cf5f58668f0d

Edward Thomson 2021-12-18T10:13:18

url: optionally allow off-site redirects In redirect application logic, (optionally) allow off-site redirects.

diff --git a/src/net.c b/src/net.c
index a91e746..79c3fcd 100644
--- a/src/net.c
+++ b/src/net.c
@@ -315,6 +315,7 @@ static void remove_service_suffix(
 int git_net_url_apply_redirect(
 	git_net_url *url,
 	const char *redirect_location,
+	bool allow_offsite,
 	const char *service_suffix)
 {
 	git_net_url tmp = GIT_NET_URL_INIT;
@@ -339,8 +340,8 @@ int git_net_url_apply_redirect(
 		/* Validate that this is a legal redirection */
 
 		if (original->scheme &&
-			strcmp(original->scheme, tmp.scheme) != 0 &&
-			strcmp(tmp.scheme, "https") != 0) {
+		    strcmp(original->scheme, tmp.scheme) != 0 &&
+		    strcmp(tmp.scheme, "https") != 0) {
 			git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
 				original->scheme, tmp.scheme);
 
@@ -349,6 +350,7 @@ int git_net_url_apply_redirect(
 		}
 
 		if (original->host &&
+		    !allow_offsite &&
 		    git__strcasecmp(original->host, tmp.host) != 0) {
 			git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
 				original->host, tmp.host);
diff --git a/src/net.h b/src/net.h
index 728add3..c743974 100644
--- a/src/net.h
+++ b/src/net.h
@@ -46,6 +46,7 @@ extern bool git_net_url_is_ipv6(git_net_url *url);
 extern int git_net_url_apply_redirect(
 	git_net_url *url,
 	const char *redirect_location,
+	bool allow_offsite,
 	const char *service_suffix);
 
 /** Swaps the contents of one URL for another.  */
diff --git a/src/transports/http.c b/src/transports/http.c
index fa576ff..6a17fa9 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -233,7 +233,7 @@ static int handle_response(
 			return -1;
 		}
 
-		if (git_net_url_apply_redirect(&transport->server.url, response->location, stream->service->url) < 0) {
+		if (git_net_url_apply_redirect(&transport->server.url, response->location, false, stream->service->url) < 0) {
 			return -1;
 		}
 
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index 9b5a67d..fdc6cf2 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -1190,7 +1190,7 @@ replay:
 
 			if (!git__prefixcmp_icase(location8, prefix_https)) {
 				/* Upgrade to secure connection; disconnect and start over */
-				if (git_net_url_apply_redirect(&t->server.url, location8, s->service_url) < 0) {
+				if (git_net_url_apply_redirect(&t->server.url, location8, false, s->service_url) < 0) {
 					git__free(location8);
 					return -1;
 				}
diff --git a/tests/network/url/redirect.c b/tests/network/url/redirect.c
index 2c0b614..a94db7d 100644
--- a/tests/network/url/redirect.c
+++ b/tests/network/url/redirect.c
@@ -17,9 +17,9 @@ void test_network_url_redirect__cleanup(void)
 void test_network_url_redirect__redirect_http(void)
 {
 	cl_git_pass(git_net_url_parse(&conndata,
-				"http://example.com/foo/bar/baz"));
+		"http://example.com/foo/bar/baz"));
 	cl_git_pass(git_net_url_apply_redirect(&conndata,
-				"http://example.com/foo/bar/baz", "bar/baz"));
+		"http://example.com/foo/bar/baz", false, "bar/baz"));
 	cl_assert_equal_s(conndata.scheme, "http");
 	cl_assert_equal_s(conndata.host, "example.com");
 	cl_assert_equal_s(conndata.port, "80");
@@ -31,9 +31,9 @@ void test_network_url_redirect__redirect_http(void)
 void test_network_url_redirect__redirect_ssl(void)
 {
 	cl_git_pass(git_net_url_parse(&conndata,
-				"https://example.com/foo/bar/baz"));
+		"https://example.com/foo/bar/baz"));
 	cl_git_pass(git_net_url_apply_redirect(&conndata,
-				"https://example.com/foo/bar/baz", "bar/baz"));
+		"https://example.com/foo/bar/baz", false, "bar/baz"));
 	cl_assert_equal_s(conndata.scheme, "https");
 	cl_assert_equal_s(conndata.host, "example.com");
 	cl_assert_equal_s(conndata.port, "443");
@@ -45,9 +45,9 @@ void test_network_url_redirect__redirect_ssl(void)
 void test_network_url_redirect__redirect_leaves_root_path(void)
 {
 	cl_git_pass(git_net_url_parse(&conndata,
-				"https://example.com/foo/bar/baz"));
+		"https://example.com/foo/bar/baz"));
 	cl_git_pass(git_net_url_apply_redirect(&conndata,
-				"https://example.com/foo/bar/baz", "/foo/bar/baz"));
+		"https://example.com/foo/bar/baz", false, "/foo/bar/baz"));
 	cl_assert_equal_s(conndata.scheme, "https");
 	cl_assert_equal_s(conndata.host, "example.com");
 	cl_assert_equal_s(conndata.port, "443");
@@ -59,9 +59,9 @@ void test_network_url_redirect__redirect_leaves_root_path(void)
 void test_network_url_redirect__redirect_encoded_username_password(void)
 {
 	cl_git_pass(git_net_url_parse(&conndata,
-				"https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz"));
+		"https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz"));
 	cl_git_pass(git_net_url_apply_redirect(&conndata,
-				"https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz"));
+		"https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", false, "bar/baz"));
 	cl_assert_equal_s(conndata.scheme, "https");
 	cl_assert_equal_s(conndata.host, "example.com");
 	cl_assert_equal_s(conndata.port, "443");
@@ -70,27 +70,42 @@ void test_network_url_redirect__redirect_encoded_username_password(void)
 	cl_assert_equal_s(conndata.password, "pass@word%zyx%v");
 }
 
+void test_network_url_redirect__redirect_cross_host_allowed(void)
+{
+	cl_git_pass(git_net_url_parse(&conndata,
+		"https://bar.com/bar/baz"));
+	cl_git_pass(git_net_url_apply_redirect(&conndata,
+		"https://foo.com/bar/baz", true, NULL));
+	cl_assert_equal_s(conndata.scheme, "https");
+	cl_assert_equal_s(conndata.host, "foo.com");
+	cl_assert_equal_s(conndata.port, "443");
+	cl_assert_equal_s(conndata.path, "/bar/baz");
+	cl_assert_equal_p(conndata.username, NULL);
+	cl_assert_equal_p(conndata.password, NULL);
+}
+
 void test_network_url_redirect__redirect_cross_host_denied(void)
 {
-	cl_git_pass(git_net_url_parse(&conndata, "https://bar.com/bar/baz"));
+	cl_git_pass(git_net_url_parse(&conndata,
+		"https://bar.com/bar/baz"));
 	cl_git_fail_with(git_net_url_apply_redirect(&conndata,
-				"https://foo.com/bar/baz", NULL),
-			-1);
+		"https://foo.com/bar/baz", false, NULL), -1);
 }
 
 void test_network_url_redirect__redirect_http_downgrade_denied(void)
 {
-	cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz"));
+	cl_git_pass(git_net_url_parse(&conndata,
+		"https://foo.com/bar/baz"));
 	cl_git_fail_with(git_net_url_apply_redirect(&conndata,
-				"http://foo.com/bar/baz", NULL),
-			-1);
+		"http://foo.com/bar/baz", true, NULL), -1);
 }
 
 void test_network_url_redirect__redirect_relative(void)
 {
-	cl_git_pass(git_net_url_parse(&conndata, "http://foo.com/bar/baz/biff"));
+	cl_git_pass(git_net_url_parse(&conndata,
+		"http://foo.com/bar/baz/biff"));
 	cl_git_pass(git_net_url_apply_redirect(&conndata,
-				"/zap/baz/biff?bam", NULL));
+		"/zap/baz/biff?bam", true, NULL));
 	cl_assert_equal_s(conndata.scheme, "http");
 	cl_assert_equal_s(conndata.host, "foo.com");
 	cl_assert_equal_s(conndata.port, "80");
@@ -101,9 +116,10 @@ void test_network_url_redirect__redirect_relative(void)
 
 void test_network_url_redirect__redirect_relative_ssl(void)
 {
-	cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz/biff"));
+	cl_git_pass(git_net_url_parse(&conndata,
+		"https://foo.com/bar/baz/biff"));
 	cl_git_pass(git_net_url_apply_redirect(&conndata,
-				"/zap/baz/biff?bam", NULL));
+		"/zap/baz/biff?bam", true, NULL));
 	cl_assert_equal_s(conndata.scheme, "https");
 	cl_assert_equal_s(conndata.host, "foo.com");
 	cl_assert_equal_s(conndata.port, "443");
@@ -114,16 +130,18 @@ void test_network_url_redirect__redirect_relative_ssl(void)
 
 void test_network_url_redirect__service_query_no_query_params_in_location(void)
 {
-	cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/info/refs?service=git-upload-pack"));
+	cl_git_pass(git_net_url_parse(&conndata,
+		"https://foo.com/bar/info/refs?service=git-upload-pack"));
 	cl_git_pass(git_net_url_apply_redirect(&conndata,
-				"/baz/info/refs", "/info/refs?service=git-upload-pack"));
+		"/baz/info/refs", true, "/info/refs?service=git-upload-pack"));
 	cl_assert_equal_s(conndata.path, "/baz");
 }
 
 void test_network_url_redirect__service_query_with_query_params_in_location(void)
 {
-	cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/info/refs?service=git-upload-pack"));
+	cl_git_pass(git_net_url_parse(&conndata,
+		"https://foo.com/bar/info/refs?service=git-upload-pack"));
 	cl_git_pass(git_net_url_apply_redirect(&conndata,
-				"/baz/info/refs?service=git-upload-pack", "/info/refs?service=git-upload-pack"));
+		"/baz/info/refs?service=git-upload-pack", true, "/info/refs?service=git-upload-pack"));
 	cl_assert_equal_s(conndata.path, "/baz");
 }