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'
This commit is contained in:
Junio C Hamano 2020-08-11 18:04:12 -07:00
commit 73a9255166
5 changed files with 184 additions and 0 deletions

View File

@ -57,6 +57,24 @@ uploadpack.allowFilter::
If this option is set, `upload-pack` will support partial
clone and partial fetch object filtering.
uploadpackfilter.allow::
Provides a default value for unspecified object filters (see: the
below configuration variable).
Defaults to `true`.
uploadpackfilter.<filter>.allow::
Explicitly allow or ban the object filter corresponding to
`<filter>`, where `<filter>` may be one of: `blob:none`,
`blob:limit`, `tree`, `sparse:oid`, or `combine`. If using
combined filters, both `combine` and all of the nested filter
kinds must be allowed. Defaults to `uploadpackfilter.allow`.
uploadpackfilter.tree.maxDepth::
Only allow `--filter=tree=<n>` when `n` is no more than the value of
`uploadpackfilter.tree.maxDepth`. If set, this also implies
`uploadpackfilter.tree.allow=true`, unless this configuration
variable had already been set. Has no effect if unset.
uploadpack.allowRefInWant::
If this option is set, `upload-pack` will support the `ref-in-want`
feature of the protocol version 2 `fetch` command. This feature

View File

@ -15,6 +15,29 @@ static int parse_combine_filter(
const char *arg,
struct strbuf *errbuf);
const char *list_object_filter_config_name(enum list_objects_filter_choice c)
{
switch (c) {
case LOFC_DISABLED:
/* we have no name for "no filter at all" */
break;
case LOFC_BLOB_NONE:
return "blob:none";
case LOFC_BLOB_LIMIT:
return "blob:limit";
case LOFC_TREE_DEPTH:
return "tree";
case LOFC_SPARSE_OID:
return "sparse:oid";
case LOFC_COMBINE:
return "combine";
case LOFC__COUNT:
/* not a real filter type; just the count of all filters */
break;
}
BUG("list_object_filter_choice_name: invalid argument '%d'", c);
}
/*
* Parse value of the argument to the "filter" keyword.
* On the command line this looks like:

View File

@ -17,6 +17,12 @@ enum list_objects_filter_choice {
LOFC__COUNT /* must be last */
};
/*
* Returns a configuration key suitable for describing the given object filter,
* e.g.: "blob:none", "combine", etc.
*/
const char *list_object_filter_config_name(enum list_objects_filter_choice c);
struct list_objects_filter_options {
/*
* 'filter_spec' is the raw argument value given on the command line

View File

@ -235,6 +235,39 @@ test_expect_success 'implicitly construct combine: filter with repeated flags' '
test_cmp unique_types.expected unique_types.actual
'
test_expect_success 'upload-pack fails banned object filters' '
test_config -C srv.bare uploadpackfilter.blob:none.allow false &&
test_must_fail ok=sigpipe git clone --no-checkout --filter=blob:none \
"file://$(pwd)/srv.bare" pc3 2>err &&
test_i18ngrep "filter '\''blob:none'\'' not supported" err
'
test_expect_success 'upload-pack fails banned combine object filters' '
test_config -C srv.bare uploadpackfilter.allow false &&
test_config -C srv.bare uploadpackfilter.combine.allow true &&
test_config -C srv.bare uploadpackfilter.tree.allow true &&
test_config -C srv.bare uploadpackfilter.blob:none.allow false &&
test_must_fail ok=sigpipe git clone --no-checkout --filter=tree:1 \
--filter=blob:none "file://$(pwd)/srv.bare" pc3 2>err &&
test_i18ngrep "filter '\''blob:none'\'' not supported" err
'
test_expect_success 'upload-pack fails banned object filters with fallback' '
test_config -C srv.bare uploadpackfilter.allow false &&
test_must_fail ok=sigpipe git clone --no-checkout --filter=blob:none \
"file://$(pwd)/srv.bare" pc3 2>err &&
test_i18ngrep "filter '\''blob:none'\'' not supported" err
'
test_expect_success 'upload-pack limits tree depth filters' '
test_config -C srv.bare uploadpackfilter.allow false &&
test_config -C srv.bare uploadpackfilter.tree.allow true &&
test_config -C srv.bare uploadpackfilter.tree.maxDepth 0 &&
test_must_fail ok=sigpipe git clone --no-checkout --filter=tree:1 \
"file://$(pwd)/srv.bare" pc3 2>err &&
test_i18ngrep "tree filter allows max depth 0, but got 1" err
'
test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' '
rm -rf src dst &&
git init src &&

View File

@ -88,6 +88,7 @@ struct upload_pack_data {
enum allow_uor allow_uor;
struct list_objects_filter_options filter_options;
struct string_list allowed_filters;
struct packet_writer writer;
@ -103,6 +104,8 @@ struct upload_pack_data {
unsigned no_progress : 1;
unsigned use_include_tag : 1;
unsigned allow_filter : 1;
unsigned allow_filter_fallback : 1;
unsigned long tree_filter_max_depth;
unsigned done : 1; /* v2 only */
unsigned allow_ref_in_want : 1; /* v2 only */
@ -120,6 +123,7 @@ static void upload_pack_data_init(struct upload_pack_data *data)
struct string_list deepen_not = STRING_LIST_INIT_DUP;
struct string_list uri_protocols = STRING_LIST_INIT_DUP;
struct object_array extra_edge_obj = OBJECT_ARRAY_INIT;
struct string_list allowed_filters = STRING_LIST_INIT_DUP;
memset(data, 0, sizeof(*data));
data->symref = symref;
@ -131,6 +135,9 @@ static void upload_pack_data_init(struct upload_pack_data *data)
data->deepen_not = deepen_not;
data->uri_protocols = uri_protocols;
data->extra_edge_obj = extra_edge_obj;
data->allowed_filters = allowed_filters;
data->allow_filter_fallback = 1;
data->tree_filter_max_depth = ULONG_MAX;
packet_writer_init(&data->writer, 1);
data->keepalive = 5;
@ -147,6 +154,7 @@ static void upload_pack_data_clear(struct upload_pack_data *data)
string_list_clear(&data->deepen_not, 0);
object_array_clear(&data->extra_edge_obj);
list_objects_filter_release(&data->filter_options);
string_list_clear(&data->allowed_filters, 1);
free((char *)data->pack_objects_hook);
}
@ -983,6 +991,63 @@ static int process_deepen_not(const char *line, struct string_list *deepen_not,
return 0;
}
NORETURN __attribute__((format(printf,2,3)))
static void send_err_and_die(struct upload_pack_data *data,
const char *fmt, ...)
{
struct strbuf buf = STRBUF_INIT;
va_list ap;
va_start(ap, fmt);
strbuf_vaddf(&buf, fmt, ap);
va_end(ap);
packet_writer_error(&data->writer, "%s", buf.buf);
die("%s", buf.buf);
}
static void check_one_filter(struct upload_pack_data *data,
struct list_objects_filter_options *opts)
{
const char *key = list_object_filter_config_name(opts->choice);
struct string_list_item *item = string_list_lookup(&data->allowed_filters,
key);
int allowed;
if (item)
allowed = (intptr_t)item->util;
else
allowed = data->allow_filter_fallback;
if (!allowed)
send_err_and_die(data, "filter '%s' not supported", key);
if (opts->choice == LOFC_TREE_DEPTH &&
opts->tree_exclude_depth > data->tree_filter_max_depth)
send_err_and_die(data,
"tree filter allows max depth %lu, but got %lu",
data->tree_filter_max_depth,
opts->tree_exclude_depth);
}
static void check_filter_recurse(struct upload_pack_data *data,
struct list_objects_filter_options *opts)
{
size_t i;
check_one_filter(data, opts);
if (opts->choice != LOFC_COMBINE)
return;
for (i = 0; i < opts->sub_nr; i++)
check_filter_recurse(data, &opts->sub[i]);
}
static void die_if_using_banned_filter(struct upload_pack_data *data)
{
check_filter_recurse(data, &data->filter_options);
}
static void receive_needs(struct upload_pack_data *data,
struct packet_reader *reader)
{
@ -1013,6 +1078,7 @@ static void receive_needs(struct upload_pack_data *data,
die("git upload-pack: filtering capability not negotiated");
list_objects_filter_die_if_populated(&data->filter_options);
parse_list_objects_filter(&data->filter_options, arg);
die_if_using_banned_filter(data);
continue;
}
@ -1170,6 +1236,41 @@ static int find_symref(const char *refname, const struct object_id *oid,
return 0;
}
static int parse_object_filter_config(const char *var, const char *value,
struct upload_pack_data *data)
{
struct strbuf buf = STRBUF_INIT;
const char *sub, *key;
size_t sub_len;
if (parse_config_key(var, "uploadpackfilter", &sub, &sub_len, &key))
return 0;
if (!sub) {
if (!strcmp(key, "allow"))
data->allow_filter_fallback = git_config_bool(var, value);
return 0;
}
strbuf_add(&buf, sub, sub_len);
if (!strcmp(key, "allow"))
string_list_insert(&data->allowed_filters, buf.buf)->util =
(void *)(intptr_t)git_config_bool(var, value);
else if (!strcmp(buf.buf, "tree") && !strcmp(key, "maxdepth")) {
if (!value) {
strbuf_release(&buf);
return config_error_nonbool(var);
}
string_list_insert(&data->allowed_filters, buf.buf)->util =
(void *)(intptr_t)1;
data->tree_filter_max_depth = git_config_ulong(var, value);
}
strbuf_release(&buf);
return 0;
}
static int upload_pack_config(const char *var, const char *value, void *cb_data)
{
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)
return git_config_string(&data->pack_objects_hook, var, value);
}
parse_object_filter_config(var, value, data);
return parse_hide_refs_config(var, value, "uploadpack");
}
@ -1389,6 +1492,7 @@ static void process_args(struct packet_reader *request,
if (data->allow_filter && skip_prefix(arg, "filter ", &p)) {
list_objects_filter_die_if_populated(&data->filter_options);
parse_list_objects_filter(&data->filter_options, p);
die_if_using_banned_filter(data);
continue;
}