Skip to content

Commit 46fd7b3

Browse files
bk2204gitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 82eb249 commit 46fd7b3

File tree

7 files changed

+103
-21
lines changed

7 files changed

+103
-21
lines changed

Documentation/gitcredentials.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ context would not match:
131131
because the hostnames differ. Nor would it match `foo.example.com`; Git
132132
compares hostnames exactly, without considering whether two hosts are part of
133133
the same domain. Likewise, a config entry for `http://example.com` would not
134-
match: Git compares the protocols exactly.
134+
match: Git compares the protocols exactly. However, you may use wildcards in
135+
the domain name and other pattern matching techniques as with the `http.<url>.*`
136+
options.
135137

136138
If the "pattern" URL does include a path component, then this too must match
137139
exactly: the context `https://example.com/bar/baz.git` will match a config

credential.c

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "url.h"
77
#include "prompt.h"
88
#include "sigchain.h"
9+
#include "urlmatch.h"
910

1011
void credential_init(struct credential *c)
1112
{
@@ -40,31 +41,14 @@ static int credential_config_callback(const char *var, const char *value,
4041
void *data)
4142
{
4243
struct credential *c = data;
43-
const char *key, *dot;
44+
const char *key;
4445

4546
if (!skip_prefix(var, "credential.", &key))
4647
return 0;
4748

4849
if (!value)
4950
return config_error_nonbool(var);
5051

51-
dot = strrchr(key, '.');
52-
if (dot) {
53-
struct credential want = CREDENTIAL_INIT;
54-
char *url = xmemdupz(key, dot - key);
55-
int matched;
56-
57-
credential_from_url(&want, url);
58-
matched = credential_match(&want, c);
59-
60-
credential_clear(&want);
61-
free(url);
62-
63-
if (!matched)
64-
return 0;
65-
key = dot + 1;
66-
}
67-
6852
if (!strcmp(key, "helper")) {
6953
if (*value)
7054
string_list_append(&c->helpers, value);
@@ -89,11 +73,38 @@ static int proto_is_http(const char *s)
8973
return !strcmp(s, "https") || !strcmp(s, "http");
9074
}
9175

76+
static void credential_describe(struct credential *c, struct strbuf *out);
77+
static void credential_format(struct credential *c, struct strbuf *out);
78+
79+
static int select_all(const struct urlmatch_item *a,
80+
const struct urlmatch_item *b)
81+
{
82+
return 0;
83+
}
84+
9285
static void credential_apply_config(struct credential *c)
9386
{
87+
char *normalized_url;
88+
struct urlmatch_config config = { STRING_LIST_INIT_DUP };
89+
struct strbuf url = STRBUF_INIT;
90+
9491
if (c->configured)
9592
return;
96-
git_config(credential_config_callback, c);
93+
94+
config.section = "credential";
95+
config.key = NULL;
96+
config.collect_fn = credential_config_callback;
97+
config.cascade_fn = NULL;
98+
config.select_fn = select_all;
99+
config.cb = c;
100+
101+
credential_format(c, &url);
102+
normalized_url = url_normalize(url.buf, &config.url);
103+
104+
git_config(urlmatch_config_entry, &config);
105+
free(normalized_url);
106+
strbuf_release(&url);
107+
97108
c->configured = 1;
98109

99110
if (!c->use_http_path && proto_is_http(c->protocol)) {
@@ -114,6 +125,23 @@ static void credential_describe(struct credential *c, struct strbuf *out)
114125
strbuf_addf(out, "/%s", c->path);
115126
}
116127

128+
static void credential_format(struct credential *c, struct strbuf *out)
129+
{
130+
if (!c->protocol)
131+
return;
132+
strbuf_addf(out, "%s://", c->protocol);
133+
if (c->username && *c->username) {
134+
strbuf_add_percentencode(out, c->username);
135+
strbuf_addch(out, '@');
136+
}
137+
if (c->host)
138+
strbuf_addstr(out, c->host);
139+
if (c->path) {
140+
strbuf_addch(out, '/');
141+
strbuf_add_percentencode(out, c->path);
142+
}
143+
}
144+
117145
static char *credential_ask_one(const char *what, struct credential *c,
118146
int flags)
119147
{

strbuf.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,21 @@ void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src)
479479
}
480480
}
481481

482+
#define URL_UNSAFE_CHARS " <>\"%{}|\\^`:/?#[]@!$&'()*+,;="
483+
484+
void strbuf_add_percentencode(struct strbuf *dst, const char *src)
485+
{
486+
size_t i, len = strlen(src);
487+
488+
for (i = 0; i < len; i++) {
489+
unsigned char ch = src[i];
490+
if (ch <= 0x1F || ch >= 0x7F || strchr(URL_UNSAFE_CHARS, ch))
491+
strbuf_addf(dst, "%%%02X", (unsigned char)ch);
492+
else
493+
strbuf_addch(dst, ch);
494+
}
495+
}
496+
482497
size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
483498
{
484499
size_t res;

strbuf.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,12 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb,
366366
*/
367367
void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
368368

369+
/**
370+
* Append the contents of a string to a strbuf, percent-encoding any characters
371+
* that are needed to be encoded for a URL.
372+
*/
373+
void strbuf_add_percentencode(struct strbuf *dst, const char *src);
374+
369375
/**
370376
* Append the given byte size as a human-readable string (i.e. 12.23 KiB,
371377
* 3.50 MiB).

t/t0300-credentials.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,26 @@ test_expect_success 'http paths can be part of context' '
397397
EOF
398398
'
399399

400+
test_expect_success 'context uses urlmatch' '
401+
test_config "credential.https://*.org.useHttpPath" true &&
402+
check fill "verbatim foo bar" <<-\EOF
403+
protocol=https
404+
host=example.org
405+
path=foo.git
406+
--
407+
protocol=https
408+
host=example.org
409+
path=foo.git
410+
username=foo
411+
password=bar
412+
--
413+
verbatim: get
414+
verbatim: protocol=https
415+
verbatim: host=example.org
416+
verbatim: path=foo.git
417+
EOF
418+
'
419+
400420
test_expect_success 'helpers can abort the process' '
401421
test_must_fail git \
402422
-c credential.helper="!f() { echo quit=1; }; f" \

urlmatch.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,8 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
557557
const char *key, *dot;
558558
struct strbuf synthkey = STRBUF_INIT;
559559
int retval;
560+
int (*select_fn)(const struct urlmatch_item *a, const struct urlmatch_item *b) =
561+
collect->select_fn ? collect->select_fn : cmp_matches;
560562

561563
if (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {
562564
if (collect->cascade_fn)
@@ -587,7 +589,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)
587589
if (!item->util) {
588590
item->util = xcalloc(1, sizeof(matched));
589591
} else {
590-
if (cmp_matches(&matched, item->util) < 0)
592+
if (select_fn(&matched, item->util) < 0)
591593
/*
592594
* Our match is worse than the old one,
593595
* we cannot use it.

urlmatch.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ struct urlmatch_config {
5050
void *cb;
5151
int (*collect_fn)(const char *var, const char *value, void *cb);
5252
int (*cascade_fn)(const char *var, const char *value, void *cb);
53+
/*
54+
* Compare the two matches, the one just discovered and the existing
55+
* best match and return a negative value if the found item is to be
56+
* rejected or a non-negative value if it is to be accepted. If this
57+
* field is set to NULL, use the default comparison technique, which
58+
* checks to ses if found is better (according to the urlmatch
59+
* specificity rules) than existing.
60+
*/
61+
int (*select_fn)(const struct urlmatch_item *found, const struct urlmatch_item *existing);
5362
};
5463

5564
int urlmatch_config_entry(const char *var, const char *value, void *cb);

0 commit comments

Comments
 (0)