Unverified Commit c44f5688 by Edward Thomson Committed by GitHub

Merge pull request #5823 from libgit2/ethomson/path_validation

Working directory path validation
parents cabfa3b3 c15ed350
core.longpaths support
======================
Historically, Windows has limited absolute path lengths to `MAX_PATH`
(260) characters.
Unfortunately, 260 characters is a punishing small maximum. This is
especially true for developers where dependencies may have dependencies
in a folder, each dependency themselves having dependencies in a
sub-folder, ad (seemingly) infinitum.
So although the Windows APIs _by default_ honor this 260 character
maximum, you can get around this by using separate APIs. Git honors a
`core.longpaths` configuration option that allows some paths on Windows
to exceed these 260 character limits.
And because they've gone and done it, that means that libgit2 has to
honor this value, too.
Since `core.longpaths` is a _configuration option_ that means that we
need to be able to resolve a configuration - including in _the repository
itself_ in order to know whether long paths should be supported.
Therefore, in libgit2, `core.longpaths` affects paths in working
directories _only_. Paths to the repository, and to items inside the
`.git` folder, must be no longer than 260 characters.
This definition is required to avoid a paradoxical setting: if you
had a repository in a folder that was 280 characters long, how would
you know whether `core.longpaths` support should be enabled? Even if
`core.longpaths` was set to true in a system configuration file, the
repository itself may set `core.longpaths` to false in _its_ configuration
file, which you could only read if `core.longpaths` were set to true.
Thus, `core.longpaths` must _only_ apply to working directory items,
and cannot apply to the `.git` folder or its contents.
......@@ -67,7 +67,7 @@ int git_attr_get(
if (git_repository_is_bare(repo))
dir_flag = GIT_DIR_FLAG_FALSE;
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
return -1;
if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
......@@ -133,7 +133,7 @@ int git_attr_get_many_with_session(
if (git_repository_is_bare(repo))
dir_flag = GIT_DIR_FLAG_FALSE;
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
return -1;
if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
......@@ -217,7 +217,7 @@ int git_attr_foreach(
if (git_repository_is_bare(repo))
dir_flag = GIT_DIR_FLAG_FALSE;
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
return -1;
if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
......@@ -523,10 +523,14 @@ static int collect_attr_files(
return error;
/* Resolve path in a non-bare repo */
if (workdir != NULL)
error = git_path_find_dir(&dir, path, workdir);
else
if (workdir != NULL) {
if (!(error = git_repository_workdir_path(&dir, repo, path)))
error = git_path_find_dir(&dir);
}
else {
error = git_path_dirname_r(&dir, path);
}
if (error < 0)
goto cleanup;
......
......@@ -403,7 +403,7 @@ int git_attr_file__load_standalone(git_attr_file **out, const char *path)
if ((error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE)) < 0 ||
(error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 ||
(error = git_attr_cache__alloc_file_entry(&file->entry, NULL, path, &file->pool)) < 0)
(error = git_attr_cache__alloc_file_entry(&file->entry, NULL, NULL, path, &file->pool)) < 0)
goto out;
*out = file;
......@@ -503,14 +503,19 @@ git_attr_assignment *git_attr_rule__lookup_assignment(
}
int git_attr_path__init(
git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
git_attr_path *info,
git_repository *repo,
const char *path,
const char *base,
git_dir_flag dir_flag)
{
ssize_t root;
/* build full path as best we can */
git_buf_init(&info->full, 0);
if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
if (git_path_join_unrooted(&info->full, path, base, &root) < 0 ||
git_path_validate_workdir_buf(repo, &info->full) < 0)
return -1;
info->path = info->full.ptr + root;
......
......@@ -207,8 +207,11 @@ extern git_attr_assignment *git_attr_rule__lookup_assignment(
typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag;
extern int git_attr_path__init(
git_attr_path *info, const char *path, const char *base, git_dir_flag is_dir);
git_attr_path *out,
git_repository *repo,
const char *path,
const char *base,
git_dir_flag is_dir);
extern void git_attr_path__free(git_attr_path *info);
extern int git_attr_assignment__parse(
......
......@@ -38,6 +38,7 @@ GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
int git_attr_cache__alloc_file_entry(
git_attr_file_entry **out,
git_repository *repo,
const char *base,
const char *path,
git_pool *pool)
......@@ -65,6 +66,9 @@ int git_attr_cache__alloc_file_entry(
}
memcpy(&ce->fullpath[baselen], path, pathlen);
if (git_path_validate_workdir_with_len(repo, ce->fullpath, pathlen + baselen) < 0)
return -1;
ce->path = &ce->fullpath[baselen];
*out = ce;
......@@ -79,8 +83,8 @@ static int attr_cache_make_entry(
git_attr_file_entry *entry = NULL;
int error;
if ((error = git_attr_cache__alloc_file_entry(&entry, git_repository_workdir(repo),
path, &cache->pool)) < 0)
if ((error = git_attr_cache__alloc_file_entry(&entry, repo,
git_repository_workdir(repo), path, &cache->pool)) < 0)
return error;
if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0)
......@@ -169,7 +173,8 @@ static int attr_cache_lookup(
if (base != NULL && git_path_root(filename) < 0) {
git_buf *p = attr_session ? &attr_session->tmp : &path;
if (git_buf_joinpath(p, base, filename) < 0)
if (git_buf_joinpath(p, base, filename) < 0 ||
git_path_validate_workdir_buf(repo, p) < 0)
return -1;
filename = p->ptr;
......
......@@ -44,6 +44,7 @@ extern bool git_attr_cache__is_cached(
extern int git_attr_cache__alloc_file_entry(
git_attr_file_entry **out,
git_repository *repo,
const char *base,
const char *path,
git_pool *pool);
......
......@@ -198,11 +198,7 @@ int git_blob__create_from_paths(
GIT_ASSERT_ARG(hint_path || !try_load_filters);
if (!content_path) {
if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
return GIT_EBAREREPO;
if (git_buf_joinpath(
&path, git_repository_workdir(repo), hint_path) < 0)
if (git_repository_workdir_path(&path, repo, hint_path) < 0)
return -1;
content_path = path.ptr;
......
......@@ -329,6 +329,9 @@ static int checkout_target_fullpath(
if (path && git_buf_puts(&data->target_path, path) < 0)
return -1;
if (git_path_validate_workdir_buf(data->repo, &data->target_path) < 0)
return -1;
*out = &data->target_path;
return 0;
......@@ -1278,14 +1281,14 @@ static int checkout_verify_paths(
unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS;
if (action & CHECKOUT_ACTION__REMOVE) {
if (!git_path_isvalid(repo, delta->old_file.path, delta->old_file.mode, flags)) {
if (!git_path_validate(repo, delta->old_file.path, delta->old_file.mode, flags)) {
git_error_set(GIT_ERROR_CHECKOUT, "cannot remove invalid path '%s'", delta->old_file.path);
return -1;
}
}
if (action & ~CHECKOUT_ACTION__REMOVE) {
if (!git_path_isvalid(repo, delta->new_file.path, delta->new_file.mode, flags)) {
if (!git_path_validate(repo, delta->new_file.path, delta->new_file.mode, flags)) {
git_error_set(GIT_ERROR_CHECKOUT, "cannot checkout to invalid path '%s'", delta->new_file.path);
return -1;
}
......@@ -2032,7 +2035,8 @@ static int checkout_merge_path(
const char *our_label_raw, *their_label_raw, *suffix;
int error = 0;
if ((error = git_buf_joinpath(out, git_repository_workdir(data->repo), result->path)) < 0)
if ((error = git_buf_joinpath(out, data->opts.target_directory, result->path)) < 0 ||
(error = git_path_validate_workdir_buf(data->repo, out)) < 0)
return error;
/* Most conflicts simply use the filename in the index */
......@@ -2331,6 +2335,22 @@ static void checkout_data_clear(checkout_data *data)
git_attr_session__free(&data->attr_session);
}
static int validate_target_directory(checkout_data *data)
{
int error;
if ((error = git_path_validate_workdir(data->repo, data->opts.target_directory)) < 0)
return error;
if (git_path_isdir(data->opts.target_directory))
return 0;
error = checkout_mkdir(data, data->opts.target_directory, NULL,
GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR);
return error;
}
static int checkout_data_init(
checkout_data *data,
git_iterator *target,
......@@ -2363,10 +2383,7 @@ static int checkout_data_init(
if (!data->opts.target_directory)
data->opts.target_directory = git_repository_workdir(repo);
else if (!git_path_isdir(data->opts.target_directory) &&
(error = checkout_mkdir(data,
data->opts.target_directory, NULL,
GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
else if ((error = validate_target_directory(data)) < 0)
goto cleanup;
if ((error = git_repository_index(&data->index, data->repo)) < 0)
......
......@@ -83,6 +83,7 @@
#include "thread.h"
#include "integer.h"
#include "assert_safe.h"
#include "utf8.h"
/*
* Include the declarations for deprecated functions; this ensures
......
......@@ -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)
......
......@@ -394,8 +394,7 @@ static int diff_file_content_load_workdir(
if (fc->file->mode == GIT_FILEMODE_TREE)
return 0;
if (git_buf_joinpath(
&path, git_repository_workdir(fc->repo), fc->file->path) < 0)
if (git_repository_workdir_path(&path, fc->repo, fc->file->path) < 0)
return -1;
if (S_ISLNK(fc->file->mode))
......
......@@ -596,8 +596,7 @@ int git_diff__oid_for_entry(
memset(out, 0, sizeof(*out));
if (git_buf_joinpath(&full_path,
git_repository_workdir(diff->base.repo), entry.path) < 0)
if (git_repository_workdir_path(&full_path, diff->base.repo, entry.path) < 0)
return -1;
if (!mode) {
......
......@@ -476,8 +476,8 @@ static int similarity_sig(
git_diff_file *file = info->file;
if (info->src == GIT_ITERATOR_WORKDIR) {
if ((error = git_buf_joinpath(
&info->data, git_repository_workdir(info->repo), file->path)) < 0)
if ((error = git_repository_workdir_path(
&info->data, info->repo, file->path)) < 0)
return error;
/* if path is not a regular file, just skip this item */
......
......@@ -6,7 +6,6 @@
*/
#include "diff_xdiff.h"
#include "util.h"
#include "git2/errors.h"
#include "diff.h"
......@@ -128,7 +127,7 @@ static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
info->hunk.header_len = sizeof(info->hunk.header) - 1;
/* Sanitize the hunk header in case there is invalid Unicode */
buffer_len = git__utf8_valid_buf_length((const uint8_t *) bufs[0].ptr, info->hunk.header_len);
buffer_len = git_utf8_valid_buf_length(bufs[0].ptr, info->hunk.header_len);
/* Sanitizing the hunk header may delete the newline, so add it back again if there is room */
if (buffer_len < info->hunk.header_len) {
bufs[0].ptr[buffer_len] = '\n';
......
......@@ -972,8 +972,10 @@ int git_filter_list_stream_file(
if ((error = stream_list_init(
&stream_start, &filter_streams, filters, target)) < 0 ||
(error = git_path_join_unrooted(&abspath, path, base, NULL)) < 0)
(error = git_path_join_unrooted(&abspath, path, base, NULL)) < 0 ||
(error = git_path_validate_workdir_buf(repo, &abspath)) < 0)
goto done;
initialized = 1;
if ((fd = git_futils_open_ro(abspath.ptr)) < 0) {
......
......@@ -309,12 +309,17 @@ int git_ignore__for_path(
if ((error = git_path_dirname_r(&local, path)) < 0 ||
(error = git_path_resolve_relative(&local, 0)) < 0 ||
(error = git_path_to_dir(&local)) < 0 ||
(error = git_buf_joinpath(&ignores->dir, workdir, local.ptr)) < 0)
{;} /* Nothing, we just want to stop on the first error */
(error = git_buf_joinpath(&ignores->dir, workdir, local.ptr)) < 0 ||
(error = git_path_validate_workdir_buf(repo, &ignores->dir)) < 0) {
/* Nothing, we just want to stop on the first error */
}
git_buf_dispose(&local);
} else {
error = git_buf_joinpath(&ignores->dir, path, "");
if (!(error = git_buf_joinpath(&ignores->dir, path, "")))
error = git_path_validate_filesystem(ignores->dir.ptr, ignores->dir.size);
}
if (error < 0)
goto cleanup;
......@@ -453,7 +458,7 @@ int git_ignore__lookup(
*out = GIT_IGNORE_NOTFOUND;
if (git_attr_path__init(
&path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0)
&path, ignores->repo, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0)
return -1;
/* first process builtins - success means path was found */
......@@ -537,7 +542,7 @@ int git_ignore_path_is_ignored(
else if (git_repository_is_bare(repo))
dir_flag = GIT_DIR_FLAG_FALSE;
if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 ||
if ((error = git_attr_path__init(&path, repo, pathname, workdir, dir_flag)) < 0 ||
(error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
goto cleanup;
......@@ -590,7 +595,7 @@ int git_ignore__check_pathspec_for_exact_ignores(
git_attr_fnmatch *match;
int ignored;
git_buf path = GIT_BUF_INIT;
const char *wd, *filename;
const char *filename;
git_index *idx;
if ((error = git_repository__ensure_not_bare(
......@@ -598,8 +603,6 @@ int git_ignore__check_pathspec_for_exact_ignores(
(error = git_repository_index(&idx, repo)) < 0)
return error;
wd = git_repository_workdir(repo);
git_vector_foreach(vspec, i, match) {
/* skip wildcard matches (if they are being used) */
if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 &&
......@@ -612,7 +615,7 @@ int git_ignore__check_pathspec_for_exact_ignores(
if (git_index_get_bypath(idx, filename, 0) != NULL)
continue;
if ((error = git_buf_joinpath(&path, wd, filename)) < 0)
if ((error = git_repository_workdir_path(&path, repo, filename)) < 0)
break;
/* is there a file on disk that matches this exactly? */
......
......@@ -944,7 +944,7 @@ static int index_entry_create(
if (st)
mode = st->st_mode;
if (!git_path_isvalid(repo, path, mode, path_valid_flags)) {
if (!git_path_validate(repo, path, mode, path_valid_flags)) {
git_error_set(GIT_ERROR_INDEX, "invalid path: '%s'", path);
return -1;
}
......@@ -988,7 +988,7 @@ static int index_entry_init(
if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
return GIT_EBAREREPO;
if (git_buf_joinpath(&path, git_repository_workdir(repo), rel_path) < 0)
if (git_repository_workdir_path(&path, repo, rel_path) < 0)
return -1;
error = git_path_lstat(path.ptr, &st);
......@@ -1532,7 +1532,7 @@ static int add_repo_as_submodule(git_index_entry **out, git_index *index, const
struct stat st;
int error;
if ((error = git_buf_joinpath(&abspath, git_repository_workdir(repo), path)) < 0)
if ((error = git_repository_workdir_path(&abspath, repo, path)) < 0)
return error;
if ((error = p_stat(abspath.ptr, &st)) < 0) {
......
......@@ -1278,7 +1278,8 @@ static int filesystem_iterator_entry_hash(
return git_repository_hashfile(&entry->id,
iter->base.repo, entry->path, GIT_OBJECT_BLOB, NULL);
if (!(error = git_buf_joinpath(&fullpath, iter->root, entry->path)))
if (!(error = git_buf_joinpath(&fullpath, iter->root, entry->path)) &&
!(error = git_path_validate_workdir_buf(iter->base.repo, &fullpath)))
error = git_odb_hashfile(&entry->id, fullpath.ptr, GIT_OBJECT_BLOB);
git_buf_dispose(&fullpath);
......@@ -1359,7 +1360,8 @@ static int filesystem_iterator_frame_push(
else
git_buf_puts(&root, iter->root);
if (git_buf_oom(&root)) {
if (git_buf_oom(&root) ||
git_path_validate_workdir_buf(iter->base.repo, &root) < 0) {
error = -1;
goto done;
}
......@@ -1389,7 +1391,8 @@ static int filesystem_iterator_frame_push(
iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL;
bool dir_expected = false;
if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0)
if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0 ||
(error = git_path_validate_workdir_with_len(iter->base.repo, path, path_len)) < 0)
goto done;
GIT_ASSERT(path_len > iter->root_len);
......@@ -1562,7 +1565,8 @@ static int filesystem_iterator_is_dir(
}
if ((error = git_buf_joinpath(&fullpath, iter->root, entry->path)) < 0 ||
(error = p_stat(fullpath.ptr, &st)) < 0)
(error = git_path_validate_workdir_buf(iter->base.repo, &fullpath)) < 0 ||
(error = p_stat(fullpath.ptr, &st)) < 0)
goto done;
*is_dir = S_ISDIR(st.st_mode);
......
......@@ -330,6 +330,10 @@ static int mailmap_add_file_ondisk(
if (error < 0)
goto cleanup;
error = git_path_validate_workdir_buf(repo, &fullpath);
if (error < 0)
goto cleanup;
error = git_futils_readbuffer(&content, fullpath.ptr);
if (error < 0)
goto cleanup;
......
......@@ -754,15 +754,13 @@ bool git_path_contains_file(git_buf *base, const char *file)
return _check_dir_contents(base, file, &git_path_isfile);
}
int git_path_find_dir(git_buf *dir, const char *path, const char *base)
int git_path_find_dir(git_buf *dir)
{
int error = git_path_join_unrooted(dir, path, base, NULL);
int error = 0;
char buf[GIT_PATH_MAX];
if (!error) {
char buf[GIT_PATH_MAX];
if (p_realpath(dir->ptr, buf) != NULL)
error = git_buf_sets(dir, buf);
}
if (p_realpath(dir->ptr, buf) != NULL)
error = git_buf_sets(dir, buf);
/* call dirname if this is not a directory */
if (!error) /* && git_path_isdir(dir->ptr) == false) */
......@@ -1562,8 +1560,8 @@ GIT_INLINE(bool) verify_dospath(
static int32_t next_hfs_char(const char **in, size_t *len)
{
while (*len) {
int32_t codepoint;
int cp_len = git__utf8_iterate((const uint8_t *)(*in), (int)(*len), &codepoint);
uint32_t codepoint;
int cp_len = git_utf8_iterate(&codepoint, *in, *len);
if (cp_len < 0)
return -1;
......@@ -1595,7 +1593,7 @@ static int32_t next_hfs_char(const char **in, size_t *len)
* the ASCII range, which is perfectly fine, because the
* git folder name can only be composed of ascii characters
*/
return git__tolower(codepoint);
return git__tolower((int)codepoint);
}
return 0; /* NULL byte -- end of string */
}
......@@ -1877,7 +1875,7 @@ GIT_INLINE(unsigned int) dotgit_flags(
return flags;
}
bool git_path_isvalid(
bool git_path_validate(
git_repository *repo,
const char *path,
uint16_t mode,
......@@ -1904,6 +1902,46 @@ bool git_path_isvalid(
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_filesystem(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_filesystem(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;
......
......@@ -283,7 +283,7 @@ extern int git_path_prettify_dir(git_buf *path_out, const char *path, const char
* appends the trailing '/'. If the path does not exist, it is
* treated like a regular filename.
*/
extern int git_path_find_dir(git_buf *dir, const char *path, const char *base);
extern int git_path_find_dir(git_buf *dir);
/**
* Resolve relative references within a path.
......@@ -626,21 +626,96 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or
#define GIT_PATH_REJECT_INDEX_DEFAULTS \
GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT
/*
* Determine whether a path is a valid git path or not - this must not contain
/**
* Validate a "bare" git path. This ensures that the given path is legal
* to place in the index or a tree. This should be checked by mechanisms
* like `git_index_add` and `git_treebuilder_insert` when taking user
* data, and by `git_checkout` before constructing on-disk paths.
*
* 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".
*/
extern bool git_path_isvalid(
extern bool git_path_validate(
git_repository *repo,
const char *path,
uint16_t mode,
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);
......
......@@ -98,8 +98,7 @@ static int workdir_reader_read(
git_oid id;
int error;
if ((error = git_buf_joinpath(&path,
git_repository_workdir(reader->repo), filename)) < 0)
if ((error = git_repository_workdir_path(&path, reader->repo, filename)) < 0)
goto done;
if ((error = p_lstat(path.ptr, &st)) < 0) {
......
......@@ -68,6 +68,35 @@ typedef struct refdb_fs_backend {
static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name);
GIT_INLINE(int) loose_path(
git_buf *out,
const char *base,
const char *refname)
{
if (git_buf_joinpath(out, base, refname) < 0)
return -1;
return git_path_validate_filesystem_with_suffix(out->ptr, out->size,
CONST_STRLEN(".lock"));
}
GIT_INLINE(int) reflog_path(
git_buf *out,
git_repository *repo,
const char *refname)
{
const char *base;
int error;
base = (strcmp(refname, GIT_HEAD_FILE) == 0) ? repo->gitdir :
repo->commondir;
if ((error = git_buf_joinpath(out, base, GIT_REFLOG_DIR)) < 0)
return error;
return loose_path(out, out->ptr, refname);
}
static int packref_cmp(const void *a_, const void *b_)
{
const struct packref *a = a_, *b = b_;
......@@ -223,9 +252,8 @@ static int loose_readbuffer(git_buf *buf, const char *base, const char *path)
{
int error;
/* build full path to file */
if ((error = git_buf_joinpath(buf, base, path)) < 0 ||
(error = git_futils_readbuffer(buf, buf->ptr)) < 0)
if ((error = loose_path(buf, base, path)) < 0 ||
(error = git_futils_readbuffer(buf, buf->ptr)) < 0)
git_buf_dispose(buf);
return error;
......@@ -336,7 +364,7 @@ static int refdb_fs_backend__exists(
*exists = 0;
if ((error = git_buf_joinpath(&ref_path, backend->gitpath, ref_name)) < 0)
if ((error = loose_path(&ref_path, backend->gitpath, ref_name)) < 0)
goto out;
if (git_path_isfile(ref_path.ptr)) {
......@@ -789,7 +817,7 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
GIT_ASSERT_ARG(backend);
GIT_ASSERT_ARG(name);
if (!git_path_isvalid(backend->repo, name, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
if (!git_path_validate(backend->repo, name, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", name);
return GIT_EINVALIDSPEC;
}
......@@ -805,8 +833,8 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *
if ((error = git_futils_rmdir_r(name, basedir, GIT_RMDIR_SKIP_NONEMPTY)) < 0)
return error;
if (git_buf_joinpath(&ref_path, basedir, name) < 0)
return -1;
if ((error = loose_path(&ref_path, basedir, name)) < 0)
return error;
filebuf_flags = GIT_FILEBUF_CREATE_LEADING_DIRS;
if (backend->fsync)
......@@ -1329,6 +1357,9 @@ static int refdb_fs_backend__prune_refs(
backend->commonpath,
git_buf_cstr(&relative_path));
if (!error)
error = git_path_validate_filesystem(base_path.ptr, base_path.size);
if (error < 0)
goto cleanup;
......@@ -1371,19 +1402,19 @@ static int refdb_fs_backend__delete(
static int loose_delete(refdb_fs_backend *backend, const char *ref_name)
{
git_buf loose_path = GIT_BUF_INIT;
git_buf path = GIT_BUF_INIT;
int error = 0;
if (git_buf_joinpath(&loose_path, backend->commonpath, ref_name) < 0)
return -1;
if ((error = loose_path(&path, backend->commonpath, ref_name)) < 0)
return error;
error = p_unlink(loose_path.ptr);
error = p_unlink(path.ptr);
if (error < 0 && errno == ENOENT)
error = GIT_ENOTFOUND;
else if (error != 0)
error = -1;
git_buf_dispose(&loose_path);
git_buf_dispose(&path);
return error;
}
......@@ -1677,13 +1708,6 @@ static int create_new_reflog_file(const char *filepath)
return p_close(fd);
}
GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name)
{
if (strcmp(name, GIT_HEAD_FILE) == 0)
return git_buf_join3(path, '/', repo->gitdir, GIT_REFLOG_DIR, name);
return git_buf_join3(path, '/', repo->commondir, GIT_REFLOG_DIR, name);
}
static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
{
refdb_fs_backend *backend;
......@@ -1696,7 +1720,7 @@ static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *
backend = GIT_CONTAINER_OF(_backend, refdb_fs_backend, parent);
repo = backend->repo;
if ((error = retrieve_reflog_path(&path, repo, name)) < 0)
if ((error = reflog_path(&path, repo, name)) < 0)
return error;
error = create_new_reflog_file(git_buf_cstr(&path));
......@@ -1710,7 +1734,7 @@ static int has_reflog(git_repository *repo, const char *name)
int ret = 0;
git_buf path = GIT_BUF_INIT;
if (retrieve_reflog_path(&path, repo, name) < 0)
if (reflog_path(&path, repo, name) < 0)
goto cleanup;
ret = git_path_isfile(git_buf_cstr(&path));
......@@ -1751,7 +1775,7 @@ static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend,
if (reflog_alloc(&log, name) < 0)
return -1;
if (retrieve_reflog_path(&log_path, repo, name) < 0)
if (reflog_path(&log_path, repo, name) < 0)
goto cleanup;
error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path));
......@@ -1828,12 +1852,12 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char
repo = backend->repo;
if (!git_path_isvalid(backend->repo, refname, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
if (!git_path_validate(backend->repo, refname, 0, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) {
git_error_set(GIT_ERROR_INVALID, "invalid reference name '%s'", refname);
return GIT_EINVALIDSPEC;
}
if (retrieve_reflog_path(&log_path, repo, refname) < 0)
if (reflog_path(&log_path, repo, refname) < 0)
return -1;
if (!git_path_isfile(git_buf_cstr(&log_path))) {
......@@ -1934,7 +1958,7 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co
if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0)
goto cleanup;
if ((error = retrieve_reflog_path(&path, repo, ref->name)) < 0)
if ((error = reflog_path(&path, repo, ref->name)) < 0)
goto cleanup;
if (((error = git_futils_mkpath2file(git_buf_cstr(&path), 0777)) < 0) &&
......@@ -1997,11 +2021,11 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_
if (git_buf_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0)
return -1;
if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0)
return -1;
if ((error = loose_path(&old_path, git_buf_cstr(&temp_path), old_name)) < 0)
return error;
if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0)
return -1;
if ((error = loose_path(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized))) < 0)
return error;
if (!git_path_exists(git_buf_cstr(&old_path))) {
error = GIT_ENOTFOUND;
......@@ -2015,8 +2039,8 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_
* - a/b -> a/b/c
* - a/b/c/d -> a/b/c
*/
if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0)
return -1;
if ((error = loose_path(&temp_path, git_buf_cstr(&temp_path), "temp_reflog")) < 0)
return error;
if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) {
error = -1;
......@@ -2065,7 +2089,7 @@ static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name
GIT_ASSERT_ARG(_backend);
GIT_ASSERT_ARG(name);
if ((error = retrieve_reflog_path(&path, backend->repo, name)) < 0)
if ((error = reflog_path(&path, backend->repo, name)) < 0)
goto out;
if (!git_path_exists(path.ptr))
......
......@@ -186,6 +186,63 @@ void git_repository_free(git_repository *repo)
git__free(repo);
}
/* Check if we have a separate commondir (e.g. we have a worktree) */
static int lookup_commondir(bool *separate, git_buf *commondir, git_buf *repository_path)
{
git_buf common_link = GIT_BUF_INIT;
int error;
/*
* If there's no commondir file, the repository path is the
* common path, but it needs a trailing slash.
*/
if (!git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) {
if ((error = git_buf_set(commondir, repository_path->ptr, repository_path->size)) == 0)
error = git_path_to_dir(commondir);
*separate = false;
goto done;
}
*separate = true;
if ((error = git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 ||
(error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0)
goto done;
git_buf_rtrim(&common_link);
if (git_path_is_relative(common_link.ptr)) {
if ((error = git_buf_joinpath(commondir, repository_path->ptr, common_link.ptr)) < 0)
goto done;
} else {
git_buf_swap(commondir, &common_link);
}
git_buf_dispose(&common_link);
/* Make sure the commondir path always has a trailing slash */
error = git_path_prettify_dir(commondir, commondir->ptr, NULL);
done:
return error;
}
GIT_INLINE(int) validate_repo_path(git_buf *path)
{
/*
* The longest static path in a repository (or commondir) is the
* packed refs file. (Loose refs may be longer since they
* include the reference name, but will be validated when the
* path is constructed.)
*/
static size_t suffix_len =
CONST_STRLEN("objects/pack/pack-.pack.lock") +
GIT_OID_HEXSZ;
return git_path_validate_filesystem_with_suffix(
path->ptr, path->size, suffix_len);
}
/*
* Git repository open methods
*
......@@ -193,48 +250,30 @@ void git_repository_free(git_repository *repo)
*/
static int is_valid_repository_path(bool *out, git_buf *repository_path, git_buf *common_path)
{
bool separate_commondir = false;
int error;
*out = false;
/* Check if we have a separate commondir (e.g. we have a
* worktree) */
if (git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) {
git_buf common_link = GIT_BUF_INIT;
if ((error = git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE)) < 0 ||
(error = git_futils_readbuffer(&common_link, common_link.ptr)) < 0)
return error;
git_buf_rtrim(&common_link);
if (git_path_is_relative(common_link.ptr)) {
if ((error = git_buf_joinpath(common_path, repository_path->ptr, common_link.ptr)) < 0)
return error;
} else {
git_buf_swap(common_path, &common_link);
}
git_buf_dispose(&common_link);
}
else {
if ((error = git_buf_set(common_path, repository_path->ptr, repository_path->size)) < 0)
return error;
}
/* Make sure the commondir path always has a trailing * slash */
if (git_buf_rfind(common_path, '/') != (ssize_t)common_path->size - 1)
if ((error = git_buf_putc(common_path, '/')) < 0)
return error;
if ((error = lookup_commondir(&separate_commondir, common_path, repository_path)) < 0)
return error;
/* Ensure HEAD file exists */
if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false)
return 0;
/* Check files in common dir */
if (git_path_contains_dir(common_path, GIT_OBJECTS_DIR) == false)
return 0;
if (git_path_contains_dir(common_path, GIT_REFS_DIR) == false)
return 0;
/* Ensure the repo (and commondir) are valid paths */
if ((error = validate_repo_path(common_path)) < 0 ||
(separate_commondir &&
(error = validate_repo_path(repository_path)) < 0))
return error;
*out = true;
return 0;
}
......@@ -2493,6 +2532,22 @@ const char *git_repository_workdir(const git_repository *repo)
return repo->workdir;
}
int git_repository_workdir_path(
git_buf *out, git_repository *repo, const char *path)
{
int error;
if (!repo->workdir) {
git_error_set(GIT_ERROR_REPOSITORY, "repository has no working directory");
return GIT_EBAREREPO;
}
if (!(error = git_buf_joinpath(out, repo->workdir, path)))
error = git_path_validate_workdir_buf(repo, out);
return error;
}
const char *git_repository_commondir(const git_repository *repo)
{
GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL);
......@@ -2685,9 +2740,9 @@ int git_repository_hashfile(
* now that is not possible because git_filters_load() needs it.
*/
error = git_path_join_unrooted(
&full_path, path, git_repository_workdir(repo), NULL);
if (error < 0)
if ((error = git_path_join_unrooted(
&full_path, path, git_repository_workdir(repo), NULL)) < 0 ||
(error = git_path_validate_workdir_buf(repo, &full_path)) < 0)
return error;
if (!as_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 */
......@@ -238,4 +241,12 @@ bool git_repository__reserved_names(
*/
int git_repository_initialbranch(git_buf *out, git_repository *repo);
/*
* Given a relative `path`, this makes it absolute based on the
* repository's working directory. This will perform validation
* to ensure that the path is not longer than MAX_PATH on Windows
* (unless `core.longpaths` is set in the repo config).
*/
int git_repository_workdir_path(git_buf *out, git_repository *repo, const char *path);
#endif
......@@ -380,8 +380,10 @@ int git_submodule__lookup_with_cache(
/* If it's not configured, we still check if there's a repo at the path */
if (git_repository_workdir(repo)) {
git_buf path = GIT_BUF_INIT;
if (git_buf_join3(&path,
'/', git_repository_workdir(repo), name, DOT_GIT) < 0)
if (git_buf_join3(&path, '/',
git_repository_workdir(repo),
name, DOT_GIT) < 0 ||
git_path_validate_workdir_buf(NULL, &path) < 0)
return -1;
if (git_path_exists(path.ptr))
......@@ -418,7 +420,7 @@ int git_submodule_name_is_valid(git_repository *repo, const char *name, int flag
git_buf_attach_notowned(&buf, name, strlen(name));
}
isvalid = git_path_isvalid(repo, buf.ptr, 0, flags);
isvalid = git_path_validate(repo, buf.ptr, 0, flags);
git_buf_dispose(&buf);
return isvalid;
......@@ -553,10 +555,10 @@ int git_submodule__map(git_repository *repo, git_strmap *map)
int error = 0;
git_index *idx = NULL;
git_tree *head = NULL;
const char *wd = NULL;
git_buf path = GIT_BUF_INIT;
git_submodule *sm;
git_config *mods = NULL;
bool has_workdir;
GIT_ASSERT_ARG(repo);
GIT_ASSERT_ARG(map);
......@@ -567,12 +569,14 @@ int git_submodule__map(git_repository *repo, git_strmap *map)
if (git_repository_head_tree(&head, repo) < 0)
git_error_clear();
wd = git_repository_workdir(repo);
if (wd && (error = git_buf_joinpath(&path, wd, GIT_MODULES_FILE)) < 0)
has_workdir = git_repository_workdir(repo) != NULL;
if (has_workdir &&
(error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0)
goto cleanup;
/* add submodule information from .gitmodules */
if (wd) {
if (has_workdir) {
lfc_data data = { 0 };
data.map = map;
data.repo = repo;
......@@ -599,7 +603,7 @@ int git_submodule__map(git_repository *repo, git_strmap *map)
goto cleanup;
}
/* shallow scan submodules in work tree as needed */
if (wd) {
if (has_workdir) {
git_strmap_foreach_value(map, sm, {
submodule_load_from_wd_lite(sm);
});
......@@ -683,7 +687,7 @@ static int submodule_repo_init(
git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
git_repository *subrepo = NULL;
error = git_buf_joinpath(&workdir, git_repository_workdir(parent_repo), path);
error = git_repository_workdir_path(&workdir, parent_repo, path);
if (error < 0)
goto cleanup;
......@@ -790,7 +794,7 @@ int git_submodule_add_setup(
/* init submodule repository and add origin remote as needed */
error = git_buf_joinpath(&name, git_repository_workdir(repo), path);
error = git_repository_workdir_path(&name, repo, path);
if (error < 0)
goto cleanup;
......@@ -896,10 +900,9 @@ int git_submodule_clone(git_repository **out, git_submodule *submodule, const gi
opts.remote_cb = clone_return_origin;
opts.remote_cb_payload = submodule;
git_buf_puts(&rel_path, git_repository_workdir(git_submodule_owner(submodule)));
git_buf_joinpath(&rel_path, git_buf_cstr(&rel_path), git_submodule_path(submodule));
GIT_ERROR_CHECK_ALLOC_BUF(&rel_path);
error = git_repository_workdir_path(&rel_path, git_submodule_owner(submodule), git_submodule_path(submodule));
if (error < 0)
goto cleanup;
error = git_clone__submodule(&clone, git_submodule_url(submodule), git_buf_cstr(&rel_path), &opts);
if (error < 0)
......@@ -946,9 +949,8 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index)
sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID;
if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 ||
(error = git_buf_joinpath(
&path, git_repository_workdir(sm->repo), sm->path)) < 0 ||
(error = git_submodule_open(&sm_repo, sm)) < 0)
(error = git_repository_workdir_path(&path, sm->repo, sm->path)) < 0 ||
(error = git_submodule_open(&sm_repo, sm)) < 0)
goto cleanup;
/* read stat information for submodule working directory */
......@@ -1237,7 +1239,7 @@ static int submodule_repo_create(
GIT_REPOSITORY_INIT_RELATIVE_GITLINK;
/* Workdir: path to sub-repo working directory */
error = git_buf_joinpath(&workdir, git_repository_workdir(parent_repo), path);
error = git_repository_workdir_path(&workdir, parent_repo, path);
if (error < 0)
goto cleanup;
......@@ -1534,8 +1536,7 @@ static int git_submodule__open(
wd = git_repository_workdir(sm->repo);
if (git_buf_joinpath(&path, wd, sm->path) < 0 ||
git_buf_joinpath(&path, path.ptr, DOT_GIT) < 0)
if (git_buf_join3(&path, '/', wd, sm->path, DOT_GIT) < 0)
return -1;
sm->flags = sm->flags &
......@@ -2080,7 +2081,7 @@ static int submodule_load_from_wd_lite(git_submodule *sm)
{
git_buf path = GIT_BUF_INIT;
if (git_buf_joinpath(&path, git_repository_workdir(sm->repo), sm->path) < 0)
if (git_repository_workdir_path(&path, sm->repo, sm->path) < 0)
return -1;
if (git_path_isdir(path.ptr))
......@@ -2100,15 +2101,14 @@ static int submodule_load_from_wd_lite(git_submodule *sm)
*/
static int gitmodules_snapshot(git_config **snap, git_repository *repo)
{
const char *workdir = git_repository_workdir(repo);
git_config *mods = NULL;
git_buf path = GIT_BUF_INIT;
int error;
if (!workdir)
if (git_repository_workdir(repo) == NULL)
return GIT_ENOTFOUND;
if ((error = git_buf_joinpath(&path, workdir, GIT_MODULES_FILE)) < 0)
if ((error = git_repository_workdir_path(&path, repo, GIT_MODULES_FILE)) < 0)
return error;
if ((error = git_config_open_ondisk(&mods, path.ptr)) < 0)
......@@ -2132,12 +2132,11 @@ static git_config_backend *open_gitmodules(
git_repository *repo,
int okay_to_create)
{
const char *workdir = git_repository_workdir(repo);
git_buf path = GIT_BUF_INIT;
git_config_backend *mods = NULL;
if (workdir != NULL) {
if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0)
if (git_repository_workdir(repo) != NULL) {
if (git_repository_workdir_path(&path, repo, GIT_MODULES_FILE) != 0)
return NULL;
if (okay_to_create || git_path_isfile(path.ptr)) {
......@@ -2250,8 +2249,9 @@ static int get_url_base(git_buf *url, git_repository *repo)
if ((error = git_worktree_open_from_repository(&wt, repo)) < 0)
goto out;
error = git_buf_sets(url, wt->parent_path);
} else
} else {
error = git_buf_sets(url, git_repository_workdir(repo));
}
out:
git_remote_free(remote);
......
......@@ -54,7 +54,7 @@ GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode)
static int valid_entry_name(git_repository *repo, const char *filename)
{
return *filename != '\0' &&
git_path_isvalid(repo, filename, 0,
git_path_validate(repo, filename, 0,
GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT | GIT_PATH_REJECT_SLASH);
}
......
/*
* 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 "utf8.h"
#include "common.h"
/*
* git_utf8_iterate is taken from the utf8proc project,
* http://www.public-software-group.org/utf8proc
*
* Copyright (c) 2009 Public Software Group e. V., Berlin, Germany
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the ""Software""),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
static const uint8_t utf8proc_utf8class[256] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0
};
static int utf8_charlen(const uint8_t *str, size_t str_len)
{
uint8_t length;
size_t i;
length = utf8proc_utf8class[str[0]];
if (!length)
return -1;
if (str_len > 0 && length > str_len)
return -1;
for (i = 1; i < length; i++) {
if ((str[i] & 0xC0) != 0x80)
return -1;
}
return (int)length;
}
int git_utf8_iterate(uint32_t *out, const char *_str, size_t str_len)
{
const uint8_t *str = (const uint8_t *)_str;
uint32_t uc = 0;
int length;
*out = 0;
if ((length = utf8_charlen(str, str_len)) < 0)
return -1;
switch (length) {
case 1:
uc = str[0];
break;
case 2:
uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F);
if (uc < 0x80) uc = -1;
break;
case 3:
uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6)
+ (str[2] & 0x3F);
if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) ||
(uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1;
break;
case 4:
uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12)
+ ((str[2] & 0x3F) << 6) + (str[3] & 0x3F);
if (uc < 0x10000 || uc >= 0x110000) uc = -1;
break;
default:
return -1;
}
if ((uc & 0xFFFF) >= 0xFFFE)
return -1;
*out = uc;
return length;
}
size_t git_utf8_char_length(const char *_str, size_t str_len)
{
const uint8_t *str = (const uint8_t *)_str;
size_t offset = 0, count = 0;
while (offset < str_len) {
int length = utf8_charlen(str + offset, str_len - offset);
if (length < 0)
length = 1;
offset += length;
count++;
}
return count;
}
size_t git_utf8_valid_buf_length(const char *_str, size_t str_len)
{
const uint8_t *str = (const uint8_t *)_str;
size_t offset = 0;
while (offset < str_len) {
int length = utf8_charlen(str + offset, str_len - offset);
if (length < 0)
break;
offset += length;
}
return offset;
}
/*
* 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_utf8_h__
#define INCLUDE_utf8_h__
#include "common.h"
/*
* Iterate through an UTF-8 string, yielding one codepoint at a time.
*
* @param out pointer where to store the current codepoint
* @param str current position in the string
* @param str_len size left in the string
* @return length in bytes of the read codepoint; -1 if the codepoint was invalid
*/
extern int git_utf8_iterate(uint32_t *out, const char *str, size_t str_len);
/**
* Returns the number of characters in the given string.
*
* This function will count invalid codepoints; if any given byte is
* not part of a valid UTF-8 codepoint, then it will be counted toward
* the length in characters.
*
* In other words:
* 0x24 (U+0024 "$") has length 1
* 0xc2 0xa2 (U+00A2 "¢") has length 1
* 0x24 0xc2 0xa2 (U+0024 U+00A2 "$¢") has length 2
* 0xf0 0x90 0x8d 0x88 (U+10348 "𐍈") has length 1
* 0x24 0xc0 0xc1 0x34 (U+0024 <invalid> <invalid> "4) has length 4
*
* @param str string to scan
* @param str_len size of the string
* @return length in characters of the string
*/
extern size_t git_utf8_char_length(const char *str, size_t str_len);
/**
* Iterate through an UTF-8 string and stops after finding any invalid UTF-8
* codepoints.
*
* @param str string to scan
* @param str_len size of the string
* @return length in bytes of the string that contains valid data
*/
extern size_t git_utf8_valid_buf_length(const char *str, size_t str_len);
#endif
......@@ -734,123 +734,6 @@ void git__qsort_r(
#endif
}
/*
* git__utf8_iterate is taken from the utf8proc project,
* http://www.public-software-group.org/utf8proc
*
* Copyright (c) 2009 Public Software Group e. V., Berlin, Germany
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the ""Software""),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
static const int8_t utf8proc_utf8class[256] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0
};
static int util_utf8_charlen(const uint8_t *str, size_t str_len)
{
size_t length, i;
length = utf8proc_utf8class[str[0]];
if (!length)
return -1;
if (str_len > 0 && length > str_len)
return -1;
for (i = 1; i < length; i++) {
if ((str[i] & 0xC0) != 0x80)
return -1;
}
return (int)length;
}
int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst)
{
int length;
int32_t uc = -1;
*dst = -1;
length = util_utf8_charlen(str, str_len);
if (length < 0)
return -1;
switch (length) {
case 1:
uc = str[0];
break;
case 2:
uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F);
if (uc < 0x80) uc = -1;
break;
case 3:
uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6)
+ (str[2] & 0x3F);
if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) ||
(uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1;
break;
case 4:
uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12)
+ ((str[2] & 0x3F) << 6) + (str[3] & 0x3F);
if (uc < 0x10000 || uc >= 0x110000) uc = -1;
break;
}
if (uc < 0 || ((uc & 0xFFFF) >= 0xFFFE))
return -1;
*dst = uc;
return length;
}
size_t git__utf8_valid_buf_length(const uint8_t *str, size_t str_len)
{
size_t offset = 0;
while (offset < str_len) {
int length = util_utf8_charlen(str + offset, str_len - offset);
if (length < 0)
break;
offset += length;
}
return offset;
}
#ifdef GIT_WIN32
int git__getenv(git_buf *out, const char *name)
{
......
......@@ -317,27 +317,6 @@ extern int git__date_rfc2822_fmt(char *out, size_t len, const git_time *date);
extern size_t git__unescape(char *str);
/*
* Iterate through an UTF-8 string, yielding one
* codepoint at a time.
*
* @param str current position in the string
* @param str_len size left in the string; -1 if the string is NULL-terminated
* @param dst pointer where to store the current codepoint
* @return length in bytes of the read codepoint; -1 if the codepoint was invalid
*/
extern int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst);
/*
* Iterate through an UTF-8 string and stops after finding any invalid UTF-8
* codepoints.
*
* @param str string to scan
* @param str_len size of the string
* @return length in bytes of the string that contains valid data
*/
extern size_t git__utf8_valid_buf_length(const uint8_t *str, size_t str_len);
/*
* Safely zero-out memory, making sure that the compiler
* doesn't optimize away the operation.
*/
......
......@@ -135,6 +135,9 @@ static int open_worktree_dir(git_worktree **out, const char *parent, const char
goto out;
}
if ((error = git_path_validate_workdir(NULL, dir)) < 0)
goto out;
if ((wt = git__calloc(1, sizeof(*wt))) == NULL) {
error = -1;
goto out;
......@@ -264,14 +267,14 @@ int git_worktree_validate(const git_worktree *wt)
wt->commondir_path);
return GIT_ERROR;
}
if (!git_path_exists(wt->worktree_path)) {
git_error_set(GIT_ERROR_WORKTREE,
"worktree directory '%s' does not exist",
wt->worktree_path);
return GIT_ERROR;
}
return 0;
}
......
......@@ -474,6 +474,15 @@
"-asparagus which had been laid by, boil it until these last articles are\n" \
"-sufficiently done, thicken with flour, butter and milk, and serve it up.\n"
#define DIFF_ADD_INVALID_FILENAME \
"diff --git a/.git/hello_world.txt b/.git/hello_world.txt\n" \
"new file mode 100644\n" \
"index 0000000..f75ba05\n" \
"--- /dev/null\n" \
"+++ b/.git/hello_world.txt\n" \
"@@ -0,0 +1 @@\n" \
"+Hello, world.\n"
void validate_apply_workdir(
git_repository *repo,
struct merge_index_entry *workdir_entries,
......
......@@ -734,3 +734,14 @@ void test_apply_both__cant_remove_file_twice(void)
git_diff_free(diff);
}
void test_apply_both__cant_add_invalid_filename(void)
{
git_diff *diff;
cl_git_pass(git_diff_from_buffer(&diff, DIFF_ADD_INVALID_FILENAME,
strlen(DIFF_ADD_INVALID_FILENAME)));
cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL));
git_diff_free(diff);
}
......@@ -13,7 +13,7 @@ void test_attr_lookup__simple(void)
cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path);
cl_assert(file->rules.length == 1);
cl_git_pass(git_attr_path__init(&path, "test", NULL, GIT_DIR_FLAG_UNKNOWN));
cl_git_pass(git_attr_path__init(&path, NULL, "test", NULL, GIT_DIR_FLAG_UNKNOWN));
cl_assert_equal_s("test", path.path);
cl_assert_equal_s("test", path.basename);
cl_assert(!path.is_dir);
......@@ -36,7 +36,7 @@ static void run_test_cases(git_attr_file *file, struct attr_expected *cases, int
int error;
for (c = cases; c->path != NULL; c++) {
cl_git_pass(git_attr_path__init(&path, c->path, NULL, GIT_DIR_FLAG_UNKNOWN));
cl_git_pass(git_attr_path__init(&path, NULL, c->path, NULL, GIT_DIR_FLAG_UNKNOWN));
if (force_dir)
path.is_dir = 1;
......@@ -133,7 +133,7 @@ void test_attr_lookup__match_variants(void)
cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path);
cl_assert(file->rules.length == 10);
cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL, GIT_DIR_FLAG_UNKNOWN));
cl_git_pass(git_attr_path__init(&path, NULL, "/testing/for/pat0", NULL, GIT_DIR_FLAG_UNKNOWN));
cl_assert_equal_s("pat0", path.basename);
run_test_cases(file, cases, 0);
......
#include "clar_libgit2.h"
void test_core_utf8__char_length(void)
{
cl_assert_equal_i(0, git_utf8_char_length("", 0));
cl_assert_equal_i(1, git_utf8_char_length("$", 1));
cl_assert_equal_i(5, git_utf8_char_length("abcde", 5));
cl_assert_equal_i(1, git_utf8_char_length("\xc2\xa2", 2));
cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2\xa2", 3));
cl_assert_equal_i(1, git_utf8_char_length("\xf0\x90\x8d\x88", 4));
/* uncontinued character counted as single characters */
cl_assert_equal_i(2, git_utf8_char_length("\x24\xc2", 2));
cl_assert_equal_i(3, git_utf8_char_length("\x24\xc2\xc2\xa2", 4));
/* invalid characters are counted as single characters */
cl_assert_equal_i(4, git_utf8_char_length("\x24\xc0\xc0\x34", 4));
cl_assert_equal_i(4, git_utf8_char_length("\x24\xf5\xfd\xc2", 4));
}
......@@ -113,8 +113,8 @@ void test_path_dotgit__dotgit_modules(void)
void test_path_dotgit__dotgit_modules_symlink(void)
{
cl_assert_equal_b(true, git_path_isvalid(NULL, ".gitmodules", 0, GIT_PATH_REJECT_DOT_GIT_HFS|GIT_PATH_REJECT_DOT_GIT_NTFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".gitmodules . .::$DATA", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS));
cl_assert_equal_b(true, git_path_validate(NULL, ".gitmodules", 0, GIT_PATH_REJECT_DOT_GIT_HFS|GIT_PATH_REJECT_DOT_GIT_NTFS));
cl_assert_equal_b(false, git_path_validate(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_validate(NULL, ".gitmodules", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS));
cl_assert_equal_b(false, git_path_validate(NULL, ".gitmodules . .::$DATA", S_IFLNK, GIT_PATH_REJECT_DOT_GIT_NTFS));
}
......@@ -42,3 +42,43 @@ void test_refs_basic__reference_realloc(void)
git_reference_free(new_ref);
git_reference_free(ref);
}
void test_refs_basic__longpaths(void)
{
#ifdef GIT_WIN32
const char *base;
size_t base_len, extra_len;
ssize_t remain_len, i;
git_buf refname = GIT_BUF_INIT;
git_reference *one = NULL, *two = NULL;
git_oid id;
cl_git_pass(git_oid_fromstr(&id, "099fabac3a9ea935598528c27f866e34089c2eff"));
base = git_repository_path(g_repo);
base_len = git_utf8_char_length(base, strlen(base));
extra_len = CONST_STRLEN("logs/refs/heads/") + CONST_STRLEN(".lock");
remain_len = (ssize_t)MAX_PATH - (base_len + extra_len);
cl_assert(remain_len > 0);
cl_git_pass(git_buf_puts(&refname, "refs/heads/"));
for (i = 0; i < remain_len; i++) {
cl_git_pass(git_buf_putc(&refname, 'a'));
}
/*
* The full path to the reflog lockfile is 260 characters,
* this is permitted.
*/
cl_git_pass(git_reference_create(&one, g_repo, refname.ptr, &id, 0, NULL));
/* Adding one more character gives us a path that is too long. */
cl_git_pass(git_buf_putc(&refname, 'z'));
cl_git_fail(git_reference_create(&two, g_repo, refname.ptr, &id, 0, NULL));
git_reference_free(one);
git_reference_free(two);
#endif
}
......@@ -704,3 +704,35 @@ void test_repo_init__defaultbranch_config_empty(void)
git_reference_free(head);
}
void test_repo_init__longpath(void)
{
#ifdef GIT_WIN32
size_t padding = CONST_STRLEN("objects/pack/pack-.pack.lock") + GIT_OID_HEXSZ;
size_t max, i;
git_buf path = GIT_BUF_INIT;
git_repository *one = NULL, *two = NULL;
/*
* Files within repositories need to fit within MAX_PATH;
* that means a repo path must be at most (MAX_PATH - 18).
*/
cl_git_pass(git_buf_puts(&path, clar_sandbox_path()));
cl_git_pass(git_buf_putc(&path, '/'));
max = ((MAX_PATH) - path.size) - padding;
for (i = 0; i < max - 1; i++)
cl_git_pass(git_buf_putc(&path, 'a'));
cl_git_pass(git_repository_init(&one, path.ptr, 1));
/* Paths longer than this are rejected */
cl_git_pass(git_buf_putc(&path, 'z'));
cl_git_fail(git_repository_init(&two, path.ptr, 1));
git_repository_free(one);
git_repository_free(two);
git_buf_dispose(&path);
#endif
}
......@@ -4,6 +4,7 @@
#include "clone.h"
#include "buffer.h"
#include "futils.h"
#include "repository.h"
static git_buf path = GIT_BUF_INIT;
......@@ -29,6 +30,7 @@ void test_win32_longpath__initialize(void)
void test_win32_longpath__cleanup(void)
{
git_buf_dispose(&path);
cl_git_sandbox_cleanup();
}
#ifdef GIT_WIN32
......@@ -60,3 +62,17 @@ void test_win32_longpath__errmsg_on_checkout(void)
assert_name_too_long();
#endif
}
void test_win32_longpath__workdir_path_validated(void)
{
#ifdef GIT_WIN32
git_repository *repo = cl_git_sandbox_init("testrepo");
git_buf out = GIT_BUF_INIT;
cl_git_pass(git_repository_workdir_path(&out, repo, "a.txt"));
/* even if the repo path is a drive letter, this is too long */
cl_git_fail(git_repository_workdir_path(&out, repo, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.txt"));
cl_assert(git__prefixcmp(git_error_last()->message, "path too long") == 0);
#endif
}
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