diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2021-03-20 13:01:00 +0000 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2021-04-28 13:03:03 +0100 |
commit | dc1ba018289aa40e1948a5fbb673652f86170677 (patch) | |
tree | 716f065ad99fd09abe846cc6a8b7a5aff6e87c60 | |
parent | 88323cd0723cf2f6cb6daa1d2fc275a3fdde5efc (diff) | |
download | libgit2-dc1ba018289aa40e1948a5fbb673652f86170677.tar.gz |
path: introduce ondisk and workdir path validation
Introduce `git_path_validate_filesystem` which validates (absolute) on-disk
paths and `git_path_validate_workdir` to perform validations on (absolute)
working directory paths. These functions are useful as there may be system
limitations on on-disk paths, particularly on Windows (for example,
enforcing MAX_PATH).
For working directory paths, these limitations may be per-repository, based
on the `core.longpaths` configuration setting.
-rw-r--r-- | src/config_cache.c | 1 | ||||
-rw-r--r-- | src/path.c | 40 | ||||
-rw-r--r-- | src/path.h | 70 | ||||
-rw-r--r-- | src/repository.h | 3 | ||||
-rw-r--r-- | tests/path/core.c | 58 |
5 files changed, 172 insertions, 0 deletions
diff --git a/src/config_cache.c b/src/config_cache.c index 37617af38..4bb91f52b 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -86,6 +86,7 @@ static struct map_data _configmaps[] = { {"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT }, {"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT }, {"core.fsyncobjectfiles", NULL, 0, GIT_FSYNCOBJECTFILES_DEFAULT }, + {"core.longpaths", NULL, 0, GIT_LONGPATHS_DEFAULT }, }; int git_config__configmap_lookup(int *out, git_config *config, git_configmap_item item) diff --git a/src/path.c b/src/path.c index b4b15660b..a4c289e16 100644 --- a/src/path.c +++ b/src/path.c @@ -1904,6 +1904,46 @@ bool git_path_validate( return verify_component(repo, start, (c - start), mode, flags); } +#ifdef GIT_WIN32 +GIT_INLINE(bool) should_validate_longpaths(git_repository *repo) +{ + int longpaths = 0; + + if (repo && + git_repository__configmap_lookup(&longpaths, repo, GIT_CONFIGMAP_LONGPATHS) < 0) + longpaths = 0; + + return (longpaths == 0); +} + +#else +# define should_validate_longpaths(repo) (GIT_UNUSED(repo), false) +#endif + +int git_path_validate_workdir(git_repository *repo, const char *path) +{ + if (should_validate_longpaths(repo)) + return git_path_validate_ondisk(path, strlen(path)); + + return 0; +} + +int git_path_validate_workdir_with_len( + git_repository *repo, + const char *path, + size_t path_len) +{ + if (should_validate_longpaths(repo)) + return git_path_validate_ondisk(path, path_len); + + return 0; +} + +int git_path_validate_workdir_buf(git_repository *repo, git_buf *path) +{ + return git_path_validate_workdir_with_len(repo, path->ptr, path->size); +} + int git_path_normalize_slashes(git_buf *out, const char *path) { int error; diff --git a/src/path.h b/src/path.h index 1ff15dff1..0cf2dbcdf 100644 --- a/src/path.h +++ b/src/path.h @@ -635,6 +635,10 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or * This will ensure that a git path does not contain any "unsafe" components, * a '.' or '..' component, or a component that is ".git" (in any case). * + * (Note: if you take or construct an on-disk path -- a workdir path, + * a path to a git repository or a reference name that could be a loose + * ref -- you should _also_ validate that with `git_path_validate_workdir`.) + * * `repo` is optional. If specified, it will be used to determine the short * path name to reject (if `GIT_PATH_REJECT_DOS_SHORTNAME` is specified), * in addition to the default of "git~1". @@ -646,6 +650,72 @@ extern bool git_path_validate( unsigned int flags); /** + * Validate an on-disk path, taking into account that it will have a + * suffix appended (eg, `.lock`). + */ +GIT_INLINE(int) git_path_validate_filesystem_with_suffix( + const char *path, + size_t path_len, + size_t suffix_len) +{ +#ifdef GIT_WIN32 + size_t path_chars, total_chars; + + path_chars = git_utf8_char_length(path, path_len); + + if (GIT_ADD_SIZET_OVERFLOW(&total_chars, path_chars, suffix_len) || + total_chars > MAX_PATH) { + git_error_set(GIT_ERROR_FILESYSTEM, "path too long: '%s'", path); + return -1; + } + return 0; +#else + GIT_UNUSED(path); + GIT_UNUSED(path_len); + GIT_UNUSED(suffix_len); + return 0; +#endif +} + +/** + * Validate an path on the filesystem. This ensures that the given + * path is valid for the operating system/platform; for example, this + * will ensure that the given absolute path is smaller than MAX_PATH on + * Windows. + * + * For paths within the working directory, you should use ensure that + * `core.longpaths` is obeyed. Use `git_path_validate_workdir`. + */ +GIT_INLINE(int) git_path_validate_filesystem( + const char *path, + size_t path_len) +{ + return git_path_validate_filesystem_with_suffix(path, path_len, 0); +} + +/** + * Validate a path relative to the repo's worktree. This ensures that + * the given working tree path is valid for the operating system/platform. + * This will ensure that an absolute path is smaller than MAX_PATH on + * Windows, while keeping `core.longpaths` configuration settings in mind. + * + * This should be checked by mechamisms like `git_checkout` after + * contructing on-disk paths and before trying to write them. + * + * If the repository is null, no repository configuration is applied. + */ +extern int git_path_validate_workdir( + git_repository *repo, + const char *path); +extern int git_path_validate_workdir_with_len( + git_repository *repo, + const char *path, + size_t path_len); +extern int git_path_validate_workdir_buf( + git_repository *repo, + git_buf *buf); + +/** * Convert any backslashes into slashes */ int git_path_normalize_slashes(git_buf *out, const char *path); diff --git a/src/repository.h b/src/repository.h index c0a28242a..4df85005c 100644 --- a/src/repository.h +++ b/src/repository.h @@ -51,6 +51,7 @@ typedef enum { GIT_CONFIGMAP_PROTECTHFS, /* core.protectHFS */ GIT_CONFIGMAP_PROTECTNTFS, /* core.protectNTFS */ GIT_CONFIGMAP_FSYNCOBJECTFILES, /* core.fsyncObjectFiles */ + GIT_CONFIGMAP_LONGPATHS, /* core.longpaths */ GIT_CONFIGMAP_CACHE_MAX } git_configmap_item; @@ -116,6 +117,8 @@ typedef enum { GIT_PROTECTNTFS_DEFAULT = GIT_CONFIGMAP_TRUE, /* core.fsyncObjectFiles */ GIT_FSYNCOBJECTFILES_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.longpaths */ + GIT_LONGPATHS_DEFAULT = GIT_CONFIGMAP_FALSE, } git_configmap_value; /* internal repository init flags */ diff --git a/tests/path/core.c b/tests/path/core.c index 8294213ba..672321646 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -1,6 +1,11 @@ #include "clar_libgit2.h" #include "path.h" +void test_path_core__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + static void test_make_relative( const char *expected_path, const char *path, @@ -306,6 +311,59 @@ void test_path_core__isvalid_dotgit_with_hfs_ignorables(void) cl_assert_equal_b(true, git_path_validate(NULL, ".git\xe2\xab\x81", 0, GIT_PATH_REJECT_DOT_GIT_HFS)); } +void test_path_core__validate_workdir(void) +{ + cl_must_pass(git_path_validate_workdir(NULL, "/foo/bar")); + cl_must_pass(git_path_validate_workdir(NULL, "C:\\Foo\\Bar")); + cl_must_pass(git_path_validate_workdir(NULL, "\\\\?\\C:\\Foo\\Bar")); + cl_must_pass(git_path_validate_workdir(NULL, "\\\\?\\C:\\Foo\\Bar")); + cl_must_pass(git_path_validate_workdir(NULL, "\\\\?\\UNC\\server\\C$\\folder")); + +#ifdef GIT_WIN32 + /* + * In the absense of a repo configuration, 259 character paths + * succeed. >= 260 character paths fail. + */ + cl_must_pass(git_path_validate_workdir(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\ok.txt")); + cl_must_pass(git_path_validate_workdir(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\260.txt")); + cl_must_fail(git_path_validate_workdir(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\longer_than_260.txt")); + + /* count characters, not bytes */ + cl_must_pass(git_path_validate_workdir(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\260.txt")); + cl_must_fail(git_path_validate_workdir(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\long.txt")); +#else + cl_must_pass(git_path_validate_workdir(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/ok.txt")); + cl_must_pass(git_path_validate_workdir(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/260.txt")); + cl_must_pass(git_path_validate_workdir(NULL, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + cl_must_pass(git_path_validate_workdir(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\260.txt")); + cl_must_pass(git_path_validate_workdir(NULL, "C:\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\aaaaaaaaa\\\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\xc2\xa2\\long.txt")); +#endif +} + +void test_path_core__validate_workdir_with_core_longpath(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + git_config *config; + + repo = cl_git_sandbox_init("empty_bare.git"); + + cl_git_pass(git_repository_open(&repo, "empty_bare.git")); + cl_git_pass(git_repository_config(&config, repo)); + + /* fail by default */ + cl_must_fail(git_path_validate_workdir(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + + /* set core.longpaths explicitly on */ + cl_git_pass(git_config_set_bool(config, "core.longpaths", 1)); + cl_must_pass(git_path_validate_workdir(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); + + /* set core.longpaths explicitly off */ + cl_git_pass(git_config_set_bool(config, "core.longpaths", 0)); + cl_must_fail(git_path_validate_workdir(repo, "/c/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/aaaaaaaaa/longer_than_260.txt")); +#endif +} + static void test_join_unrooted( const char *expected_result, ssize_t expected_rootlen, |