Skip to content

Commit a272b9e

Browse files
Patrick Steinhardtgitster
authored andcommitted
urlmatch: allow globbing for the URL host part
The URL matching function computes for two URLs whether they match not. The match is performed by splitting up the URL into different parts and then doing an exact comparison with the to-be-matched URL. The main user of `urlmatch` is the configuration subsystem. It allows to set certain configurations based on the URL which is being connected to via keys like `http.<url>.*`. A common use case for this is to set proxies for only some remotes which match the given URL. Unfortunately, having exact matches for all parts of the URL can become quite tedious in some setups. Imagine for example a corporate network where there are dozens or even hundreds of subdomains, which would have to be configured individually. Allow users to write an asterisk '*' in place of any 'host' or 'subdomain' label as part of the host name. For example, "http.https://*.example.com.proxy" sets "http.proxy" for all direct subdomains of "https://example.com", e.g. "https://foo.example.com", but not "https://foo.bar.example.com". Signed-off-by: Patrick Steinhardt <[email protected]> Helped-by: Junio C Hamano <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent af99049 commit a272b9e

File tree

3 files changed

+121
-5
lines changed

3 files changed

+121
-5
lines changed

Documentation/config.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1914,7 +1914,10 @@ http.<url>.*::
19141914
must match exactly between the config key and the URL.
19151915

19161916
. Host/domain name (e.g., `example.com` in `https://example.com/`).
1917-
This field must match exactly between the config key and the URL.
1917+
This field must match between the config key and the URL. It is
1918+
possible to specify a `*` as part of the host name to match all subdomains
1919+
at this level. `https://*.example.com/` for example would match
1920+
`https://foo.example.com/`, but not `https://foo.bar.example.com/`.
19181921

19191922
. Port number (e.g., `8080` in `http://example.com:8080/`).
19201923
This field must match exactly between the config key and the URL.

t/t1300-repo-config.sh

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,18 @@ test_expect_success 'urlmatch favors more specific URLs' '
11871187
cookieFile = /tmp/user.txt
11881188
[http "https://[email protected]/"]
11891189
cookieFile = /tmp/averylonguser.txt
1190+
[http "https://preceding.example.com"]
1191+
cookieFile = /tmp/preceding.txt
1192+
[http "https://*.example.com"]
1193+
cookieFile = /tmp/wildcard.txt
1194+
[http "https://*.example.com/wildcardwithsubdomain"]
1195+
cookieFile = /tmp/wildcardwithsubdomain.txt
1196+
[http "https://trailing.example.com"]
1197+
cookieFile = /tmp/trailing.txt
1198+
[http "https://user@*.example.com/"]
1199+
cookieFile = /tmp/wildcardwithuser.txt
1200+
[http "https://sub.example.com/"]
1201+
cookieFile = /tmp/sub.txt
11901202
EOF
11911203
11921204
echo http.cookiefile /tmp/root.txt >expect &&
@@ -1207,6 +1219,66 @@ test_expect_success 'urlmatch favors more specific URLs' '
12071219
12081220
echo http.cookiefile /tmp/subdirectory.txt >expect &&
12091221
git config --get-urlmatch HTTP https://[email protected]/subdirectory >actual &&
1222+
test_cmp expect actual &&
1223+
1224+
echo http.cookiefile /tmp/preceding.txt >expect &&
1225+
git config --get-urlmatch HTTP https://preceding.example.com >actual &&
1226+
test_cmp expect actual &&
1227+
1228+
echo http.cookiefile /tmp/wildcard.txt >expect &&
1229+
git config --get-urlmatch HTTP https://wildcard.example.com >actual &&
1230+
test_cmp expect actual &&
1231+
1232+
echo http.cookiefile /tmp/sub.txt >expect &&
1233+
git config --get-urlmatch HTTP https://sub.example.com/wildcardwithsubdomain >actual &&
1234+
test_cmp expect actual &&
1235+
1236+
echo http.cookiefile /tmp/trailing.txt >expect &&
1237+
git config --get-urlmatch HTTP https://trailing.example.com >actual &&
1238+
test_cmp expect actual &&
1239+
1240+
echo http.cookiefile /tmp/sub.txt >expect &&
1241+
git config --get-urlmatch HTTP https://[email protected] >actual &&
1242+
test_cmp expect actual
1243+
'
1244+
1245+
test_expect_success 'urlmatch with wildcard' '
1246+
cat >.git/config <<-\EOF &&
1247+
[http]
1248+
sslVerify
1249+
[http "https://*.example.com"]
1250+
sslVerify = false
1251+
cookieFile = /tmp/cookie.txt
1252+
EOF
1253+
1254+
test_expect_code 1 git config --bool --get-urlmatch doesnt.exist https://good.example.com >actual &&
1255+
test_must_be_empty actual &&
1256+
1257+
echo true >expect &&
1258+
git config --bool --get-urlmatch http.SSLverify https://example.com >actual &&
1259+
test_cmp expect actual &&
1260+
1261+
echo true >expect &&
1262+
git config --bool --get-urlmatch http.SSLverify https://good-example.com >actual &&
1263+
test_cmp expect actual &&
1264+
1265+
echo true >expect &&
1266+
git config --bool --get-urlmatch http.sslverify https://deep.nested.example.com >actual &&
1267+
test_cmp expect actual &&
1268+
1269+
echo false >expect &&
1270+
git config --bool --get-urlmatch http.sslverify https://good.example.com >actual &&
1271+
test_cmp expect actual &&
1272+
1273+
{
1274+
echo http.cookiefile /tmp/cookie.txt &&
1275+
echo http.sslverify false
1276+
} >expect &&
1277+
git config --get-urlmatch HTTP https://good.example.com >actual &&
1278+
test_cmp expect actual &&
1279+
1280+
echo http.sslverify >expect &&
1281+
git config --get-urlmatch HTTP https://more.example.com.au >actual &&
12101282
test_cmp expect actual
12111283
'
12121284

urlmatch.c

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,49 @@ static int append_normalized_escapes(struct strbuf *buf,
6363
return 1;
6464
}
6565

66+
static const char *end_of_token(const char *s, int c, size_t n)
67+
{
68+
const char *next = memchr(s, c, n);
69+
if (!next)
70+
next = s + n;
71+
return next;
72+
}
73+
74+
static int match_host(const struct url_info *url_info,
75+
const struct url_info *pattern_info)
76+
{
77+
const char *url = url_info->url + url_info->host_off;
78+
const char *pat = pattern_info->url + pattern_info->host_off;
79+
int url_len = url_info->host_len;
80+
int pat_len = pattern_info->host_len;
81+
82+
while (url_len && pat_len) {
83+
const char *url_next = end_of_token(url, '.', url_len);
84+
const char *pat_next = end_of_token(pat, '.', pat_len);
85+
86+
if (pat_next == pat + 1 && pat[0] == '*')
87+
/* wildcard matches anything */
88+
;
89+
else if ((pat_next - pat) == (url_next - url) &&
90+
!memcmp(url, pat, url_next - url))
91+
/* the components are the same */
92+
;
93+
else
94+
return 0; /* found an unmatch */
95+
96+
if (url_next < url + url_len)
97+
url_next++;
98+
url_len -= url_next - url;
99+
url = url_next;
100+
if (pat_next < pat + pat_len)
101+
pat_next++;
102+
pat_len -= pat_next - pat;
103+
pat = pat_next;
104+
}
105+
106+
return (!url_len && !pat_len);
107+
}
108+
66109
static char *url_normalize_1(const char *url, struct url_info *out_info, char allow_globs)
67110
{
68111
/*
@@ -467,9 +510,7 @@ static int match_urls(const struct url_info *url,
467510
}
468511

469512
/* check the host */
470-
if (url_prefix->host_len != url->host_len ||
471-
strncmp(url->url + url->host_off,
472-
url_prefix->url + url_prefix->host_off, url->host_len))
513+
if (!match_host(url, url_prefix))
473514
return 0; /* host names do not match */
474515

475516
/* check the port */
@@ -528,7 +569,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
528569
struct url_info norm_info;
529570

530571
config_url = xmemdupz(key, dot - key);
531-
norm_url = url_normalize(config_url, &norm_info);
572+
norm_url = url_normalize_1(config_url, &norm_info, 1);
532573
free(config_url);
533574
if (!norm_url)
534575
return 0;

0 commit comments

Comments
 (0)