Skip to content

Commit c44f568

Browse files
authored
Merge pull request libgit2#5823 from libgit2/ethomson/path_validation
Working directory path validation
2 parents cabfa3b + c15ed35 commit c44f568

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1039
-506
lines changed

docs/win32-longpaths.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
core.longpaths support
2+
======================
3+
4+
Historically, Windows has limited absolute path lengths to `MAX_PATH`
5+
(260) characters.
6+
7+
Unfortunately, 260 characters is a punishing small maximum. This is
8+
especially true for developers where dependencies may have dependencies
9+
in a folder, each dependency themselves having dependencies in a
10+
sub-folder, ad (seemingly) infinitum.
11+
12+
So although the Windows APIs _by default_ honor this 260 character
13+
maximum, you can get around this by using separate APIs. Git honors a
14+
`core.longpaths` configuration option that allows some paths on Windows
15+
to exceed these 260 character limits.
16+
17+
And because they've gone and done it, that means that libgit2 has to
18+
honor this value, too.
19+
20+
Since `core.longpaths` is a _configuration option_ that means that we
21+
need to be able to resolve a configuration - including in _the repository
22+
itself_ in order to know whether long paths should be supported.
23+
24+
Therefore, in libgit2, `core.longpaths` affects paths in working
25+
directories _only_. Paths to the repository, and to items inside the
26+
`.git` folder, must be no longer than 260 characters.
27+
28+
This definition is required to avoid a paradoxical setting: if you
29+
had a repository in a folder that was 280 characters long, how would
30+
you know whether `core.longpaths` support should be enabled? Even if
31+
`core.longpaths` was set to true in a system configuration file, the
32+
repository itself may set `core.longpaths` to false in _its_ configuration
33+
file, which you could only read if `core.longpaths` were set to true.
34+
35+
Thus, `core.longpaths` must _only_ apply to working directory items,
36+
and cannot apply to the `.git` folder or its contents.

src/attr.c

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ int git_attr_get(
6767
if (git_repository_is_bare(repo))
6868
dir_flag = GIT_DIR_FLAG_FALSE;
6969

70-
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
70+
if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
7171
return -1;
7272

7373
if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
@@ -133,7 +133,7 @@ int git_attr_get_many_with_session(
133133
if (git_repository_is_bare(repo))
134134
dir_flag = GIT_DIR_FLAG_FALSE;
135135

136-
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
136+
if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
137137
return -1;
138138

139139
if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
@@ -217,7 +217,7 @@ int git_attr_foreach(
217217
if (git_repository_is_bare(repo))
218218
dir_flag = GIT_DIR_FLAG_FALSE;
219219

220-
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
220+
if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
221221
return -1;
222222

223223
if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
@@ -523,10 +523,14 @@ static int collect_attr_files(
523523
return error;
524524

525525
/* Resolve path in a non-bare repo */
526-
if (workdir != NULL)
527-
error = git_path_find_dir(&dir, path, workdir);
528-
else
526+
if (workdir != NULL) {
527+
if (!(error = git_repository_workdir_path(&dir, repo, path)))
528+
error = git_path_find_dir(&dir);
529+
}
530+
else {
529531
error = git_path_dirname_r(&dir, path);
532+
}
533+
530534
if (error < 0)
531535
goto cleanup;
532536

src/attr_file.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ int git_attr_file__load_standalone(git_attr_file **out, const char *path)
403403

404404
if ((error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE)) < 0 ||
405405
(error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 ||
406-
(error = git_attr_cache__alloc_file_entry(&file->entry, NULL, path, &file->pool)) < 0)
406+
(error = git_attr_cache__alloc_file_entry(&file->entry, NULL, NULL, path, &file->pool)) < 0)
407407
goto out;
408408

409409
*out = file;
@@ -503,14 +503,19 @@ git_attr_assignment *git_attr_rule__lookup_assignment(
503503
}
504504

505505
int git_attr_path__init(
506-
git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
506+
git_attr_path *info,
507+
git_repository *repo,
508+
const char *path,
509+
const char *base,
510+
git_dir_flag dir_flag)
507511
{
508512
ssize_t root;
509513

510514
/* build full path as best we can */
511515
git_buf_init(&info->full, 0);
512516

513-
if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
517+
if (git_path_join_unrooted(&info->full, path, base, &root) < 0 ||
518+
git_path_validate_workdir_buf(repo, &info->full) < 0)
514519
return -1;
515520

516521
info->path = info->full.ptr + root;

src/attr_file.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,11 @@ extern git_attr_assignment *git_attr_rule__lookup_assignment(
207207
typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag;
208208

209209
extern int git_attr_path__init(
210-
git_attr_path *info, const char *path, const char *base, git_dir_flag is_dir);
211-
210+
git_attr_path *out,
211+
git_repository *repo,
212+
const char *path,
213+
const char *base,
214+
git_dir_flag is_dir);
212215
extern void git_attr_path__free(git_attr_path *info);
213216

214217
extern int git_attr_assignment__parse(

src/attrcache.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
3838

3939
int git_attr_cache__alloc_file_entry(
4040
git_attr_file_entry **out,
41+
git_repository *repo,
4142
const char *base,
4243
const char *path,
4344
git_pool *pool)
@@ -65,6 +66,9 @@ int git_attr_cache__alloc_file_entry(
6566
}
6667
memcpy(&ce->fullpath[baselen], path, pathlen);
6768

69+
if (git_path_validate_workdir_with_len(repo, ce->fullpath, pathlen + baselen) < 0)
70+
return -1;
71+
6872
ce->path = &ce->fullpath[baselen];
6973
*out = ce;
7074

@@ -79,8 +83,8 @@ static int attr_cache_make_entry(
7983
git_attr_file_entry *entry = NULL;
8084
int error;
8185

82-
if ((error = git_attr_cache__alloc_file_entry(&entry, git_repository_workdir(repo),
83-
path, &cache->pool)) < 0)
86+
if ((error = git_attr_cache__alloc_file_entry(&entry, repo,
87+
git_repository_workdir(repo), path, &cache->pool)) < 0)
8488
return error;
8589

8690
if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0)
@@ -169,7 +173,8 @@ static int attr_cache_lookup(
169173
if (base != NULL && git_path_root(filename) < 0) {
170174
git_buf *p = attr_session ? &attr_session->tmp : &path;
171175

172-
if (git_buf_joinpath(p, base, filename) < 0)
176+
if (git_buf_joinpath(p, base, filename) < 0 ||
177+
git_path_validate_workdir_buf(repo, p) < 0)
173178
return -1;
174179

175180
filename = p->ptr;

src/attrcache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ extern bool git_attr_cache__is_cached(
4444

4545
extern int git_attr_cache__alloc_file_entry(
4646
git_attr_file_entry **out,
47+
git_repository *repo,
4748
const char *base,
4849
const char *path,
4950
git_pool *pool);

src/blob.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,7 @@ int git_blob__create_from_paths(
198198
GIT_ASSERT_ARG(hint_path || !try_load_filters);
199199

200200
if (!content_path) {
201-
if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
202-
return GIT_EBAREREPO;
203-
204-
if (git_buf_joinpath(
205-
&path, git_repository_workdir(repo), hint_path) < 0)
201+
if (git_repository_workdir_path(&path, repo, hint_path) < 0)
206202
return -1;
207203

208204
content_path = path.ptr;

src/checkout.c

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,9 @@ static int checkout_target_fullpath(
329329
if (path && git_buf_puts(&data->target_path, path) < 0)
330330
return -1;
331331

332+
if (git_path_validate_workdir_buf(data->repo, &data->target_path) < 0)
333+
return -1;
334+
332335
*out = &data->target_path;
333336

334337
return 0;
@@ -1278,14 +1281,14 @@ static int checkout_verify_paths(
12781281
unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS;
12791282

12801283
if (action & CHECKOUT_ACTION__REMOVE) {
1281-
if (!git_path_isvalid(repo, delta->old_file.path, delta->old_file.mode, flags)) {
1284+
if (!git_path_validate(repo, delta->old_file.path, delta->old_file.mode, flags)) {
12821285
git_error_set(GIT_ERROR_CHECKOUT, "cannot remove invalid path '%s'", delta->old_file.path);
12831286
return -1;
12841287
}
12851288
}
12861289

12871290
if (action & ~CHECKOUT_ACTION__REMOVE) {
1288-
if (!git_path_isvalid(repo, delta->new_file.path, delta->new_file.mode, flags)) {
1291+
if (!git_path_validate(repo, delta->new_file.path, delta->new_file.mode, flags)) {
12891292
git_error_set(GIT_ERROR_CHECKOUT, "cannot checkout to invalid path '%s'", delta->new_file.path);
12901293
return -1;
12911294
}
@@ -2032,7 +2035,8 @@ static int checkout_merge_path(
20322035
const char *our_label_raw, *their_label_raw, *suffix;
20332036
int error = 0;
20342037

2035-
if ((error = git_buf_joinpath(out, git_repository_workdir(data->repo), result->path)) < 0)
2038+
if ((error = git_buf_joinpath(out, data->opts.target_directory, result->path)) < 0 ||
2039+
(error = git_path_validate_workdir_buf(data->repo, out)) < 0)
20362040
return error;
20372041

20382042
/* Most conflicts simply use the filename in the index */
@@ -2331,6 +2335,22 @@ static void checkout_data_clear(checkout_data *data)
23312335
git_attr_session__free(&data->attr_session);
23322336
}
23332337

2338+
static int validate_target_directory(checkout_data *data)
2339+
{
2340+
int error;
2341+
2342+
if ((error = git_path_validate_workdir(data->repo, data->opts.target_directory)) < 0)
2343+
return error;
2344+
2345+
if (git_path_isdir(data->opts.target_directory))
2346+
return 0;
2347+
2348+
error = checkout_mkdir(data, data->opts.target_directory, NULL,
2349+
GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR);
2350+
2351+
return error;
2352+
}
2353+
23342354
static int checkout_data_init(
23352355
checkout_data *data,
23362356
git_iterator *target,
@@ -2363,10 +2383,7 @@ static int checkout_data_init(
23632383

23642384
if (!data->opts.target_directory)
23652385
data->opts.target_directory = git_repository_workdir(repo);
2366-
else if (!git_path_isdir(data->opts.target_directory) &&
2367-
(error = checkout_mkdir(data,
2368-
data->opts.target_directory, NULL,
2369-
GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
2386+
else if ((error = validate_target_directory(data)) < 0)
23702387
goto cleanup;
23712388

23722389
if ((error = git_repository_index(&data->index, data->repo)) < 0)

src/common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
#include "thread.h"
8484
#include "integer.h"
8585
#include "assert_safe.h"
86+
#include "utf8.h"
8687

8788
/*
8889
* Include the declarations for deprecated functions; this ensures

src/config_cache.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ static struct map_data _configmaps[] = {
8686
{"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT },
8787
{"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT },
8888
{"core.fsyncobjectfiles", NULL, 0, GIT_FSYNCOBJECTFILES_DEFAULT },
89+
{"core.longpaths", NULL, 0, GIT_LONGPATHS_DEFAULT },
8990
};
9091

9192
int git_config__configmap_lookup(int *out, git_config *config, git_configmap_item item)

0 commit comments

Comments
 (0)