summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2021-03-20 13:01:00 +0000
committerEdward Thomson <ethomson@edwardthomson.com>2021-04-28 13:03:03 +0100
commitdc1ba018289aa40e1948a5fbb673652f86170677 (patch)
tree716f065ad99fd09abe846cc6a8b7a5aff6e87c60
parent88323cd0723cf2f6cb6daa1d2fc275a3fdde5efc (diff)
downloadlibgit2-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.c1
-rw-r--r--src/path.c40
-rw-r--r--src/path.h70
-rw-r--r--src/repository.h3
-rw-r--r--tests/path/core.c58
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,