Commit 9e98e443ca5e95bfb570536187c6e155bc126e59

Edward Thomson 2021-08-31T22:01:34

url: introduce `git_net_url_matches_pattern_list` Provide a utility method on a url to determine if it matches any pattern in a comma-separated list, similar to what one would find in `NO_PROXY` environment variables.

diff --git a/src/net.c b/src/net.c
index f3cca5d..3322f68 100644
--- a/src/net.c
+++ b/src/net.c
@@ -459,6 +459,25 @@ bool git_net_url_matches_pattern(git_net_url *url, const char *pattern)
 	return matches_pattern(url, pattern, strlen(pattern));
 }
 
+bool git_net_url_matches_pattern_list(
+	git_net_url *url,
+	const char *pattern_list)
+{
+	const char *pattern, *pattern_end, *sep;
+
+	for (pattern = pattern_list;
+	     pattern && *pattern;
+	     pattern = sep ? sep + 1 : NULL) {
+		sep = strchr(pattern, ',');
+		pattern_end = sep ? sep : strchr(pattern, '\0');
+
+		if (matches_pattern(url, pattern, (pattern_end - pattern)))
+			return true;
+	}
+
+	return false;
+}
+
 void git_net_url_dispose(git_net_url *url)
 {
 	if (url->username)
diff --git a/src/net.h b/src/net.h
index 4d4c7c7..971e002 100644
--- a/src/net.h
+++ b/src/net.h
@@ -58,6 +58,9 @@ extern int git_net_url_fmt_path(git_buf *buf, git_net_url *url);
 extern bool git_net_url_matches_pattern(
 	git_net_url *url,
 	const char *pattern);
+extern bool git_net_url_matches_pattern_list(
+	git_net_url *url,
+	const char *pattern_list);
 
 /** Disposes the contents of the structure. */
 extern void git_net_url_dispose(git_net_url *url);
diff --git a/tests/network/url/pattern.c b/tests/network/url/pattern.c
index fbe1f9e..5e4495f 100644
--- a/tests/network/url/pattern.c
+++ b/tests/network/url/pattern.c
@@ -52,3 +52,52 @@ void test_network_url_pattern__single(void)
 		git_net_url_dispose(&url);
 	}
 }
+
+void test_network_url_pattern__list(void)
+{
+	git_net_url url;
+	size_t i;
+
+	struct url_pattern url_patterns[] = {
+		/* Wildcard matches */
+		{ "https://example.com/", "", false },
+		{ "https://example.com/", "*", true },
+		{ "https://example.com/", ",example.com,", true },
+		{ "https://example.com/", "foo,,example.com,,bar", true },
+		{ "https://example.com/", "foo,,zzz,,*,,bar", true },
+
+		/* Literals */
+		{ "https://example.com/", "example.com", true },
+		{ "https://example.com/", "foo.bar,example.com", true },
+		{ "https://example.com/", "foo.bar", false },
+		{ "https://example.com/", "foo.bar,example.org", false },
+		{ "https://www.example.com/", "foo.example.com,www.example.com,bar.example.com", true },
+		{ "https://www.example.com/", "foo.example.com,baz.example.com,bar.example.com", false },
+		{ "https://foo.example.com/", "www.example.com", false },
+		{ "https://foo.example.com/", "bar.example.com,www.example.com,", false },
+
+		/* Wildcards */
+		{ "https://example.com/", ".example.com", true },
+		{ "https://example.com/", "*.example.com", true },
+		{ "https://example.com/", "foo.com,bar.com,.example.com", true },
+		{ "https://example.com/", ".foo.com,.bar.com,.example.com", true },
+		{ "https://example.com/", ".foo.com,.bar.com,asdf.com", false },
+		{ "https://example.com/", "*.foo,*.bar,*.example.com,*.asdf", true },
+		{ "https://example.com/", "*.foo,*.bar,*.asdf", false },
+
+
+		/* Ports! */
+		{ "https://example.com/", "example.com:443", true },
+		{ "https://example.com/", "example.com:42,example.com:443,example.com:99", true },
+		{ "https://example.com/", "example.com:42,example.com:80,example.org:443", false },
+		{ "https://example.com:1443/", "example.com", true },
+		{ "https://example.com:44/", "example.com:443", false },
+		{ "https://example.com:443/", "example.com:44", false },
+	};
+
+	for (i = 0; i < ARRAY_SIZE(url_patterns); i++) {
+		cl_git_pass(git_net_url_parse(&url, url_patterns[i].url));
+		cl_assert_(git_net_url_matches_pattern_list(&url, url_patterns[i].pattern) == url_patterns[i].matches, url_patterns[i].pattern);
+		git_net_url_dispose(&url);
+	}
+}