Skip to content

Commit 73a9255

Browse files
committed
Merge branch 'tb/upload-pack-filters'
The component to respond to "git fetch" request is made more configurable to selectively allow or reject object filtering specification used for partial cloning. * tb/upload-pack-filters: t5616: use test_i18ngrep for upload-pack errors upload-pack.c: introduce 'uploadpackfilter.tree.maxDepth' upload-pack.c: allow banning certain object filter(s) list_objects_filter_options: introduce 'list_object_filter_config_name'
2 parents a3afa4b + 6cc275e commit 73a9255

File tree

5 files changed

+184
-0
lines changed

5 files changed

+184
-0
lines changed

Documentation/config/uploadpack.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ uploadpack.allowFilter::
5757
If this option is set, `upload-pack` will support partial
5858
clone and partial fetch object filtering.
5959

60+
uploadpackfilter.allow::
61+
Provides a default value for unspecified object filters (see: the
62+
below configuration variable).
63+
Defaults to `true`.
64+
65+
uploadpackfilter.<filter>.allow::
66+
Explicitly allow or ban the object filter corresponding to
67+
`<filter>`, where `<filter>` may be one of: `blob:none`,
68+
`blob:limit`, `tree`, `sparse:oid`, or `combine`. If using
69+
combined filters, both `combine` and all of the nested filter
70+
kinds must be allowed. Defaults to `uploadpackfilter.allow`.
71+
72+
uploadpackfilter.tree.maxDepth::
73+
Only allow `--filter=tree=<n>` when `n` is no more than the value of
74+
`uploadpackfilter.tree.maxDepth`. If set, this also implies
75+
`uploadpackfilter.tree.allow=true`, unless this configuration
76+
variable had already been set. Has no effect if unset.
77+
6078
uploadpack.allowRefInWant::
6179
If this option is set, `upload-pack` will support the `ref-in-want`
6280
feature of the protocol version 2 `fetch` command. This feature

list-objects-filter-options.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,29 @@ static int parse_combine_filter(
1515
const char *arg,
1616
struct strbuf *errbuf);
1717

18+
const char *list_object_filter_config_name(enum list_objects_filter_choice c)
19+
{
20+
switch (c) {
21+
case LOFC_DISABLED:
22+
/* we have no name for "no filter at all" */
23+
break;
24+
case LOFC_BLOB_NONE:
25+
return "blob:none";
26+
case LOFC_BLOB_LIMIT:
27+
return "blob:limit";
28+
case LOFC_TREE_DEPTH:
29+
return "tree";
30+
case LOFC_SPARSE_OID:
31+
return "sparse:oid";
32+
case LOFC_COMBINE:
33+
return "combine";
34+
case LOFC__COUNT:
35+
/* not a real filter type; just the count of all filters */
36+
break;
37+
}
38+
BUG("list_object_filter_choice_name: invalid argument '%d'", c);
39+
}
40+
1841
/*
1942
* Parse value of the argument to the "filter" keyword.
2043
* On the command line this looks like:

list-objects-filter-options.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ enum list_objects_filter_choice {
1717
LOFC__COUNT /* must be last */
1818
};
1919

20+
/*
21+
* Returns a configuration key suitable for describing the given object filter,
22+
* e.g.: "blob:none", "combine", etc.
23+
*/
24+
const char *list_object_filter_config_name(enum list_objects_filter_choice c);
25+
2026
struct list_objects_filter_options {
2127
/*
2228
* 'filter_spec' is the raw argument value given on the command line

t/t5616-partial-clone.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,39 @@ test_expect_success 'implicitly construct combine: filter with repeated flags' '
235235
test_cmp unique_types.expected unique_types.actual
236236
'
237237

238+
test_expect_success 'upload-pack fails banned object filters' '
239+
test_config -C srv.bare uploadpackfilter.blob:none.allow false &&
240+
test_must_fail ok=sigpipe git clone --no-checkout --filter=blob:none \
241+
"file://$(pwd)/srv.bare" pc3 2>err &&
242+
test_i18ngrep "filter '\''blob:none'\'' not supported" err
243+
'
244+
245+
test_expect_success 'upload-pack fails banned combine object filters' '
246+
test_config -C srv.bare uploadpackfilter.allow false &&
247+
test_config -C srv.bare uploadpackfilter.combine.allow true &&
248+
test_config -C srv.bare uploadpackfilter.tree.allow true &&
249+
test_config -C srv.bare uploadpackfilter.blob:none.allow false &&
250+
test_must_fail ok=sigpipe git clone --no-checkout --filter=tree:1 \
251+
--filter=blob:none "file://$(pwd)/srv.bare" pc3 2>err &&
252+
test_i18ngrep "filter '\''blob:none'\'' not supported" err
253+
'
254+
255+
test_expect_success 'upload-pack fails banned object filters with fallback' '
256+
test_config -C srv.bare uploadpackfilter.allow false &&
257+
test_must_fail ok=sigpipe git clone --no-checkout --filter=blob:none \
258+
"file://$(pwd)/srv.bare" pc3 2>err &&
259+
test_i18ngrep "filter '\''blob:none'\'' not supported" err
260+
'
261+
262+
test_expect_success 'upload-pack limits tree depth filters' '
263+
test_config -C srv.bare uploadpackfilter.allow false &&
264+
test_config -C srv.bare uploadpackfilter.tree.allow true &&
265+
test_config -C srv.bare uploadpackfilter.tree.maxDepth 0 &&
266+
test_must_fail ok=sigpipe git clone --no-checkout --filter=tree:1 \
267+
"file://$(pwd)/srv.bare" pc3 2>err &&
268+
test_i18ngrep "tree filter allows max depth 0, but got 1" err
269+
'
270+
238271
test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' '
239272
rm -rf src dst &&
240273
git init src &&

upload-pack.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ struct upload_pack_data {
8888
enum allow_uor allow_uor;
8989

9090
struct list_objects_filter_options filter_options;
91+
struct string_list allowed_filters;
9192

9293
struct packet_writer writer;
9394

@@ -103,6 +104,8 @@ struct upload_pack_data {
103104
unsigned no_progress : 1;
104105
unsigned use_include_tag : 1;
105106
unsigned allow_filter : 1;
107+
unsigned allow_filter_fallback : 1;
108+
unsigned long tree_filter_max_depth;
106109

107110
unsigned done : 1; /* v2 only */
108111
unsigned allow_ref_in_want : 1; /* v2 only */
@@ -120,6 +123,7 @@ static void upload_pack_data_init(struct upload_pack_data *data)
120123
struct string_list deepen_not = STRING_LIST_INIT_DUP;
121124
struct string_list uri_protocols = STRING_LIST_INIT_DUP;
122125
struct object_array extra_edge_obj = OBJECT_ARRAY_INIT;
126+
struct string_list allowed_filters = STRING_LIST_INIT_DUP;
123127

124128
memset(data, 0, sizeof(*data));
125129
data->symref = symref;
@@ -131,6 +135,9 @@ static void upload_pack_data_init(struct upload_pack_data *data)
131135
data->deepen_not = deepen_not;
132136
data->uri_protocols = uri_protocols;
133137
data->extra_edge_obj = extra_edge_obj;
138+
data->allowed_filters = allowed_filters;
139+
data->allow_filter_fallback = 1;
140+
data->tree_filter_max_depth = ULONG_MAX;
134141
packet_writer_init(&data->writer, 1);
135142

136143
data->keepalive = 5;
@@ -147,6 +154,7 @@ static void upload_pack_data_clear(struct upload_pack_data *data)
147154
string_list_clear(&data->deepen_not, 0);
148155
object_array_clear(&data->extra_edge_obj);
149156
list_objects_filter_release(&data->filter_options);
157+
string_list_clear(&data->allowed_filters, 1);
150158

151159
free((char *)data->pack_objects_hook);
152160
}
@@ -983,6 +991,63 @@ static int process_deepen_not(const char *line, struct string_list *deepen_not,
983991
return 0;
984992
}
985993

994+
NORETURN __attribute__((format(printf,2,3)))
995+
static void send_err_and_die(struct upload_pack_data *data,
996+
const char *fmt, ...)
997+
{
998+
struct strbuf buf = STRBUF_INIT;
999+
va_list ap;
1000+
1001+
va_start(ap, fmt);
1002+
strbuf_vaddf(&buf, fmt, ap);
1003+
va_end(ap);
1004+
1005+
packet_writer_error(&data->writer, "%s", buf.buf);
1006+
die("%s", buf.buf);
1007+
}
1008+
1009+
static void check_one_filter(struct upload_pack_data *data,
1010+
struct list_objects_filter_options *opts)
1011+
{
1012+
const char *key = list_object_filter_config_name(opts->choice);
1013+
struct string_list_item *item = string_list_lookup(&data->allowed_filters,
1014+
key);
1015+
int allowed;
1016+
1017+
if (item)
1018+
allowed = (intptr_t)item->util;
1019+
else
1020+
allowed = data->allow_filter_fallback;
1021+
1022+
if (!allowed)
1023+
send_err_and_die(data, "filter '%s' not supported", key);
1024+
1025+
if (opts->choice == LOFC_TREE_DEPTH &&
1026+
opts->tree_exclude_depth > data->tree_filter_max_depth)
1027+
send_err_and_die(data,
1028+
"tree filter allows max depth %lu, but got %lu",
1029+
data->tree_filter_max_depth,
1030+
opts->tree_exclude_depth);
1031+
}
1032+
1033+
static void check_filter_recurse(struct upload_pack_data *data,
1034+
struct list_objects_filter_options *opts)
1035+
{
1036+
size_t i;
1037+
1038+
check_one_filter(data, opts);
1039+
if (opts->choice != LOFC_COMBINE)
1040+
return;
1041+
1042+
for (i = 0; i < opts->sub_nr; i++)
1043+
check_filter_recurse(data, &opts->sub[i]);
1044+
}
1045+
1046+
static void die_if_using_banned_filter(struct upload_pack_data *data)
1047+
{
1048+
check_filter_recurse(data, &data->filter_options);
1049+
}
1050+
9861051
static void receive_needs(struct upload_pack_data *data,
9871052
struct packet_reader *reader)
9881053
{
@@ -1013,6 +1078,7 @@ static void receive_needs(struct upload_pack_data *data,
10131078
die("git upload-pack: filtering capability not negotiated");
10141079
list_objects_filter_die_if_populated(&data->filter_options);
10151080
parse_list_objects_filter(&data->filter_options, arg);
1081+
die_if_using_banned_filter(data);
10161082
continue;
10171083
}
10181084

@@ -1170,6 +1236,41 @@ static int find_symref(const char *refname, const struct object_id *oid,
11701236
return 0;
11711237
}
11721238

1239+
static int parse_object_filter_config(const char *var, const char *value,
1240+
struct upload_pack_data *data)
1241+
{
1242+
struct strbuf buf = STRBUF_INIT;
1243+
const char *sub, *key;
1244+
size_t sub_len;
1245+
1246+
if (parse_config_key(var, "uploadpackfilter", &sub, &sub_len, &key))
1247+
return 0;
1248+
1249+
if (!sub) {
1250+
if (!strcmp(key, "allow"))
1251+
data->allow_filter_fallback = git_config_bool(var, value);
1252+
return 0;
1253+
}
1254+
1255+
strbuf_add(&buf, sub, sub_len);
1256+
1257+
if (!strcmp(key, "allow"))
1258+
string_list_insert(&data->allowed_filters, buf.buf)->util =
1259+
(void *)(intptr_t)git_config_bool(var, value);
1260+
else if (!strcmp(buf.buf, "tree") && !strcmp(key, "maxdepth")) {
1261+
if (!value) {
1262+
strbuf_release(&buf);
1263+
return config_error_nonbool(var);
1264+
}
1265+
string_list_insert(&data->allowed_filters, buf.buf)->util =
1266+
(void *)(intptr_t)1;
1267+
data->tree_filter_max_depth = git_config_ulong(var, value);
1268+
}
1269+
1270+
strbuf_release(&buf);
1271+
return 0;
1272+
}
1273+
11731274
static int upload_pack_config(const char *var, const char *value, void *cb_data)
11741275
{
11751276
struct upload_pack_data *data = cb_data;
@@ -1209,6 +1310,8 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)
12091310
return git_config_string(&data->pack_objects_hook, var, value);
12101311
}
12111312

1313+
parse_object_filter_config(var, value, data);
1314+
12121315
return parse_hide_refs_config(var, value, "uploadpack");
12131316
}
12141317

@@ -1389,6 +1492,7 @@ static void process_args(struct packet_reader *request,
13891492
if (data->allow_filter && skip_prefix(arg, "filter ", &p)) {
13901493
list_objects_filter_die_if_populated(&data->filter_options);
13911494
parse_list_objects_filter(&data->filter_options, p);
1495+
die_if_using_banned_filter(data);
13921496
continue;
13931497
}
13941498

0 commit comments

Comments
 (0)