Commit dc1ba018 by Edward Thomson

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.
parent 88323cd0
......@@ -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)
......
......@@ -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;
......
......@@ -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);
......
......@@ -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 */
......
#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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment