Skip to content

Commit a31ea47

Browse files
committed
promisor-remote: check advertised name or URL
A previous commit introduced a "promisor.acceptFromServer" configuration variable with only "None" or "All" as valid values. Let's introduce "KnownName" and "KnownUrl" as valid values for this configuration option to give more choice to a client about which promisor remotes it might accept among those that the server advertised. In case of "KnownName", the client will accept promisor remotes which are already configured on the client and have the same name as those advertised by the client. This could be useful in a corporate setup where servers and clients are trusted to not switch names and URLs, but where some kind of control is still useful. In case of "KnownUrl", the client will accept promisor remotes which have both the same name and the same URL configured on the client as the name and URL advertised by the server. This is the most secure option, so it should be used if possible. Signed-off-by: Christian Couder <[email protected]>
1 parent f324c12 commit a31ea47

File tree

3 files changed

+138
-12
lines changed

3 files changed

+138
-12
lines changed

Documentation/config/promisor.txt

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,19 @@ promisor.advertise::
1212
promisor.acceptFromServer::
1313
If set to "all", a client will accept all the promisor remotes
1414
a server might advertise using the "promisor-remote"
15-
capability. Default is "none", which means no promisor remote
16-
advertised by a server will be accepted. By accepting a
17-
promisor remote, the client agrees that the server might omit
18-
objects that are lazily fetchable from this promisor remote
19-
from its responses to "fetch" and "clone" requests from the
20-
client. See linkgit:gitprotocol-v2[5].
15+
capability. If set to "knownName" the client will accept
16+
promisor remotes which are already configured on the client
17+
and have the same name as those advertised by the client. This
18+
is not very secure, but could be used in a corporate setup
19+
where servers and clients are trusted to not switch name and
20+
URLs. If set to "knownUrl", the client will accept promisor
21+
remotes which have both the same name and the same URL
22+
configured on the client as the name and URL advertised by the
23+
server. This is more secure than "all" or "knownUrl", so it
24+
should be used if possible instead of those options. Default
25+
is "none", which means no promisor remote advertised by a
26+
server will be accepted. By accepting a promisor remote, the
27+
client agrees that the server might omit objects that are
28+
lazily fetchable from this promisor remote from its responses
29+
to "fetch" and "clone" requests from the client. See
30+
linkgit:gitprotocol-v2[5].

promisor-remote.c

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -369,30 +369,73 @@ char *promisor_remote_info(struct repository *repo)
369369
return strbuf_detach(&sb, NULL);
370370
}
371371

372+
/*
373+
* Find first index of 'vec' where there is 'val'. 'val' is compared
374+
* case insensively to the strings in 'vec'. If not found 'vec->nr' is
375+
* returned.
376+
*/
377+
static size_t strvec_find_index(struct strvec *vec, const char *val)
378+
{
379+
for (size_t i = 0; i < vec->nr; i++)
380+
if (!strcasecmp(vec->v[i], val))
381+
return i;
382+
return vec->nr;
383+
}
384+
372385
enum accept_promisor {
373386
ACCEPT_NONE = 0,
387+
ACCEPT_KNOWN_URL,
388+
ACCEPT_KNOWN_NAME,
374389
ACCEPT_ALL
375390
};
376391

377392
static int should_accept_remote(enum accept_promisor accept,
378-
const char *remote_name UNUSED,
379-
const char *remote_url UNUSED)
393+
const char *remote_name, const char *remote_url,
394+
struct strvec *names, struct strvec *urls)
380395
{
396+
size_t i;
397+
381398
if (accept == ACCEPT_ALL)
382399
return 1;
383400

384-
BUG("Unhandled 'enum accept_promisor' value '%d'", accept);
401+
i = strvec_find_index(names, remote_name);
402+
403+
if (i >= names->nr)
404+
/* We don't know about that remote */
405+
return 0;
406+
407+
if (accept == ACCEPT_KNOWN_NAME)
408+
return 1;
409+
410+
if (accept != ACCEPT_KNOWN_URL)
411+
BUG("Unhandled 'enum accept_promisor' value '%d'", accept);
412+
413+
if (!strcasecmp(urls->v[i], remote_url))
414+
return 1;
415+
416+
warning(_("known remote named '%s' but with url '%s' instead of '%s'"),
417+
remote_name, urls->v[i], remote_url);
418+
419+
return 0;
385420
}
386421

387-
static void filter_promisor_remote(struct strvec *accepted, const char *info)
422+
static void filter_promisor_remote(struct repository *repo,
423+
struct strvec *accepted,
424+
const char *info)
388425
{
389426
struct strbuf **remotes;
390427
char *accept_str;
391428
enum accept_promisor accept = ACCEPT_NONE;
429+
struct strvec names = STRVEC_INIT;
430+
struct strvec urls = STRVEC_INIT;
392431

393432
if (!git_config_get_string("promisor.acceptfromserver", &accept_str)) {
394433
if (!accept_str || !*accept_str || !strcasecmp("None", accept_str))
395434
accept = ACCEPT_NONE;
435+
else if (!strcasecmp("KnownUrl", accept_str))
436+
accept = ACCEPT_KNOWN_URL;
437+
else if (!strcasecmp("KnownName", accept_str))
438+
accept = ACCEPT_KNOWN_NAME;
396439
else if (!strcasecmp("All", accept_str))
397440
accept = ACCEPT_ALL;
398441
else
@@ -403,6 +446,9 @@ static void filter_promisor_remote(struct strvec *accepted, const char *info)
403446
if (accept == ACCEPT_NONE)
404447
return;
405448

449+
if (accept != ACCEPT_ALL)
450+
promisor_info_vecs(repo, &names, &urls);
451+
406452
/* Parse remote info received */
407453

408454
remotes = strbuf_split_str(info, ';', 0);
@@ -432,7 +478,7 @@ static void filter_promisor_remote(struct strvec *accepted, const char *info)
432478
if (remote_url)
433479
decoded_url = url_percent_decode(remote_url);
434480

435-
if (decoded_name && should_accept_remote(accept, decoded_name, decoded_url))
481+
if (decoded_name && should_accept_remote(accept, decoded_name, decoded_url, &names, &urls))
436482
strvec_push(accepted, decoded_name);
437483

438484
strbuf_list_free(elems);
@@ -441,6 +487,8 @@ static void filter_promisor_remote(struct strvec *accepted, const char *info)
441487
}
442488

443489
free(accept_str);
490+
strvec_clear(&names);
491+
strvec_clear(&urls);
444492
strbuf_list_free(remotes);
445493
}
446494

@@ -449,7 +497,7 @@ char *promisor_remote_reply(const char *info)
449497
struct strvec accepted = STRVEC_INIT;
450498
struct strbuf reply = STRBUF_INIT;
451499

452-
filter_promisor_remote(&accepted, info);
500+
filter_promisor_remote(the_repository, &accepted, info);
453501

454502
if (!accepted.nr)
455503
return NULL;

t/t5710-promisor-remote-capability.sh

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,74 @@ test_expect_success "init + fetch with promisor.advertise set to 'true'" '
149149
check_missing_objects server 1 "$oid"
150150
'
151151

152+
test_expect_success "clone with promisor.acceptfromserver set to 'KnownName'" '
153+
git -C server config promisor.advertise true &&
154+
155+
# Clone from server to create a client
156+
GIT_NO_LAZY_FETCH=0 git clone -c remote.server2.promisor=true \
157+
-c remote.server2.fetch="+refs/heads/*:refs/remotes/server2/*" \
158+
-c remote.server2.url="file://$(pwd)/server2" \
159+
-c promisor.acceptfromserver=KnownName \
160+
--no-local --filter="blob:limit=5k" server client &&
161+
test_when_finished "rm -rf client" &&
162+
163+
# Check that the largest object is still missing on the server
164+
check_missing_objects server 1 "$oid"
165+
'
166+
167+
test_expect_success "clone with 'KnownName' and different remote names" '
168+
git -C server config promisor.advertise true &&
169+
170+
# Clone from server to create a client
171+
GIT_NO_LAZY_FETCH=0 git clone -c remote.serverTwo.promisor=true \
172+
-c remote.serverTwo.fetch="+refs/heads/*:refs/remotes/server2/*" \
173+
-c remote.serverTwo.url="file://$(pwd)/server2" \
174+
-c promisor.acceptfromserver=KnownName \
175+
--no-local --filter="blob:limit=5k" server client &&
176+
test_when_finished "rm -rf client" &&
177+
178+
# Check that the largest object is not missing on the server
179+
check_missing_objects server 0 "" &&
180+
181+
# Reinitialize server so that the largest object is missing again
182+
initialize_server 1 "$oid"
183+
'
184+
185+
test_expect_success "clone with promisor.acceptfromserver set to 'KnownUrl'" '
186+
git -C server config promisor.advertise true &&
187+
188+
# Clone from server to create a client
189+
GIT_NO_LAZY_FETCH=0 git clone -c remote.server2.promisor=true \
190+
-c remote.server2.fetch="+refs/heads/*:refs/remotes/server2/*" \
191+
-c remote.server2.url="file://$(pwd)/server2" \
192+
-c promisor.acceptfromserver=KnownUrl \
193+
--no-local --filter="blob:limit=5k" server client &&
194+
test_when_finished "rm -rf client" &&
195+
196+
# Check that the largest object is still missing on the server
197+
check_missing_objects server 1 "$oid"
198+
'
199+
200+
test_expect_success "clone with 'KnownUrl' and different remote urls" '
201+
ln -s server2 serverTwo &&
202+
203+
git -C server config promisor.advertise true &&
204+
205+
# Clone from server to create a client
206+
GIT_NO_LAZY_FETCH=0 git clone -c remote.server2.promisor=true \
207+
-c remote.server2.fetch="+refs/heads/*:refs/remotes/server2/*" \
208+
-c remote.server2.url="file://$(pwd)/serverTwo" \
209+
-c promisor.acceptfromserver=KnownUrl \
210+
--no-local --filter="blob:limit=5k" server client &&
211+
test_when_finished "rm -rf client" &&
212+
213+
# Check that the largest object is not missing on the server
214+
check_missing_objects server 0 "" &&
215+
216+
# Reinitialize server so that the largest object is missing again
217+
initialize_server 1 "$oid"
218+
'
219+
152220
test_expect_success "clone with promisor.advertise set to 'true' but don't delete the client" '
153221
git -C server config promisor.advertise true &&
154222

0 commit comments

Comments
 (0)