Skip to content

Commit a411726

Browse files
committed
Merge branch 'ps/urlmatch-wildcard'
The <url> part in "http.<url>.<variable>" configuration variable can now be spelled with '*' that serves as wildcard. E.g. "http.https://*.example.com.proxy" can be used to specify the proxy used for https://a.example.com, https://b.example.com, etc., i.e. any host in the example.com domain. * ps/urlmatch-wildcard: urlmatch: allow globbing for the URL host part urlmatch: include host in urlmatch ranking urlmatch: split host and port fields in `struct url_info` urlmatch: enable normalization of URLs with globs mailmap: add Patrick Steinhardt's work address
2 parents 74aabf4 + a272b9e commit a411726

File tree

5 files changed

+220
-40
lines changed

5 files changed

+220
-40
lines changed

.mailmap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ Paolo Bonzini <[email protected]> <[email protected]>
177177
178178
179179
180+
Patrick Steinhardt <[email protected]> <[email protected]>
180181
Paul Mackerras <[email protected]> <paulus@dorrigo.(none)>
181182
Paul Mackerras <[email protected]> <paulus@pogo.(none)>
182183

Documentation/config.txt

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

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

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

t/t1300-repo-config.sh

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,111 @@ test_expect_success 'urlmatch' '
11771177
test_cmp expect actual
11781178
'
11791179

1180+
test_expect_success 'urlmatch favors more specific URLs' '
1181+
cat >.git/config <<-\EOF &&
1182+
[http "https://example.com/"]
1183+
cookieFile = /tmp/root.txt
1184+
[http "https://example.com/subdirectory"]
1185+
cookieFile = /tmp/subdirectory.txt
1186+
[http "https://[email protected]/"]
1187+
cookieFile = /tmp/user.txt
1188+
[http "https://[email protected]/"]
1189+
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
1202+
EOF
1203+
1204+
echo http.cookiefile /tmp/root.txt >expect &&
1205+
git config --get-urlmatch HTTP https://example.com >actual &&
1206+
test_cmp expect actual &&
1207+
1208+
echo http.cookiefile /tmp/subdirectory.txt >expect &&
1209+
git config --get-urlmatch HTTP https://example.com/subdirectory >actual &&
1210+
test_cmp expect actual &&
1211+
1212+
echo http.cookiefile /tmp/subdirectory.txt >expect &&
1213+
git config --get-urlmatch HTTP https://example.com/subdirectory/nested >actual &&
1214+
test_cmp expect actual &&
1215+
1216+
echo http.cookiefile /tmp/user.txt >expect &&
1217+
git config --get-urlmatch HTTP https://[email protected]/ >actual &&
1218+
test_cmp expect actual &&
1219+
1220+
echo http.cookiefile /tmp/subdirectory.txt >expect &&
1221+
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 &&
1282+
test_cmp expect actual
1283+
'
1284+
11801285
# good section hygiene
11811286
test_expect_failure 'unsetting the last key in a section removes header' '
11821287
cat >.git/config <<-\EOF &&

urlmatch.c

Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,50 @@ static int append_normalized_escapes(struct strbuf *buf,
6363
return 1;
6464
}
6565

66-
char *url_normalize(const char *url, struct url_info *out_info)
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+
109+
static char *url_normalize_1(const char *url, struct url_info *out_info, char allow_globs)
67110
{
68111
/*
69112
* Normalize NUL-terminated url using the following rules:
@@ -104,7 +147,7 @@ char *url_normalize(const char *url, struct url_info *out_info)
104147
struct strbuf norm;
105148
size_t spanned;
106149
size_t scheme_len, user_off=0, user_len=0, passwd_off=0, passwd_len=0;
107-
size_t host_off=0, host_len=0, port_len=0, path_off, path_len, result_len;
150+
size_t host_off=0, host_len=0, port_off=0, port_len=0, path_off, path_len, result_len;
108151
const char *slash_ptr, *at_ptr, *colon_ptr, *path_start;
109152
char *result;
110153

@@ -191,7 +234,12 @@ char *url_normalize(const char *url, struct url_info *out_info)
191234
strbuf_release(&norm);
192235
return NULL;
193236
}
194-
spanned = strspn(url, URL_HOST_CHARS);
237+
238+
if (allow_globs)
239+
spanned = strspn(url, URL_HOST_CHARS "*");
240+
else
241+
spanned = strspn(url, URL_HOST_CHARS);
242+
195243
if (spanned < colon_ptr - url) {
196244
/* Host name has invalid characters */
197245
if (out_info) {
@@ -258,14 +306,15 @@ char *url_normalize(const char *url, struct url_info *out_info)
258306
return NULL;
259307
}
260308
strbuf_addch(&norm, ':');
309+
port_off = norm.len;
261310
strbuf_add(&norm, url, slash_ptr - url);
262311
port_len = slash_ptr - url;
263312
}
264313
url_len -= slash_ptr - colon_ptr;
265314
url = slash_ptr;
266315
}
267316
if (host_off)
268-
host_len = norm.len - host_off;
317+
host_len = norm.len - host_off - (port_len ? port_len + 1 : 0);
269318

270319

271320
/*
@@ -373,13 +422,19 @@ char *url_normalize(const char *url, struct url_info *out_info)
373422
out_info->passwd_len = passwd_len;
374423
out_info->host_off = host_off;
375424
out_info->host_len = host_len;
425+
out_info->port_off = port_off;
376426
out_info->port_len = port_len;
377427
out_info->path_off = path_off;
378428
out_info->path_len = path_len;
379429
}
380430
return result;
381431
}
382432

433+
char *url_normalize(const char *url, struct url_info *out_info)
434+
{
435+
return url_normalize_1(url, out_info, 0);
436+
}
437+
383438
static size_t url_match_prefix(const char *url,
384439
const char *url_prefix,
385440
size_t url_prefix_len)
@@ -414,7 +469,7 @@ static size_t url_match_prefix(const char *url,
414469

415470
static int match_urls(const struct url_info *url,
416471
const struct url_info *url_prefix,
417-
int *exactusermatch)
472+
struct urlmatch_item *match)
418473
{
419474
/*
420475
* url_prefix matches url if the scheme, host and port of url_prefix
@@ -433,8 +488,8 @@ static int match_urls(const struct url_info *url,
433488
* contained a user name or false if url_prefix did not have a
434489
* user name. If there is no match *exactusermatch is left untouched.
435490
*/
436-
int usermatched = 0;
437-
int pathmatchlen;
491+
char usermatched = 0;
492+
size_t pathmatchlen;
438493

439494
if (!url || !url_prefix || !url->url || !url_prefix->url)
440495
return 0;
@@ -454,33 +509,53 @@ static int match_urls(const struct url_info *url,
454509
usermatched = 1;
455510
}
456511

457-
/* check the host and port */
458-
if (url_prefix->host_len != url->host_len ||
459-
strncmp(url->url + url->host_off,
460-
url_prefix->url + url_prefix->host_off, url->host_len))
461-
return 0; /* host names and/or ports do not match */
512+
/* check the host */
513+
if (!match_host(url, url_prefix))
514+
return 0; /* host names do not match */
515+
516+
/* check the port */
517+
if (url_prefix->port_len != url->port_len ||
518+
strncmp(url->url + url->port_off,
519+
url_prefix->url + url_prefix->port_off, url->port_len))
520+
return 0; /* ports do not match */
462521

463522
/* check the path */
464523
pathmatchlen = url_match_prefix(
465524
url->url + url->path_off,
466525
url_prefix->url + url_prefix->path_off,
467526
url_prefix->url_len - url_prefix->path_off);
527+
if (!pathmatchlen)
528+
return 0; /* paths do not match */
468529

469-
if (pathmatchlen && exactusermatch)
470-
*exactusermatch = usermatched;
471-
return pathmatchlen;
530+
if (match) {
531+
match->hostmatch_len = url_prefix->host_len;
532+
match->pathmatch_len = pathmatchlen;
533+
match->user_matched = usermatched;
534+
}
535+
536+
return 1;
537+
}
538+
539+
static int cmp_matches(const struct urlmatch_item *a,
540+
const struct urlmatch_item *b)
541+
{
542+
if (a->hostmatch_len != b->hostmatch_len)
543+
return a->hostmatch_len < b->hostmatch_len ? -1 : 1;
544+
if (a->pathmatch_len != b->pathmatch_len)
545+
return a->pathmatch_len < b->pathmatch_len ? -1 : 1;
546+
if (a->user_matched != b->user_matched)
547+
return b->user_matched ? -1 : 1;
548+
return 0;
472549
}
473550

474551
int urlmatch_config_entry(const char *var, const char *value, void *cb)
475552
{
476553
struct string_list_item *item;
477554
struct urlmatch_config *collect = cb;
478-
struct urlmatch_item *matched;
555+
struct urlmatch_item matched = {0};
479556
struct url_info *url = &collect->url;
480557
const char *key, *dot;
481558
struct strbuf synthkey = STRBUF_INIT;
482-
size_t matched_len = 0;
483-
int user_matched = 0;
484559
int retval;
485560

486561
if (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {
@@ -494,13 +569,13 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
494569
struct url_info norm_info;
495570

496571
config_url = xmemdupz(key, dot - key);
497-
norm_url = url_normalize(config_url, &norm_info);
572+
norm_url = url_normalize_1(config_url, &norm_info, 1);
498573
free(config_url);
499574
if (!norm_url)
500575
return 0;
501-
matched_len = match_urls(url, &norm_info, &user_matched);
576+
retval = match_urls(url, &norm_info, &matched);
502577
free(norm_url);
503-
if (!matched_len)
578+
if (!retval)
504579
return 0;
505580
key = dot + 1;
506581
}
@@ -510,24 +585,18 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
510585

511586
item = string_list_insert(&collect->vars, key);
512587
if (!item->util) {
513-
matched = xcalloc(1, sizeof(*matched));
514-
item->util = matched;
588+
item->util = xcalloc(1, sizeof(matched));
515589
} else {
516-
matched = item->util;
517-
/*
518-
* Is our match shorter? Is our match the same
519-
* length, and without user while the current
520-
* candidate is with user? Then we cannot use it.
521-
*/
522-
if (matched_len < matched->matched_len ||
523-
((matched_len == matched->matched_len) &&
524-
(!user_matched && matched->user_matched)))
590+
if (cmp_matches(&matched, item->util) < 0)
591+
/*
592+
* Our match is worse than the old one,
593+
* we cannot use it.
594+
*/
525595
return 0;
526596
/* Otherwise, replace it with this one. */
527597
}
528598

529-
matched->matched_len = matched_len;
530-
matched->user_matched = user_matched;
599+
memcpy(item->util, &matched, sizeof(matched));
531600
strbuf_addstr(&synthkey, collect->section);
532601
strbuf_addch(&synthkey, '.');
533602
strbuf_addstr(&synthkey, key);

0 commit comments

Comments
 (0)