diff options
-rw-r--r-- | .mailmap | 1 | ||||
-rw-r--r-- | Documentation/config.txt | 5 | ||||
-rwxr-xr-x | t/t1300-repo-config.sh | 105 | ||||
-rw-r--r-- | urlmatch.c | 137 | ||||
-rw-r--r-- | urlmatch.h | 12 |
5 files changed, 220 insertions, 40 deletions
@@ -177,6 +177,7 @@ Paolo Bonzini <bonzini@gnu.org> <paolo.bonzini@lu.unisi.ch> Pascal Obry <pascal@obry.net> <pascal.obry@gmail.com> Pascal Obry <pascal@obry.net> <pascal.obry@wanadoo.fr> Pat Notz <patnotz@gmail.com> <pknotz@sandia.gov> +Patrick Steinhardt <ps@pks.im> <patrick.steinhardt@elego.de> Paul Mackerras <paulus@samba.org> <paulus@dorrigo.(none)> Paul Mackerras <paulus@samba.org> <paulus@pogo.(none)> Peter Baumann <waste.manager@gmx.de> <Peter.B.Baumann@stud.informatik.uni-erlangen.de> diff --git a/Documentation/config.txt b/Documentation/config.txt index 015346c417..10351a2100 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1919,7 +1919,10 @@ http.<url>.*:: must match exactly between the config key and the URL. . Host/domain name (e.g., `example.com` in `https://example.com/`). - This field must match exactly between the config key and the URL. + This field must match between the config key and the URL. It is + possible to specify a `*` as part of the host name to match all subdomains + at this level. `https://*.example.com/` for example would match + `https://foo.example.com/`, but not `https://foo.bar.example.com/`. . Port number (e.g., `8080` in `http://example.com:8080/`). This field must match exactly between the config key and the URL. diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 923bfc5a26..052f120216 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -1177,6 +1177,111 @@ test_expect_success 'urlmatch' ' test_cmp expect actual ' +test_expect_success 'urlmatch favors more specific URLs' ' + cat >.git/config <<-\EOF && + [http "https://example.com/"] + cookieFile = /tmp/root.txt + [http "https://example.com/subdirectory"] + cookieFile = /tmp/subdirectory.txt + [http "https://user@example.com/"] + cookieFile = /tmp/user.txt + [http "https://averylonguser@example.com/"] + cookieFile = /tmp/averylonguser.txt + [http "https://preceding.example.com"] + cookieFile = /tmp/preceding.txt + [http "https://*.example.com"] + cookieFile = /tmp/wildcard.txt + [http "https://*.example.com/wildcardwithsubdomain"] + cookieFile = /tmp/wildcardwithsubdomain.txt + [http "https://trailing.example.com"] + cookieFile = /tmp/trailing.txt + [http "https://user@*.example.com/"] + cookieFile = /tmp/wildcardwithuser.txt + [http "https://sub.example.com/"] + cookieFile = /tmp/sub.txt + EOF + + echo http.cookiefile /tmp/root.txt >expect && + git config --get-urlmatch HTTP https://example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/subdirectory.txt >expect && + git config --get-urlmatch HTTP https://example.com/subdirectory >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/subdirectory.txt >expect && + git config --get-urlmatch HTTP https://example.com/subdirectory/nested >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/user.txt >expect && + git config --get-urlmatch HTTP https://user@example.com/ >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/subdirectory.txt >expect && + git config --get-urlmatch HTTP https://averylonguser@example.com/subdirectory >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/preceding.txt >expect && + git config --get-urlmatch HTTP https://preceding.example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/wildcard.txt >expect && + git config --get-urlmatch HTTP https://wildcard.example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/sub.txt >expect && + git config --get-urlmatch HTTP https://sub.example.com/wildcardwithsubdomain >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/trailing.txt >expect && + git config --get-urlmatch HTTP https://trailing.example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/sub.txt >expect && + git config --get-urlmatch HTTP https://user@sub.example.com >actual && + test_cmp expect actual +' + +test_expect_success 'urlmatch with wildcard' ' + cat >.git/config <<-\EOF && + [http] + sslVerify + [http "https://*.example.com"] + sslVerify = false + cookieFile = /tmp/cookie.txt + EOF + + test_expect_code 1 git config --bool --get-urlmatch doesnt.exist https://good.example.com >actual && + test_must_be_empty actual && + + echo true >expect && + git config --bool --get-urlmatch http.SSLverify https://example.com >actual && + test_cmp expect actual && + + echo true >expect && + git config --bool --get-urlmatch http.SSLverify https://good-example.com >actual && + test_cmp expect actual && + + echo true >expect && + git config --bool --get-urlmatch http.sslverify https://deep.nested.example.com >actual && + test_cmp expect actual && + + echo false >expect && + git config --bool --get-urlmatch http.sslverify https://good.example.com >actual && + test_cmp expect actual && + + { + echo http.cookiefile /tmp/cookie.txt && + echo http.sslverify false + } >expect && + git config --get-urlmatch HTTP https://good.example.com >actual && + test_cmp expect actual && + + echo http.sslverify >expect && + git config --get-urlmatch HTTP https://more.example.com.au >actual && + test_cmp expect actual +' + # good section hygiene test_expect_failure 'unsetting the last key in a section removes header' ' cat >.git/config <<-\EOF && diff --git a/urlmatch.c b/urlmatch.c index 132d342bc1..4bbde924e8 100644 --- a/urlmatch.c +++ b/urlmatch.c @@ -63,7 +63,50 @@ static int append_normalized_escapes(struct strbuf *buf, return 1; } -char *url_normalize(const char *url, struct url_info *out_info) +static const char *end_of_token(const char *s, int c, size_t n) +{ + const char *next = memchr(s, c, n); + if (!next) + next = s + n; + return next; +} + +static int match_host(const struct url_info *url_info, + const struct url_info *pattern_info) +{ + const char *url = url_info->url + url_info->host_off; + const char *pat = pattern_info->url + pattern_info->host_off; + int url_len = url_info->host_len; + int pat_len = pattern_info->host_len; + + while (url_len && pat_len) { + const char *url_next = end_of_token(url, '.', url_len); + const char *pat_next = end_of_token(pat, '.', pat_len); + + if (pat_next == pat + 1 && pat[0] == '*') + /* wildcard matches anything */ + ; + else if ((pat_next - pat) == (url_next - url) && + !memcmp(url, pat, url_next - url)) + /* the components are the same */ + ; + else + return 0; /* found an unmatch */ + + if (url_next < url + url_len) + url_next++; + url_len -= url_next - url; + url = url_next; + if (pat_next < pat + pat_len) + pat_next++; + pat_len -= pat_next - pat; + pat = pat_next; + } + + return (!url_len && !pat_len); +} + +static char *url_normalize_1(const char *url, struct url_info *out_info, char allow_globs) { /* * Normalize NUL-terminated url using the following rules: @@ -104,7 +147,7 @@ char *url_normalize(const char *url, struct url_info *out_info) struct strbuf norm; size_t spanned; size_t scheme_len, user_off=0, user_len=0, passwd_off=0, passwd_len=0; - size_t host_off=0, host_len=0, port_len=0, path_off, path_len, result_len; + size_t host_off=0, host_len=0, port_off=0, port_len=0, path_off, path_len, result_len; const char *slash_ptr, *at_ptr, *colon_ptr, *path_start; char *result; @@ -191,7 +234,12 @@ char *url_normalize(const char *url, struct url_info *out_info) strbuf_release(&norm); return NULL; } - spanned = strspn(url, URL_HOST_CHARS); + + if (allow_globs) + spanned = strspn(url, URL_HOST_CHARS "*"); + else + spanned = strspn(url, URL_HOST_CHARS); + if (spanned < colon_ptr - url) { /* Host name has invalid characters */ if (out_info) { @@ -258,6 +306,7 @@ char *url_normalize(const char *url, struct url_info *out_info) return NULL; } strbuf_addch(&norm, ':'); + port_off = norm.len; strbuf_add(&norm, url, slash_ptr - url); port_len = slash_ptr - url; } @@ -265,7 +314,7 @@ char *url_normalize(const char *url, struct url_info *out_info) url = slash_ptr; } if (host_off) - host_len = norm.len - host_off; + host_len = norm.len - host_off - (port_len ? port_len + 1 : 0); /* @@ -373,6 +422,7 @@ char *url_normalize(const char *url, struct url_info *out_info) out_info->passwd_len = passwd_len; out_info->host_off = host_off; out_info->host_len = host_len; + out_info->port_off = port_off; out_info->port_len = port_len; out_info->path_off = path_off; out_info->path_len = path_len; @@ -380,6 +430,11 @@ char *url_normalize(const char *url, struct url_info *out_info) return result; } +char *url_normalize(const char *url, struct url_info *out_info) +{ + return url_normalize_1(url, out_info, 0); +} + static size_t url_match_prefix(const char *url, const char *url_prefix, size_t url_prefix_len) @@ -414,7 +469,7 @@ static size_t url_match_prefix(const char *url, static int match_urls(const struct url_info *url, const struct url_info *url_prefix, - int *exactusermatch) + struct urlmatch_item *match) { /* * 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, * contained a user name or false if url_prefix did not have a * user name. If there is no match *exactusermatch is left untouched. */ - int usermatched = 0; - int pathmatchlen; + char usermatched = 0; + size_t pathmatchlen; if (!url || !url_prefix || !url->url || !url_prefix->url) return 0; @@ -454,33 +509,53 @@ static int match_urls(const struct url_info *url, usermatched = 1; } - /* check the host and port */ - if (url_prefix->host_len != url->host_len || - strncmp(url->url + url->host_off, - url_prefix->url + url_prefix->host_off, url->host_len)) - return 0; /* host names and/or ports do not match */ + /* check the host */ + if (!match_host(url, url_prefix)) + return 0; /* host names do not match */ + + /* check the port */ + if (url_prefix->port_len != url->port_len || + strncmp(url->url + url->port_off, + url_prefix->url + url_prefix->port_off, url->port_len)) + return 0; /* ports do not match */ /* check the path */ pathmatchlen = url_match_prefix( url->url + url->path_off, url_prefix->url + url_prefix->path_off, url_prefix->url_len - url_prefix->path_off); + if (!pathmatchlen) + return 0; /* paths do not match */ - if (pathmatchlen && exactusermatch) - *exactusermatch = usermatched; - return pathmatchlen; + if (match) { + match->hostmatch_len = url_prefix->host_len; + match->pathmatch_len = pathmatchlen; + match->user_matched = usermatched; + } + + return 1; +} + +static int cmp_matches(const struct urlmatch_item *a, + const struct urlmatch_item *b) +{ + if (a->hostmatch_len != b->hostmatch_len) + return a->hostmatch_len < b->hostmatch_len ? -1 : 1; + if (a->pathmatch_len != b->pathmatch_len) + return a->pathmatch_len < b->pathmatch_len ? -1 : 1; + if (a->user_matched != b->user_matched) + return b->user_matched ? -1 : 1; + return 0; } int urlmatch_config_entry(const char *var, const char *value, void *cb) { struct string_list_item *item; struct urlmatch_config *collect = cb; - struct urlmatch_item *matched; + struct urlmatch_item matched = {0}; struct url_info *url = &collect->url; const char *key, *dot; struct strbuf synthkey = STRBUF_INIT; - size_t matched_len = 0; - int user_matched = 0; int retval; if (!skip_prefix(var, collect->section, &key) || *(key++) != '.') { @@ -494,13 +569,13 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb) struct url_info norm_info; config_url = xmemdupz(key, dot - key); - norm_url = url_normalize(config_url, &norm_info); + norm_url = url_normalize_1(config_url, &norm_info, 1); free(config_url); if (!norm_url) return 0; - matched_len = match_urls(url, &norm_info, &user_matched); + retval = match_urls(url, &norm_info, &matched); free(norm_url); - if (!matched_len) + if (!retval) return 0; key = dot + 1; } @@ -510,24 +585,18 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb) item = string_list_insert(&collect->vars, key); if (!item->util) { - matched = xcalloc(1, sizeof(*matched)); - item->util = matched; + item->util = xcalloc(1, sizeof(matched)); } else { - matched = item->util; - /* - * Is our match shorter? Is our match the same - * length, and without user while the current - * candidate is with user? Then we cannot use it. - */ - if (matched_len < matched->matched_len || - ((matched_len == matched->matched_len) && - (!user_matched && matched->user_matched))) + if (cmp_matches(&matched, item->util) < 0) + /* + * Our match is worse than the old one, + * we cannot use it. + */ return 0; /* Otherwise, replace it with this one. */ } - matched->matched_len = matched_len; - matched->user_matched = user_matched; + memcpy(item->util, &matched, sizeof(matched)); strbuf_addstr(&synthkey, collect->section); strbuf_addch(&synthkey, '.'); strbuf_addstr(&synthkey, key); diff --git a/urlmatch.h b/urlmatch.h index 528862adc5..37ee5da85e 100644 --- a/urlmatch.h +++ b/urlmatch.h @@ -18,11 +18,12 @@ struct url_info { size_t passwd_len; /* length of passwd; if passwd_off != 0 but passwd_len == 0, an empty passwd was given */ size_t host_off; /* offset into url to start of host name (0 => none) */ - size_t host_len; /* length of host name; this INCLUDES any ':portnum'; + size_t host_len; /* length of host name; * file urls may have host_len == 0 */ - size_t port_len; /* if a portnum is present (port_len != 0), it has - * this length (excluding the leading ':') at the - * end of the host name (always 0 for file urls) */ + size_t port_off; /* offset into url to start of port number (0 => none) */ + size_t port_len; /* if a portnum is present (port_off != 0), it has + * this length (excluding the leading ':') starting + * from port_off (always 0 for file urls) */ size_t path_off; /* offset into url to the start of the url path; * this will always point to a '/' character * after the url has been normalized */ @@ -33,7 +34,8 @@ struct url_info { extern char *url_normalize(const char *, struct url_info *); struct urlmatch_item { - size_t matched_len; + size_t hostmatch_len; + size_t pathmatch_len; char user_matched; }; |