Skip to content

Commit 979a0af

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 652ce32 commit 979a0af

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
@@ -370,30 +370,73 @@ char *promisor_remote_info(struct repository *repo)
370370
return strbuf_detach(&sb, NULL);
371371
}
372372

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

378393
static int should_accept_remote(enum accept_promisor accept,
379-
const char *remote_name UNUSED,
380-
const char *remote_url UNUSED)
394+
const char *remote_name, const char *remote_url,
395+
struct strvec *names, struct strvec *urls)
381396
{
397+
size_t i;
398+
382399
if (accept == ACCEPT_ALL)
383400
return 1;
384401

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

388-
static void filter_promisor_remote(struct strvec *accepted, const char *info)
423+
static void filter_promisor_remote(struct repository *repo,
424+
struct strvec *accepted,
425+
const char *info)
389426
{
390427
struct strbuf **remotes;
391428
const char *accept_str;
392429
enum accept_promisor accept = ACCEPT_NONE;
430+
struct strvec names = STRVEC_INIT;
431+
struct strvec urls = STRVEC_INIT;
393432

394433
if (!git_config_get_string_tmp("promisor.acceptfromserver", &accept_str)) {
395434
if (!accept_str || !*accept_str || !strcasecmp("None", accept_str))
396435
accept = ACCEPT_NONE;
436+
else if (!strcasecmp("KnownUrl", accept_str))
437+
accept = ACCEPT_KNOWN_URL;
438+
else if (!strcasecmp("KnownName", accept_str))
439+
accept = ACCEPT_KNOWN_NAME;
397440
else if (!strcasecmp("All", accept_str))
398441
accept = ACCEPT_ALL;
399442
else
@@ -404,6 +447,9 @@ static void filter_promisor_remote(struct strvec *accepted, const char *info)
404447
if (accept == ACCEPT_NONE)
405448
return;
406449

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

409455
remotes = strbuf_split_str(info, ';', 0);
@@ -433,14 +479,16 @@ static void filter_promisor_remote(struct strvec *accepted, const char *info)
433479
if (remote_url)
434480
decoded_url = url_percent_decode(remote_url);
435481

436-
if (decoded_name && should_accept_remote(accept, decoded_name, decoded_url))
482+
if (decoded_name && should_accept_remote(accept, decoded_name, decoded_url, &names, &urls))
437483
strvec_push(accepted, decoded_name);
438484

439485
strbuf_list_free(elems);
440486
free(decoded_name);
441487
free(decoded_url);
442488
}
443489

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
@@ -160,6 +160,74 @@ test_expect_success "init + fetch with promisor.advertise set to 'true'" '
160160
check_missing_objects server 1 "$oid"
161161
'
162162

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

0 commit comments

Comments
 (0)