Skip to content

Commit b2bf236

Browse files
committed
fetch-pack: accept absolute-path packfile URIs
Packfile URIs require a scheme and authority even when the pack and the repository are served by the same HTTP(S) server. Allow the server to advertise an absolute-path reference, as defined by RFC 3986, section 4.2 [1]. Resolve it using the remote's scheme and authority, following section 5.2.2 [2]. For example, /foo.pack from https://example.com/repo.git resolves to https://example.com/foo.pack. Packfile URIs do not support local paths, so there is no ambiguity with a local file named /foo.pack. Add a separate packfile-uris-absolute-path fetch capability. The client declares support only for HTTP(S) remotes whose scheme is allowed by fetch.uriprotocols. This flag does not change the server's URI selection. When uploadpack.blobPackfileUri contains an absolute-path reference, reject packfile-URI requests without the flag before starting pack-objects. Leave fetches that do not request packfile URIs unchanged. Handle only references beginning with a single slash. Redact their paths in packet traces, as we do for absolute URIs. [1] https://www.rfc-editor.org/rfc/rfc3986.html#section-4.2 [2] https://www.rfc-editor.org/rfc/rfc3986.html#section-5.2.2 Signed-off-by: Friel <friel@openai.com>
1 parent 2c3adbb commit b2bf236

9 files changed

Lines changed: 215 additions & 8 deletions

File tree

Documentation/gitprotocol-v2.adoc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,18 @@ can be included in the client's request.
384384
should wait for the client to say "done" before sending the
385385
packfile.
386386
387+
If the 'packfile-uris-absolute-path' feature is advertised, the following
388+
argument can be included in the client's request:
389+
390+
packfile-uris-absolute-path
391+
Indicates that the client can resolve absolute-path references in
392+
the 'packfile-uris' section. The client MUST only send this argument
393+
when the remote uses an HTTP or HTTPS scheme accepted in its
394+
'packfile-uris' request. This declares client support; it does not
395+
request that the server change which URIs it sends. A server that
396+
requires this support MAY reject a packfile-URI request that omits
397+
this argument.
398+
387399
The response of `fetch` is broken into a number of sections separated by
388400
delimiter packets (0001), with each section beginning with its section
389401
header. Most sections are sent only when the packfile is sent.
@@ -493,6 +505,16 @@ header. Most sections are sent only when the packfile is sent.
493505
* For each URI the server sends, it sends a hash of the pack's
494506
contents (as output by git index-pack) followed by the URI.
495507
508+
* If the client sent `packfile-uris-absolute-path`, the server may
509+
send references beginning with a single `/`. These references
510+
inherit the remote URL's scheme and authority, replacing its
511+
path, query, and fragment. For example, `/foo.pack` from
512+
`https://example.com/repo.git` resolves to
513+
`https://example.com/foo.pack`. The server MUST NOT send these
514+
references unless the client sent `packfile-uris-absolute-path`.
515+
References beginning with `//` and relative paths without a
516+
leading `/` are not supported.
517+
496518
* The hashes are 40 hex characters long. When Git upgrades to a new
497519
hash algorithm, this might need to be updated. (It should match
498520
whatever index-pack outputs after "pack\t" or "keep\t".

builtin/fetch-pack.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ int cmd_fetch_pack(int argc,
187187
dest = argv[i++];
188188
else
189189
usage(fetch_pack_usage);
190+
args.url = dest;
190191

191192
/*
192193
* Copy refs from cmdline to growable list, then append any

builtin/pack-objects.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,10 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
18141814
const char *p;
18151815

18161816
if (ex) {
1817+
if (ex->uri[0] == '/' && ex->uri[1] != '/') {
1818+
oidset_insert(&excluded_by_config, oid);
1819+
return 0;
1820+
}
18171821
for (i = 0; i < uri_protocols.nr; i++) {
18181822
if (skip_prefix(ex->uri,
18191823
uri_protocols.items[i].string,

fetch-pack.c

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "mergesort.h"
3838
#include "prio-queue.h"
3939
#include "promisor-remote.h"
40+
#include "urlmatch.h"
4041

4142
static int transfer_unpack_limit = -1;
4243
static int fetch_unpack_limit = -1;
@@ -1375,6 +1376,29 @@ static int add_haves(struct fetch_negotiator *negotiator,
13751376
return haves_added;
13761377
}
13771378

1379+
static char *get_packfile_uri_base(const char *url)
1380+
{
1381+
struct url_info info;
1382+
char *base;
1383+
1384+
if (!url || !uri_protocols.nr)
1385+
return NULL;
1386+
base = url_normalize(url, &info);
1387+
if (!base)
1388+
return NULL;
1389+
1390+
/* An absolute path must not bypass the configured URI protocols. */
1391+
if ((starts_with(base, "http:") &&
1392+
unsorted_string_list_has_string(&uri_protocols, "http")) ||
1393+
(starts_with(base, "https:") &&
1394+
unsorted_string_list_has_string(&uri_protocols, "https"))) {
1395+
base[info.path_off] = '\0';
1396+
return base;
1397+
}
1398+
free(base);
1399+
return NULL;
1400+
}
1401+
13781402
static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
13791403
struct fetch_pack_args *args,
13801404
const struct ref *wants, struct oidset *common,
@@ -1423,6 +1447,13 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
14231447
}
14241448
}
14251449
if (to_send.len) {
1450+
if (server_supports_feature("fetch", "packfile-uris-absolute-path", 0)) {
1451+
char *uri_base = get_packfile_uri_base(args->url);
1452+
1453+
if (uri_base)
1454+
packet_buf_write(&req_buf, "packfile-uris-absolute-path");
1455+
free(uri_base);
1456+
}
14261457
packet_buf_write(&req_buf, "packfile-uris %s",
14271458
to_send.buf);
14281459
strbuf_release(&to_send);
@@ -1639,18 +1670,32 @@ static void receive_wanted_refs(struct packet_reader *reader,
16391670
}
16401671

16411672
static void receive_packfile_uris(struct packet_reader *reader,
1642-
struct string_list *uris)
1673+
struct string_list *uris,
1674+
const char *url)
16431675
{
1676+
char *uri_base = get_packfile_uri_base(url);
1677+
16441678
process_section_header(reader, "packfile-uris", 0);
16451679
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
1646-
if (reader->pktlen < the_hash_algo->hexsz ||
1647-
reader->line[the_hash_algo->hexsz] != ' ')
1680+
struct object_id oid;
1681+
const char *end;
1682+
1683+
if (parse_oid_hex(reader->line, &oid, &end) || *end != ' ')
16481684
die("expected '<hash> <uri>', got: %s", reader->line);
16491685

1650-
string_list_append(uris, reader->line);
1686+
if (end[1] == '/') {
1687+
if (end[2] == '/' || !uri_base)
1688+
die("unexpected relative packfile URI");
1689+
string_list_append_nodup(uris,
1690+
xstrfmt("%s %s%s", oid_to_hex(&oid),
1691+
uri_base, end + 1));
1692+
} else {
1693+
string_list_append(uris, reader->line);
1694+
}
16511695
}
16521696
if (reader->status != PACKET_READ_DELIM)
16531697
die("expected DELIM");
1698+
free(uri_base);
16541699
}
16551700

16561701
enum fetch_state {
@@ -1826,7 +1871,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
18261871
if (git_env_bool("GIT_TRACE_REDACT", 1))
18271872
reader.options |= PACKET_READ_REDACT_URI_PATH;
18281873
if (process_section_header(&reader, "packfile-uris", 1))
1829-
receive_packfile_uris(&reader, &packfile_uris);
1874+
receive_packfile_uris(&reader, &packfile_uris,
1875+
args->url);
18301876
/* We don't expect more URIs. Reset to avoid expensive URI check. */
18311877
reader.options &= ~PACKET_READ_REDACT_URI_PATH;
18321878

fetch-pack.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ struct oid_array;
1010

1111
struct fetch_pack_args {
1212
const char *uploadpack;
13+
/* Remote URL used to resolve absolute-path packfile URIs. */
14+
const char *url;
1315
int unpacklimit;
1416
int depth;
1517
const char *deepen_since;

pkt-line.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,11 @@ static const char *find_packfile_uri_path(const char *buffer)
398398
if (!(len == 40 || len == 64) || buffer[len] != ' ')
399399
return NULL; /* required "<hash>SP" not seen */
400400

401-
path = strstr(buffer + len + 1, URI_MARK);
401+
buffer += len + 1;
402+
if (buffer[0] == '/' && buffer[1] != '/')
403+
return buffer + 1;
404+
405+
path = strstr(buffer, URI_MARK);
402406
if (!path)
403407
return NULL;
404408

t/t5702-protocol-v2.sh

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1217,10 +1217,110 @@ configure_exclusion () {
12171217
git -C "$1" pack-objects "$HTTPD_DOCUMENT_ROOT_PATH/mypack" <objh >packh &&
12181218
git -C "$1" config --add \
12191219
"uploadpack.blobpackfileuri" \
1220-
"$(cat objh) $(cat packh) $HTTPD_URL/dumb/mypack-$(cat packh).pack" &&
1220+
"$(cat objh) $(cat packh) ${3-$HTTPD_URL}/dumb/mypack-$(cat packh).pack" &&
12211221
cat objh
12221222
}
12231223

1224+
test_expect_success 'setup absolute-path packfile URIs' '
1225+
P="$HTTPD_DOCUMENT_ROOT_PATH/absolute-uri" &&
1226+
git init "$P" &&
1227+
git -C "$P" config uploadpack.allowsidebandall true &&
1228+
>absolute-pack-hashes &&
1229+
for name in one two
1230+
do
1231+
test_commit -C "$P" "$name" &&
1232+
configure_exclusion "$P" "$name.t" "" >/dev/null &&
1233+
cat packh >>absolute-pack-hashes || return 1
1234+
done
1235+
'
1236+
1237+
test_expect_success 'clone with absolute-path packfile URIs' '
1238+
test_when_finished "rm -rf absolute-child log" &&
1239+
GIT_TRACE_PACKET="$TRASH_DIRECTORY/log" GIT_TRACE_REDACT=0 \
1240+
GIT_TEST_SIDEBAND_ALL=1 \
1241+
git -c protocol.version=2 -c fetch.uriprotocols=http,https \
1242+
clone "$HTTPD_URL/smart/absolute-uri" absolute-child &&
1243+
while read hash
1244+
do
1245+
test_path_is_file \
1246+
"absolute-child/.git/objects/pack/pack-$hash.pack" &&
1247+
test_grep -F "clone< \\1$hash /dumb/mypack-$hash.pack" log ||
1248+
return 1
1249+
done <absolute-pack-hashes &&
1250+
test_grep "clone< fetch=.*packfile-uris-absolute-path" log &&
1251+
test_grep "clone> packfile-uris http,https$" log &&
1252+
test_grep "clone> packfile-uris-absolute-path$" log &&
1253+
git -C absolute-child fsck
1254+
'
1255+
1256+
test_expect_success 'absolute-path packfile URI fetch redacts the path' '
1257+
test_when_finished "rm -rf absolute-child log" &&
1258+
git init absolute-child &&
1259+
GIT_TRACE_PACKET="$TRASH_DIRECTORY/log" GIT_TEST_SIDEBAND_ALL=1 \
1260+
git -C absolute-child -c protocol.version=2 \
1261+
-c fetch.uriprotocols=http,https \
1262+
fetch "$HTTPD_URL/smart/absolute-uri" &&
1263+
while read hash
1264+
do
1265+
test_grep -F "fetch< \\1$hash /<redacted>" log || return 1
1266+
done <absolute-pack-hashes &&
1267+
test_grep ! /dumb/mypack- log
1268+
'
1269+
1270+
test_expect_success 'absolute-path packfile URIs require an allowed HTTP scheme' '
1271+
test_when_finished "rm -rf absolute-child log err" &&
1272+
case "$HTTPD_PROTO" in
1273+
http) other_protocol=https ;;
1274+
https) other_protocol=http ;;
1275+
esac &&
1276+
for url in "$HTTPD_URL/smart/absolute-uri" \
1277+
"file://$HTTPD_DOCUMENT_ROOT_PATH/absolute-uri"
1278+
do
1279+
test_must_fail env GIT_TRACE_PACKET="$TRASH_DIRECTORY/log" \
1280+
GIT_TEST_SIDEBAND_ALL=1 \
1281+
git -c protocol.version=2 -c fetch.uriprotocols=$other_protocol \
1282+
clone "$url" absolute-child 2>err &&
1283+
test_grep "packfile-uris $other_protocol$" log &&
1284+
test_grep ! "clone> packfile-uris-absolute-path" log &&
1285+
test_grep "client does not support absolute-path packfile URIs" err &&
1286+
rm -rf absolute-child log || return 1
1287+
done
1288+
'
1289+
1290+
test_expect_success 'absolute-path packfile URIs reject an unsupported client before packing' '
1291+
P="$HTTPD_DOCUMENT_ROOT_PATH/absolute-uri" &&
1292+
test_when_finished "rm -f absolute-pack-objects-ran" &&
1293+
write_script "$TRASH_DIRECTORY/absolute-pack-objects-hook" <<-EOF &&
1294+
>"$TRASH_DIRECTORY/absolute-pack-objects-ran"
1295+
exec "\$@"
1296+
EOF
1297+
test_config_global uploadpack.packObjectsHook \
1298+
"$TRASH_DIRECTORY/absolute-pack-objects-hook" &&
1299+
test-tool pkt-line pack >in <<-EOF &&
1300+
command=fetch
1301+
object-format=$(test_oid algo)
1302+
0001
1303+
want $(git -C "$P" rev-parse HEAD)
1304+
sideband-all
1305+
packfile-uris http,https
1306+
done
1307+
0000
1308+
EOF
1309+
test_must_fail env GIT_PROTOCOL=version=2 git -C "$P" \
1310+
upload-pack --stateless-rpc . <in >out 2>err &&
1311+
test_grep "client does not support absolute-path packfile URIs" err &&
1312+
test_path_is_missing absolute-pack-objects-ran
1313+
'
1314+
1315+
test_expect_success 'absolute-path URI configuration does not affect ordinary fetches' '
1316+
test_when_finished "rm -rf absolute-child log" &&
1317+
GIT_TRACE_PACKET="$TRASH_DIRECTORY/log" GIT_TEST_SIDEBAND_ALL=1 \
1318+
git -c protocol.version=2 -c fetch.uriprotocols= \
1319+
clone "$HTTPD_URL/smart/absolute-uri" absolute-child &&
1320+
test_grep ! "clone> packfile-uris" log &&
1321+
git -C absolute-child fsck
1322+
'
1323+
12241324
test_expect_success 'part of packfile response provided as URI' '
12251325
P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
12261326
rm -rf "$P" http_child log &&
@@ -1241,6 +1341,8 @@ test_expect_success 'part of packfile response provided as URI' '
12411341
git -c protocol.version=2 \
12421342
-c fetch.uriprotocols=http,https \
12431343
clone "$HTTPD_URL/smart/http_parent" http_child &&
1344+
test_grep ! "clone< fetch=.*packfile-uris-absolute-path" log &&
1345+
test_grep ! "clone> packfile-uris-absolute-path" log &&
12441346
12451347
# Ensure that my-blob and other-blob are in separate packfiles.
12461348
for idx in http_child/.git/objects/pack/*.idx

transport.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ static int fetch_refs_via_pack(struct transport *transport,
487487

488488
memset(&args, 0, sizeof(args));
489489
args.uploadpack = data->options.uploadpack;
490+
args.url = transport->url;
490491
args.keep_pack = data->options.keep;
491492
args.lock_pack = 1;
492493
args.use_thin_pack = data->options.thin;

upload-pack.c

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ struct upload_pack_data {
119119
unsigned allow_ref_in_want : 1; /* v2 only */
120120
unsigned allow_sideband_all : 1; /* v2 only */
121121
unsigned seen_haves : 1; /* v2 only */
122+
/* At least one uploadpack.blobPackfileUri uses an absolute-path reference. */
123+
unsigned require_absolute_path_uris:1; /* v2 only */
124+
unsigned client_supports_absolute_path_uris:1; /* v2 only */
122125
unsigned allow_packfile_uris : 1; /* v2 only */
123126
unsigned advertise_sid : 1;
124127
unsigned sent_capabilities : 1;
@@ -1364,8 +1367,16 @@ static int upload_pack_config(const char *var, const char *value,
13641367
} else if (!strcmp("uploadpack.allowsidebandall", var)) {
13651368
data->allow_sideband_all = git_config_bool(var, value);
13661369
} else if (!strcmp("uploadpack.blobpackfileuri", var)) {
1367-
if (value)
1370+
if (value) {
1371+
struct object_id oid;
1372+
const char *uri;
1373+
13681374
data->allow_packfile_uris = 1;
1375+
if (!parse_oid_hex(value, &oid, &uri) && *uri == ' ' &&
1376+
!parse_oid_hex(uri + 1, &oid, &uri) && *uri == ' ' &&
1377+
uri[1] == '/' && uri[2] != '/')
1378+
data->require_absolute_path_uris = 1;
1379+
}
13691380
} else if (!strcmp("core.precomposeunicode", var)) {
13701381
cfg->precomposed_unicode = git_config_bool(var, value);
13711382
} else if (!strcmp("transfer.advertisesid", var)) {
@@ -1660,6 +1671,12 @@ static void process_args(struct packet_reader *request,
16601671
continue;
16611672
}
16621673

1674+
if (data->require_absolute_path_uris &&
1675+
!strcmp(arg, "packfile-uris-absolute-path")) {
1676+
data->client_supports_absolute_path_uris = 1;
1677+
continue;
1678+
}
1679+
16631680
if (data->allow_packfile_uris &&
16641681
skip_prefix(arg, "packfile-uris ", &p)) {
16651682
if (data->uri_protocols.nr)
@@ -1679,6 +1696,11 @@ static void process_args(struct packet_reader *request,
16791696
if (request->status != PACKET_READ_FLUSH)
16801697
die(_("expected flush after fetch arguments"));
16811698

1699+
if (data->uri_protocols.nr && data->require_absolute_path_uris &&
1700+
!data->client_supports_absolute_path_uris)
1701+
send_err_and_die(data,
1702+
"client does not support absolute-path packfile URIs");
1703+
16821704
if (trace2_is_enabled())
16831705
trace2_fetch_info(data);
16841706
}
@@ -1852,6 +1874,9 @@ int upload_pack_advertise(struct repository *r,
18521874
if (data.allow_sideband_all)
18531875
strbuf_addstr(value, " sideband-all");
18541876

1877+
if (data.require_absolute_path_uris)
1878+
strbuf_addstr(value, " packfile-uris-absolute-path");
1879+
18551880
if (data.allow_packfile_uris)
18561881
strbuf_addstr(value, " packfile-uris");
18571882
}

0 commit comments

Comments
 (0)