credential: allow wildcard patterns when matching config
In some cases, a user will want to use a specific credential helper for
a wildcard pattern, such as https://*.corp.example.com. We have code
that handles this already with the urlmatch code, so let's use that
instead of our custom code.
Since the urlmatch code is a superset of our current matching in terms
of capabilities, there shouldn't be any cases of things that matched
previously that don't match now. However, in addition to wildcard
matching, we now use partial path matching, which can cause slightly
different behavior in the case that a helper applies to the prefix
(considering path components) of the remote URL. While different, this
is probably the behavior people were wanting anyway.
Since we're using the urlmatch code, we need to encode the components
we've gotten into a URL to match, so add a function to percent-encode
data and format the URL with it. We now also no longer need to the
custom code to match URLs, so let's remove it.
Additionally, the urlmatch code always looks for the best match, whereas
we want all matches for credential helpers to preserve existing
behavior. Let's add an optional field, select_fn, that lets us control
which items we want (in this case, all of them) and default it to the
best-match code that already exists for other users.
Signed-off-by: brian m. carlson <bk2204@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
brian m. carlson committedFeb 20, 2020 at 02:24 UTC46fd7b390034eb5dde6a3f955e677c5260e4d10c
7 files changed+103-21
Documentation/gitcredentials.txt
+3-1
index ea759fdee5..12cb032352 100644--- a/Documentation/gitcredentials.txt+++ b/Documentation/gitcredentials.txt@@ -131,7 +131,9 @@ context would not match: because the hostnames differ. Nor would it match `foo.example.com`; Git compares hostnames exactly, without considering whether two hosts are part of the same domain. Likewise, a config entry for `http://example.com` would not-match: Git compares the protocols exactly.+match: Git compares the protocols exactly. However, you may use wildcards in+the domain name and other pattern matching techniques as with the `http.<url>.*`+options. If the "pattern" URL does include a path component, then this too must match exactly: the context `https://example.com/bar/baz.git` will match a config
index bfa66569a4..2504ae4ecb 100644--- a/strbuf.h+++ b/strbuf.h@@ -366,6 +366,12 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, */ void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);+/**+ * Append the contents of a string to a strbuf, percent-encoding any characters+ * that are needed to be encoded for a URL.+ */+void strbuf_add_percentencode(struct strbuf *dst, const char *src);+ /** * Append the given byte size as a human-readable string (i.e. 12.23 KiB, * 3.50 MiB).
t/t0300-credentials.sh
+20
index 8f87599056..39f097ea9e 100755--- a/t/t0300-credentials.sh+++ b/t/t0300-credentials.sh@@ -397,6 +397,26 @@ test_expect_success 'http paths can be part of context' ' EOF '+test_expect_success 'context uses urlmatch' '+ test_config "credential.https://*.org.useHttpPath" true &&+ check fill "verbatim foo bar" <<-\EOF+ protocol=https+ host=example.org+ path=foo.git+ --+ protocol=https+ host=example.org+ path=foo.git+ username=foo+ password=bar+ --+ verbatim: get+ verbatim: protocol=https+ verbatim: host=example.org+ verbatim: path=foo.git+ EOF+'+ test_expect_success 'helpers can abort the process' ' test_must_fail git \ -c credential.helper="!f() { echo quit=1; }; f" \
urlmatch.c
+3-1
index 3e42bd7504..29272a5c4f 100644--- a/urlmatch.c+++ b/urlmatch.c@@ -557,6 +557,8 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb) const char *key, *dot; struct strbuf synthkey = STRBUF_INIT; int retval;+ int (*select_fn)(const struct urlmatch_item *a, const struct urlmatch_item *b) =+ collect->select_fn ? collect->select_fn : cmp_matches; if (!skip_prefix(var, collect->section, &key) || *(key++) != '.') { if (collect->cascade_fn)@@ -587,7 +589,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb) if (!item->util) { item->util = xcalloc(1, sizeof(matched)); } else {- if (cmp_matches(&matched, item->util) < 0)+ if (select_fn(&matched, item->util) < 0) /* * Our match is worse than the old one, * we cannot use it.
urlmatch.h
+9
index eed5f29235..2407520731 100644--- a/urlmatch.h+++ b/urlmatch.h@@ -50,6 +50,15 @@ struct urlmatch_config { void *cb; int (*collect_fn)(const char *var, const char *value, void *cb); int (*cascade_fn)(const char *var, const char *value, void *cb);+ /*+ * Compare the two matches, the one just discovered and the existing+ * best match and return a negative value if the found item is to be+ * rejected or a non-negative value if it is to be accepted. If this+ * field is set to NULL, use the default comparison technique, which+ * checks to ses if found is better (according to the urlmatch+ * specificity rules) than existing.+ */+ int (*select_fn)(const struct urlmatch_item *found, const struct urlmatch_item *existing); }; int urlmatch_config_entry(const char *var, const char *value, void *cb);