Skip to content

Commit 5176602

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 an "absolute-path" token to the packfile-uris protocol list. Request it only for HTTP(S) remotes whose scheme is allowed by fetch.uriprotocols. Without the token, keep the corresponding objects in the inline pack so older clients can still fetch them. 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 5176602

8 files changed

Lines changed: 144 additions & 11 deletions

File tree

Documentation/gitprotocol-v2.adoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,17 @@ header. Most sections are sent only when the packfile is sent.
493493
* For each URI the server sends, it sends a hash of the pack's
494494
contents (as output by git index-pack) followed by the URI.
495495
496+
* The client may include `absolute-path` in its `packfile-uris`
497+
list to accept references beginning with a single `/`. These
498+
inherit the remote URL's scheme and authority, replacing its
499+
path, query, and fragment. For example, `/foo.pack` from
500+
`https://example.com/repo.git` resolves to
501+
`https://example.com/foo.pack`. The client MUST only request
502+
`absolute-path` when the remote uses an accepted HTTP or HTTPS
503+
scheme. The server MUST NOT send these references unless the
504+
client requests `absolute-path`. References beginning with `//`
505+
and relative paths without a leading `/` are not supported.
506+
496507
* The hashes are 40 hex characters long. When Git upgrades to a new
497508
hash algorithm, this might need to be updated. (It should match
498509
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: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,10 +1815,11 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
18151815

18161816
if (ex) {
18171817
for (i = 0; i < uri_protocols.nr; i++) {
1818-
if (skip_prefix(ex->uri,
1819-
uri_protocols.items[i].string,
1820-
&p) &&
1821-
*p == ':') {
1818+
const char *protocol = uri_protocols.items[i].string;
1819+
1820+
if ((ex->uri[0] == '/' && ex->uri[1] != '/' &&
1821+
!strcmp(protocol, "absolute-path")) ||
1822+
(skip_prefix(ex->uri, protocol, &p) && *p == ':')) {
18221823
oidset_insert(&excluded_by_config, oid);
18231824
return 0;
18241825
}

fetch-pack.c

Lines changed: 49 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,11 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
14231447
}
14241448
}
14251449
if (to_send.len) {
1450+
char *uri_base = get_packfile_uri_base(args->url);
1451+
1452+
if (uri_base)
1453+
strbuf_addstr(&to_send, ",absolute-path");
1454+
free(uri_base);
14261455
packet_buf_write(&req_buf, "packfile-uris %s",
14271456
to_send.buf);
14281457
strbuf_release(&to_send);
@@ -1639,18 +1668,32 @@ static void receive_wanted_refs(struct packet_reader *reader,
16391668
}
16401669

16411670
static void receive_packfile_uris(struct packet_reader *reader,
1642-
struct string_list *uris)
1671+
struct string_list *uris,
1672+
const char *url)
16431673
{
1674+
char *uri_base = get_packfile_uri_base(url);
1675+
16441676
process_section_header(reader, "packfile-uris", 0);
16451677
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
1646-
if (reader->pktlen < the_hash_algo->hexsz ||
1647-
reader->line[the_hash_algo->hexsz] != ' ')
1678+
struct object_id oid;
1679+
const char *end;
1680+
1681+
if (parse_oid_hex(reader->line, &oid, &end) || *end != ' ')
16481682
die("expected '<hash> <uri>', got: %s", reader->line);
16491683

1650-
string_list_append(uris, reader->line);
1684+
if (end[1] == '/') {
1685+
if (end[2] == '/' || !uri_base)
1686+
die("unexpected relative packfile URI");
1687+
string_list_append_nodup(uris,
1688+
xstrfmt("%s %s%s", oid_to_hex(&oid),
1689+
uri_base, end + 1));
1690+
} else {
1691+
string_list_append(uris, reader->line);
1692+
}
16511693
}
16521694
if (reader->status != PACKET_READ_DELIM)
16531695
die("expected DELIM");
1696+
free(uri_base);
16541697
}
16551698

16561699
enum fetch_state {
@@ -1826,7 +1869,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
18261869
if (git_env_bool("GIT_TRACE_REDACT", 1))
18271870
reader.options |= PACKET_READ_REDACT_URI_PATH;
18281871
if (process_section_header(&reader, "packfile-uris", 1))
1829-
receive_packfile_uris(&reader, &packfile_uris);
1872+
receive_packfile_uris(&reader, &packfile_uris,
1873+
args->url);
18301874
/* We don't expect more URIs. Reset to avoid expensive URI check. */
18311875
reader.options &= ~PACKET_READ_REDACT_URI_PATH;
18321876

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: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1217,10 +1217,79 @@ 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 "packfile-uris http,https,absolute-path" log &&
1251+
git -C absolute-child fsck
1252+
'
1253+
1254+
test_expect_success 'absolute-path packfile URI fetch redacts the path' '
1255+
test_when_finished "rm -rf absolute-child log" &&
1256+
git init absolute-child &&
1257+
GIT_TRACE_PACKET="$TRASH_DIRECTORY/log" GIT_TEST_SIDEBAND_ALL=1 \
1258+
git -C absolute-child -c protocol.version=2 \
1259+
-c fetch.uriprotocols=http,https \
1260+
fetch "$HTTPD_URL/smart/absolute-uri" &&
1261+
while read hash
1262+
do
1263+
test_grep -F "fetch< \\1$hash /<redacted>" log || return 1
1264+
done <absolute-pack-hashes &&
1265+
test_grep ! /dumb/mypack- log
1266+
'
1267+
1268+
test_expect_success 'absolute-path packfile URIs require an allowed HTTP scheme' '
1269+
test_when_finished "rm -rf absolute-child log" &&
1270+
case "$HTTPD_PROTO" in
1271+
http) other_protocol=https ;;
1272+
https) other_protocol=http ;;
1273+
esac &&
1274+
for url in "$HTTPD_URL/smart/absolute-uri" \
1275+
"file://$HTTPD_DOCUMENT_ROOT_PATH/absolute-uri"
1276+
do
1277+
GIT_TRACE_PACKET="$TRASH_DIRECTORY/log" GIT_TEST_SIDEBAND_ALL=1 \
1278+
git -c protocol.version=2 -c fetch.uriprotocols=$other_protocol \
1279+
clone "$url" absolute-child &&
1280+
test_grep "packfile-uris $other_protocol$" log &&
1281+
test_grep ! "packfile-uris .*absolute-path" log &&
1282+
while read hash
1283+
do
1284+
test_path_is_missing \
1285+
"absolute-child/.git/objects/pack/pack-$hash.pack" ||
1286+
return 1
1287+
done <absolute-pack-hashes &&
1288+
git -C absolute-child fsck &&
1289+
rm -rf absolute-child log || return 1
1290+
done
1291+
'
1292+
12241293
test_expect_success 'part of packfile response provided as URI' '
12251294
P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
12261295
rm -rf "$P" http_child log &&

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;

0 commit comments

Comments
 (0)