diff --git a/fuzzers/config_file_fuzzer.c b/fuzzers/config_file_fuzzer.c index 890adbfc5..763036960 100644 --- a/fuzzers/config_file_fuzzer.c +++ b/fuzzers/config_file_fuzzer.c @@ -43,7 +43,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) goto out; } - if ((err = git_config_backend_from_string(&backend, (const char*)data, size)) != 0) { + if ((err = git_config_backend_from_string(&backend, (const char*)data, size, NULL)) != 0) { goto out; } if ((err = git_config_add_backend(cfg, backend, 0, NULL, 0)) != 0) { diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h index 0a9005e35..75d20758b 100644 --- a/include/git2/sys/config.h +++ b/include/git2/sys/config.h @@ -125,6 +125,57 @@ GIT_EXTERN(int) git_config_add_backend( const git_repository *repo, int force); +/** Options for in-memory configuration backends. */ +typedef struct { + unsigned int version; + + /** + * The type of this backend (eg, "command line"). If this is + * NULL, then this will be "in-memory". + */ + const char *backend_type; + + /** + * The path to the origin; if this is NULL then it will be + * left unset in the resulting configuration entries. + */ + const char *origin_path; +} git_config_backend_memory_options; + +#define GIT_CONFIG_BACKEND_MEMORY_OPTIONS_VERSION 1 +#define GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT { GIT_CONFIG_BACKEND_MEMORY_OPTIONS_VERSION } + + +/** + * Create an in-memory configuration backend from a string in standard + * git configuration file format. + * + * @param out the new backend + * @param cfg the configuration that is to be parsed + * @param len the length of the string pointed to by `cfg` + * @param opts the options to initialize this backend with, or NULL + */ +extern int git_config_backend_from_string( + git_config_backend **out, + const char *cfg, + size_t len, + git_config_backend_memory_options *opts); + +/** + * Create an in-memory configuration backend from a list of name/value + * pairs. + * + * @param out the new backend + * @param values the configuration values to set (in "key=value" format) + * @param len the length of the values array + * @param opts the options to initialize this backend with, or NULL + */ +extern int git_config_backend_from_values( + git_config_backend **out, + const char **values, + size_t len, + git_config_backend_memory_options *opts); + /** @} */ GIT_END_DECL #endif diff --git a/src/libgit2/config_backend.h b/src/libgit2/config_backend.h index 677b2236f..37d25abe1 100644 --- a/src/libgit2/config_backend.h +++ b/src/libgit2/config_backend.h @@ -37,21 +37,6 @@ extern int git_config_backend_from_file(git_config_backend **out, const char *pa */ extern int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source); -/** - * Create an in-memory configuration file backend from a string in standard - * git configuration file format. - * - * @param out the new backend - * @param origin the name of the origin to use (or NULL for "memory") - * @param cfg the configuration that is to be parsed - * @param len the length of the string pointed to by `cfg` - */ -extern int git_config_backend_from_string( - git_config_backend **out, - const char *origin, - const char *cfg, - size_t len); - GIT_INLINE(int) git_config_backend_open(git_config_backend *cfg, unsigned int level, const git_repository *repo) { return cfg->open(cfg, level, repo); diff --git a/src/libgit2/config_mem.c b/src/libgit2/config_mem.c index 8faa53dcb..406aa83e6 100644 --- a/src/libgit2/config_mem.c +++ b/src/libgit2/config_mem.c @@ -10,16 +10,27 @@ #include "config_backend.h" #include "config_parse.h" #include "config_list.h" +#include "strlist.h" typedef struct { git_config_backend parent; - char *type; + + char *backend_type; + char *origin_path; + git_config_list *config_list; + + /* Configuration data in the config file format */ git_str cfg; + + /* Array of key=value pairs */ + char **values; + size_t values_len; } config_memory_backend; typedef struct { const char *backend_type; + const char *origin_path; git_config_list *config_list; git_config_level_t level; } config_memory_parse_data; @@ -71,6 +82,7 @@ static int read_variable_cb( entry->base.level = parse_data->level; entry->base.include_depth = 0; entry->base.backend_type = parse_data->backend_type; + entry->base.origin_path = parse_data->origin_path; entry->base.free = git_config_list_entry_free; entry->config_list = parse_data->config_list; @@ -80,25 +92,29 @@ static int read_variable_cb( return result; } -static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo) +static int parse_config( + config_memory_backend *memory_backend, + git_config_level_t level) { - config_memory_backend *memory_backend = (config_memory_backend *) backend; git_config_parser parser = GIT_PARSE_CTX_INIT; config_memory_parse_data parse_data; int error; - GIT_UNUSED(repo); - - if ((error = git_config_parser_init(&parser, "in-memory", memory_backend->cfg.ptr, - memory_backend->cfg.size)) < 0) + if ((error = git_config_parser_init(&parser, "in-memory", + memory_backend->cfg.ptr, memory_backend->cfg.size)) < 0) goto out; parse_data.backend_type = git_config_list_add_string( - memory_backend->config_list, memory_backend->type); + memory_backend->config_list, memory_backend->backend_type); + parse_data.origin_path = memory_backend->origin_path ? + git_config_list_add_string(memory_backend->config_list, + memory_backend->origin_path) : + NULL; parse_data.config_list = memory_backend->config_list; parse_data.level = level; - if ((error = git_config_parse(&parser, NULL, read_variable_cb, NULL, NULL, &parse_data)) < 0) + if ((error = git_config_parse(&parser, NULL, read_variable_cb, + NULL, NULL, &parse_data)) < 0) goto out; out: @@ -106,6 +122,74 @@ out: return error; } +static int parse_values( + config_memory_backend *memory_backend, + git_config_level_t level) +{ + git_config_list_entry *entry; + const char *eql, *backend_type, *origin_path; + size_t name_len, i; + + backend_type = git_config_list_add_string( + memory_backend->config_list, memory_backend->backend_type); + GIT_ERROR_CHECK_ALLOC(backend_type); + + origin_path = memory_backend->origin_path ? + git_config_list_add_string(memory_backend->config_list, + memory_backend->origin_path) : + NULL; + + for (i = 0; i < memory_backend->values_len; i++) { + eql = strchr(memory_backend->values[i], '='); + name_len = eql - memory_backend->values[i]; + + if (name_len == 0) { + git_error_set(GIT_ERROR_CONFIG, "empty config key"); + return -1; + } + + entry = git__calloc(1, sizeof(git_config_list_entry)); + GIT_ERROR_CHECK_ALLOC(entry); + + entry->base.name = git__strndup(memory_backend->values[i], name_len); + GIT_ERROR_CHECK_ALLOC(entry->base.name); + + if (eql) { + entry->base.value = git__strdup(eql + 1); + GIT_ERROR_CHECK_ALLOC(entry->base.value); + } + + entry->base.level = level; + entry->base.include_depth = 0; + entry->base.backend_type = backend_type; + entry->base.origin_path = origin_path; + entry->base.free = git_config_list_entry_free; + entry->config_list = memory_backend->config_list; + + if (git_config_list_append(memory_backend->config_list, entry) < 0) + return -1; + } + + return 0; +} + +static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo) +{ + config_memory_backend *memory_backend = (config_memory_backend *) backend; + + GIT_UNUSED(repo); + + if (memory_backend->cfg.size > 0 && + parse_config(memory_backend, level) < 0) + return -1; + + if (memory_backend->values_len > 0 && + parse_values(memory_backend, level) < 0) + return -1; + + return 0; +} + static int config_memory_get(git_config_backend *backend, const char *key, git_config_entry **out) { config_memory_backend *memory_backend = (config_memory_backend *) backend; @@ -192,36 +276,24 @@ static void config_memory_free(git_config_backend *_backend) if (backend == NULL) return; - git__free(backend->type); + git__free(backend->origin_path); + git__free(backend->backend_type); git_config_list_free(backend->config_list); + git_strlist_free(backend->values, backend->values_len); git_str_dispose(&backend->cfg); git__free(backend); } -int git_config_backend_from_string( - git_config_backend **out, - const char *backend_type, - const char *cfg, - size_t len) +static config_memory_backend *config_backend_new( + git_config_backend_memory_options *opts) { config_memory_backend *backend; - backend = git__calloc(1, sizeof(config_memory_backend)); - GIT_ERROR_CHECK_ALLOC(backend); + if ((backend = git__calloc(1, sizeof(config_memory_backend))) == NULL) + return NULL; - if (git_config_list_new(&backend->config_list) < 0) { - git__free(backend); - return -1; - } - - if (git_str_set(&backend->cfg, cfg, len) < 0) { - git_config_list_free(backend->config_list); - git__free(backend); - return -1; - } - - backend->type = git__strdup(backend_type ? backend_type : "in-memory"); - GIT_ERROR_CHECK_ALLOC(backend->type); + if (git_config_list_new(&backend->config_list) < 0) + goto on_error; backend->parent.version = GIT_CONFIG_BACKEND_VERSION; backend->parent.readonly = 1; @@ -237,7 +309,66 @@ int git_config_backend_from_string( backend->parent.snapshot = git_config_backend_snapshot; backend->parent.free = config_memory_free; - *out = (git_config_backend *)backend; + backend->backend_type = git__strdup(opts && opts->backend_type ? + opts->backend_type : "in-memory"); + if (backend->backend_type == NULL) + goto on_error; + + if (opts && opts->origin_path && + (backend->origin_path = git__strdup(opts->origin_path)) == NULL) + goto on_error; + + return backend; + +on_error: + git_config_list_free(backend->config_list); + git__free(backend->origin_path); + git__free(backend->backend_type); + git__free(backend); + return NULL; +} + +int git_config_backend_from_string( + git_config_backend **out, + const char *cfg, + size_t len, + git_config_backend_memory_options *opts) +{ + config_memory_backend *backend; + + if ((backend = config_backend_new(opts)) == NULL) + return -1; + + if (git_str_set(&backend->cfg, cfg, len) < 0) { + git_config_list_free(backend->config_list); + git__free(backend); + return -1; + } + + *out = (git_config_backend *)backend; + return 0; +} + +int git_config_backend_from_values( + git_config_backend **out, + const char **values, + size_t len, + git_config_backend_memory_options *opts) +{ + config_memory_backend *backend; + + if ((backend = config_backend_new(opts)) == NULL) + return -1; + + if (git_strlist_copy(&backend->values, values, len) < 0) { + git_config_list_free(backend->config_list); + git__free(backend); + return -1; + } + + backend->values_len = len; + + *out = (git_config_backend *)backend; return 0; } diff --git a/src/util/strlist.c b/src/util/strlist.c new file mode 100644 index 000000000..af9b4bbd3 --- /dev/null +++ b/src/util/strlist.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include + +#include "git2_util.h" +#include "vector.h" +#include "strlist.h" + +int git_strlist_copy(char ***out, const char **in, size_t len) +{ + char **dup; + size_t i; + + dup = git__calloc(len, sizeof(char *)); + GIT_ERROR_CHECK_ALLOC(dup); + + for (i = 0; i < len; i++) { + dup[i] = git__strdup(in[i]); + GIT_ERROR_CHECK_ALLOC(dup[i]); + } + + *out = dup; + return 0; +} + +void git_strlist_free(char **strings, size_t len) +{ + size_t i; + + if (!strings) + return; + + for (i = 0; i < len; i++) + git__free(strings[i]); + + git__free(strings); +} diff --git a/src/util/strlist.h b/src/util/strlist.h new file mode 100644 index 000000000..089a8bb57 --- /dev/null +++ b/src/util/strlist.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_runtime_h__ +#define INCLUDE_runtime_h__ + +#include "git2_util.h" + +extern int git_strlist_copy(char ***out, const char **in, size_t len); +extern void git_strlist_free(char **strings, size_t len); + +#endif diff --git a/tests/libgit2/config/memory.c b/tests/libgit2/config/memory.c index 67a61a1c5..9f533e282 100644 --- a/tests/libgit2/config/memory.c +++ b/tests/libgit2/config/memory.c @@ -34,8 +34,13 @@ static int contains_all_cb(const git_config_entry *entry, void *payload) int i; for (i = 0; entries[i].name; i++) { - if (strcmp(entries[i].name, entry->name) || - strcmp(entries[i].value , entry->value)) + if (strcmp(entries[i].name, entry->name)) + continue; + + if ((entries[i].value == NULL) ^ (entry->value == NULL)) + continue; + + if (entry->value && strcmp(entries[i].value , entry->value)) continue; if (entries[i].seen) @@ -61,7 +66,23 @@ static void assert_config_contains_all(git_config_backend *backend, static void setup_backend(const char *cfg) { - cl_git_pass(git_config_backend_from_string(&backend, "test", cfg, strlen(cfg))); + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg), &opts)); + cl_git_pass(git_config_backend_open(backend, 0, NULL)); +} + +static void setup_values_backend(const char **values, size_t len) +{ + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_values(&backend, values, len, &opts)); cl_git_pass(git_config_backend_open(backend, 0, NULL)); } @@ -88,7 +109,13 @@ void test_config_memory__malformed_fails_to_open(void) const char *cfg = "[general\n" "foo=bar\n"; - cl_git_pass(git_config_backend_from_string(&backend, "test", cfg, strlen(cfg))); + + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_string(&backend, cfg, strlen(cfg), &opts)); cl_git_fail(git_config_backend_open(backend, 0, NULL)); } @@ -137,3 +164,43 @@ void test_config_memory__foreach_sees_multivar(void) "foo=bar2\n"); assert_config_contains_all(backend, entries); } + +void test_config_memory__values(void) +{ + const char *values[] = { + "general.foo=bar1", + "general.foo=bar2", + "other.key=value", + "empty.value=", + "no.value", + }; + + struct expected_entry entries[] = { + { "general.foo", "bar1", 0 }, + { "general.foo", "bar2", 0 }, + { "other.key", "value", 0 }, + { "empty.value", "", 0 }, + { "no.value", NULL, 0 }, + { NULL, NULL, 0 } + }; + + setup_values_backend(values, 5); + assert_config_contains_all(backend, entries); +} + +void test_config_memory__valid_values(void) +{ + const char *values[] = { + "general.foo=bar1", + "=bar2", + "other.key=value" + }; + + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + + cl_git_pass(git_config_backend_from_values(&backend, values, 3, &opts)); + cl_git_fail(git_config_backend_open(backend, 0, NULL)); +} diff --git a/tests/libgit2/config/snapshot.c b/tests/libgit2/config/snapshot.c index 87a68600e..cc877063c 100644 --- a/tests/libgit2/config/snapshot.c +++ b/tests/libgit2/config/snapshot.c @@ -152,8 +152,14 @@ void test_config_snapshot__snapshot_from_in_memory(void) git_config_entry *entry; int i; + git_config_backend_memory_options opts = + GIT_CONFIG_BACKEND_MEMORY_OPTIONS_INIT; + + opts.backend_type = "test"; + opts.origin_path = "hello"; + cl_git_pass(git_config_new(&cfg)); - cl_git_pass(git_config_backend_from_string(&backend, "test", configuration, strlen(configuration))); + cl_git_pass(git_config_backend_from_string(&backend, configuration, strlen(configuration), &opts)); cl_git_pass(git_config_add_backend(cfg, backend, 0, NULL, 0)); cl_git_pass(git_config_snapshot(&snapshot, cfg)); @@ -166,7 +172,7 @@ void test_config_snapshot__snapshot_from_in_memory(void) cl_assert_equal_s("section.key", entry->name); cl_assert_equal_s("1", entry->value); cl_assert_equal_s("test", entry->backend_type); - cl_assert_equal_p(NULL, entry->origin_path); + cl_assert_equal_s("hello", entry->origin_path); git_config_entry_free(entry); }