diff --git a/src/fileops.c b/src/fileops.c index 00eb4fc37..d23246de6 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -456,3 +456,30 @@ int git_prettify_dir_path(char *buffer_out, const char *path) return GIT_SUCCESS; } + +int git_prettify_file_path(char *buffer_out, const char *path) +{ + int error, path_len, i; + const char* pattern = "/.."; + + path_len = strlen(path); + + /* Let's make sure the filename doesn't end with "/", "/." or "/.." */ + for (i = 1; path_len > i && i < 4; i++) { + if (!strncmp(path + path_len - i, pattern, i)) + return GIT_ERROR; + } + + error = git_prettify_dir_path(buffer_out, path); + if (error < GIT_SUCCESS) + return error; + + path_len = strlen(buffer_out); + if (path_len < 2) + return GIT_ERROR; + + /* Remove the trailing slash */ + buffer_out[path_len - 1] = '\0'; + + return GIT_SUCCESS; +} diff --git a/src/fileops.h b/src/fileops.h index d562dc7ed..1f179603c 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -155,4 +155,26 @@ extern int gitfo_close_cached(gitfo_cache *ioc); */ GIT_EXTERN(int) git_prettify_dir_path(char *buffer_out, const char *path); +/** + * Clean up a provided absolute or relative file path. + * + * This prettification relies on basic operations such as coalescing + * multiple forward slashes into a single slash, removing '.' and + * './' current directory segments, and removing parent directory + * whenever '..' is encountered. + * + * For instance, this will turn "d1/s1///s2/..//../s3" into "d1/s3". + * + * This only performs a string based analysis of the path. + * No checks are done to make sure the path actually makes sense from + * the file system perspective. + * + * @param buffer_out buffer to populate with the normalized path. + * @param path file path to clean up. + * @return + * - GIT_SUCCESS on success; + * - GIT_ERROR when the input path is invalid or escapes the current directory. + */ +GIT_EXTERN(int) git_prettify_file_path(char *buffer_out, const char *path); + #endif /* INCLUDE_fileops_h__ */ diff --git a/tests/t0005-path.c b/tests/t0005-path.c index dc2ccc916..087373c6d 100644 --- a/tests/t0005-path.c +++ b/tests/t0005-path.c @@ -1,12 +1,14 @@ #include "test_lib.h" #include "fileops.h" -static int ensure_normalized(const char *input_path, const char *expected_path) +typedef int (normalize_path)(char *, const char *); + +static int ensure_normalized(const char *input_path, const char *expected_path, normalize_path normalizer) { int error = GIT_SUCCESS; char buffer_out[GIT_PATH_MAX]; - error = git_prettify_dir_path(buffer_out, input_path); + error = normalizer(buffer_out, input_path); if (error < GIT_SUCCESS) return error; @@ -19,75 +21,165 @@ static int ensure_normalized(const char *input_path, const char *expected_path) return error; } -BEGIN_TEST(path_prettifying) - must_pass(ensure_normalized("./testrepo.git", "testrepo.git/")); - must_pass(ensure_normalized("./.git", ".git/")); - must_pass(ensure_normalized("./git.", "git./")); - must_pass(ensure_normalized("git./", "git./")); - must_pass(ensure_normalized("", "")); - must_pass(ensure_normalized(".", "")); - must_pass(ensure_normalized("./", "")); - must_pass(ensure_normalized("./.", "")); - must_fail(ensure_normalized("./..", NULL)); - must_fail(ensure_normalized("../.", NULL)); - must_fail(ensure_normalized("./.././/", NULL)); - must_pass(ensure_normalized("dir/..", "")); - must_pass(ensure_normalized("dir/sub/../..", "")); - must_pass(ensure_normalized("dir/sub/..///..", "")); - must_pass(ensure_normalized("dir/sub///../..", "")); - must_pass(ensure_normalized("dir/sub///..///..", "")); - must_fail(ensure_normalized("dir/sub/../../..", NULL)); - must_pass(ensure_normalized("dir", "dir/")); - must_pass(ensure_normalized("dir//", "dir/")); - must_pass(ensure_normalized("./dir", "dir/")); - must_pass(ensure_normalized("dir/.", "dir/")); - must_pass(ensure_normalized("dir///./", "dir/")); - must_pass(ensure_normalized("dir/sub/..", "dir/")); - must_pass(ensure_normalized("dir//sub/..", "dir/")); - must_pass(ensure_normalized("dir//sub/../", "dir/")); - must_pass(ensure_normalized("dir/sub/../", "dir/")); - must_pass(ensure_normalized("dir/sub/../.", "dir/")); - must_pass(ensure_normalized("dir/s1/../s2/", "dir/s2/")); - must_pass(ensure_normalized("d1/s1///s2/..//../s3/", "d1/s3/")); - must_pass(ensure_normalized("d1/s1//../s2/../../d2", "d2/")); - must_pass(ensure_normalized("dir/sub/../", "dir/")); - must_fail(ensure_normalized("....", NULL)); - must_fail(ensure_normalized("...", NULL)); - must_fail(ensure_normalized("./...", NULL)); - must_fail(ensure_normalized("d1/...", NULL)); - must_fail(ensure_normalized("d1/.../", NULL)); - must_fail(ensure_normalized("d1/.../d2", NULL)); +static int ensure_dir_path_normalized(const char *input_path, const char *expected_path) +{ + return ensure_normalized(input_path, expected_path, git_prettify_dir_path); +} - must_pass(ensure_normalized("/./testrepo.git", "/testrepo.git/")); - must_pass(ensure_normalized("/./.git", "/.git/")); - must_pass(ensure_normalized("/./git.", "/git./")); - must_pass(ensure_normalized("/git./", "/git./")); - must_pass(ensure_normalized("/", "/")); - must_pass(ensure_normalized("//", "/")); - must_pass(ensure_normalized("///", "/")); - must_pass(ensure_normalized("/.", "/")); - must_pass(ensure_normalized("/./", "/")); - must_fail(ensure_normalized("/./..", NULL)); - must_fail(ensure_normalized("/../.", NULL)); - must_fail(ensure_normalized("/./.././/", NULL)); - must_pass(ensure_normalized("/dir/..", "/")); - must_pass(ensure_normalized("/dir/sub/../..", "/")); - must_fail(ensure_normalized("/dir/sub/../../..", NULL)); - must_pass(ensure_normalized("/dir", "/dir/")); - must_pass(ensure_normalized("/dir//", "/dir/")); - must_pass(ensure_normalized("/./dir", "/dir/")); - must_pass(ensure_normalized("/dir/.", "/dir/")); - must_pass(ensure_normalized("/dir///./", "/dir/")); - must_pass(ensure_normalized("/dir//sub/..", "/dir/")); - must_pass(ensure_normalized("/dir/sub/../", "/dir/")); - must_pass(ensure_normalized("//dir/sub/../.", "/dir/")); - must_pass(ensure_normalized("/dir/s1/../s2/", "/dir/s2/")); - must_pass(ensure_normalized("/d1/s1///s2/..//../s3/", "/d1/s3/")); - must_pass(ensure_normalized("/d1/s1//../s2/../../d2", "/d2/")); - must_fail(ensure_normalized("/....", NULL)); - must_fail(ensure_normalized("/...", NULL)); - must_fail(ensure_normalized("/./...", NULL)); - must_fail(ensure_normalized("/d1/...", NULL)); - must_fail(ensure_normalized("/d1/.../", NULL)); - must_fail(ensure_normalized("/d1/.../d2", NULL)); +static int ensure_file_path_normalized(const char *input_path, const char *expected_path) +{ + return ensure_normalized(input_path, expected_path, git_prettify_file_path); +} + +BEGIN_TEST(file_path_prettifying) + must_pass(ensure_file_path_normalized("a", "a")); + must_pass(ensure_file_path_normalized("./testrepo.git", "testrepo.git")); + must_pass(ensure_file_path_normalized("./.git", ".git")); + must_pass(ensure_file_path_normalized("./git.", "git.")); + must_fail(ensure_file_path_normalized("git./", NULL)); + must_fail(ensure_file_path_normalized("", NULL)); + must_fail(ensure_file_path_normalized(".", NULL)); + must_fail(ensure_file_path_normalized("./", NULL)); + must_fail(ensure_file_path_normalized("./.", NULL)); + must_fail(ensure_file_path_normalized("./..", NULL)); + must_fail(ensure_file_path_normalized("../.", NULL)); + must_fail(ensure_file_path_normalized("./.././/", NULL)); + must_fail(ensure_file_path_normalized("dir/..", NULL)); + must_fail(ensure_file_path_normalized("dir/sub/../..", NULL)); + must_fail(ensure_file_path_normalized("dir/sub/..///..", NULL)); + must_fail(ensure_file_path_normalized("dir/sub///../..", NULL)); + must_fail(ensure_file_path_normalized("dir/sub///..///..", NULL)); + must_fail(ensure_file_path_normalized("dir/sub/../../..", NULL)); + must_pass(ensure_file_path_normalized("dir", "dir")); + must_fail(ensure_file_path_normalized("dir//", NULL)); + must_pass(ensure_file_path_normalized("./dir", "dir")); + must_fail(ensure_file_path_normalized("dir/.", NULL)); + must_fail(ensure_file_path_normalized("dir///./", NULL)); + must_fail(ensure_file_path_normalized("dir/sub/..", NULL)); + must_fail(ensure_file_path_normalized("dir//sub/..",NULL)); + must_fail(ensure_file_path_normalized("dir//sub/../", NULL)); + must_fail(ensure_file_path_normalized("dir/sub/../", NULL)); + must_fail(ensure_file_path_normalized("dir/sub/../.", NULL)); + must_fail(ensure_file_path_normalized("dir/s1/../s2/", NULL)); + must_fail(ensure_file_path_normalized("d1/s1///s2/..//../s3/", NULL)); + must_pass(ensure_file_path_normalized("d1/s1//../s2/../../d2", "d2")); + must_fail(ensure_file_path_normalized("dir/sub/../", NULL)); + must_fail(ensure_file_path_normalized("....", NULL)); + must_fail(ensure_file_path_normalized("...", NULL)); + must_fail(ensure_file_path_normalized("./...", NULL)); + must_fail(ensure_file_path_normalized("d1/...", NULL)); + must_fail(ensure_file_path_normalized("d1/.../", NULL)); + must_fail(ensure_file_path_normalized("d1/.../d2", NULL)); + + must_pass(ensure_file_path_normalized("/a", "/a")); + must_pass(ensure_file_path_normalized("/./testrepo.git", "/testrepo.git")); + must_pass(ensure_file_path_normalized("/./.git", "/.git")); + must_pass(ensure_file_path_normalized("/./git.", "/git.")); + must_fail(ensure_file_path_normalized("/git./", NULL)); + must_fail(ensure_file_path_normalized("/", NULL)); + must_fail(ensure_file_path_normalized("/.", NULL)); + must_fail(ensure_file_path_normalized("/./", NULL)); + must_fail(ensure_file_path_normalized("/./.", NULL)); + must_fail(ensure_file_path_normalized("/./..", NULL)); + must_fail(ensure_file_path_normalized("/../.", NULL)); + must_fail(ensure_file_path_normalized("/./.././/", NULL)); + must_fail(ensure_file_path_normalized("/dir/..", NULL)); + must_fail(ensure_file_path_normalized("/dir/sub/../..", NULL)); + must_fail(ensure_file_path_normalized("/dir/sub/..///..", NULL)); + must_fail(ensure_file_path_normalized("/dir/sub///../..", NULL)); + must_fail(ensure_file_path_normalized("/dir/sub///..///..", NULL)); + must_fail(ensure_file_path_normalized("/dir/sub/../../..", NULL)); + must_pass(ensure_file_path_normalized("/dir", "/dir")); + must_fail(ensure_file_path_normalized("/dir//", NULL)); + must_pass(ensure_file_path_normalized("/./dir", "/dir")); + must_fail(ensure_file_path_normalized("/dir/.", NULL)); + must_fail(ensure_file_path_normalized("/dir///./", NULL)); + must_fail(ensure_file_path_normalized("/dir/sub/..", NULL)); + must_fail(ensure_file_path_normalized("/dir//sub/..",NULL)); + must_fail(ensure_file_path_normalized("/dir//sub/../", NULL)); + must_fail(ensure_file_path_normalized("/dir/sub/../", NULL)); + must_fail(ensure_file_path_normalized("/dir/sub/../.", NULL)); + must_fail(ensure_file_path_normalized("/dir/s1/../s2/", NULL)); + must_fail(ensure_file_path_normalized("/d1/s1///s2/..//../s3/", NULL)); + must_pass(ensure_file_path_normalized("/d1/s1//../s2/../../d2", "/d2")); + must_fail(ensure_file_path_normalized("/dir/sub/../", NULL)); + must_fail(ensure_file_path_normalized("/....", NULL)); + must_fail(ensure_file_path_normalized("/...", NULL)); + must_fail(ensure_file_path_normalized("/./...", NULL)); + must_fail(ensure_file_path_normalized("/d1/...", NULL)); + must_fail(ensure_file_path_normalized("/d1/.../", NULL)); + must_fail(ensure_file_path_normalized("/d1/.../d2", NULL)); +END_TEST + +BEGIN_TEST(dir_path_prettifying) + must_pass(ensure_dir_path_normalized("./testrepo.git", "testrepo.git/")); + must_pass(ensure_dir_path_normalized("./.git", ".git/")); + must_pass(ensure_dir_path_normalized("./git.", "git./")); + must_pass(ensure_dir_path_normalized("git./", "git./")); + must_pass(ensure_dir_path_normalized("", "")); + must_pass(ensure_dir_path_normalized(".", "")); + must_pass(ensure_dir_path_normalized("./", "")); + must_pass(ensure_dir_path_normalized("./.", "")); + must_fail(ensure_dir_path_normalized("./..", NULL)); + must_fail(ensure_dir_path_normalized("../.", NULL)); + must_fail(ensure_dir_path_normalized("./.././/", NULL)); + must_pass(ensure_dir_path_normalized("dir/..", "")); + must_pass(ensure_dir_path_normalized("dir/sub/../..", "")); + must_pass(ensure_dir_path_normalized("dir/sub/..///..", "")); + must_pass(ensure_dir_path_normalized("dir/sub///../..", "")); + must_pass(ensure_dir_path_normalized("dir/sub///..///..", "")); + must_fail(ensure_dir_path_normalized("dir/sub/../../..", NULL)); + must_pass(ensure_dir_path_normalized("dir", "dir/")); + must_pass(ensure_dir_path_normalized("dir//", "dir/")); + must_pass(ensure_dir_path_normalized("./dir", "dir/")); + must_pass(ensure_dir_path_normalized("dir/.", "dir/")); + must_pass(ensure_dir_path_normalized("dir///./", "dir/")); + must_pass(ensure_dir_path_normalized("dir/sub/..", "dir/")); + must_pass(ensure_dir_path_normalized("dir//sub/..", "dir/")); + must_pass(ensure_dir_path_normalized("dir//sub/../", "dir/")); + must_pass(ensure_dir_path_normalized("dir/sub/../", "dir/")); + must_pass(ensure_dir_path_normalized("dir/sub/../.", "dir/")); + must_pass(ensure_dir_path_normalized("dir/s1/../s2/", "dir/s2/")); + must_pass(ensure_dir_path_normalized("d1/s1///s2/..//../s3/", "d1/s3/")); + must_pass(ensure_dir_path_normalized("d1/s1//../s2/../../d2", "d2/")); + must_pass(ensure_dir_path_normalized("dir/sub/../", "dir/")); + must_fail(ensure_dir_path_normalized("....", NULL)); + must_fail(ensure_dir_path_normalized("...", NULL)); + must_fail(ensure_dir_path_normalized("./...", NULL)); + must_fail(ensure_dir_path_normalized("d1/...", NULL)); + must_fail(ensure_dir_path_normalized("d1/.../", NULL)); + must_fail(ensure_dir_path_normalized("d1/.../d2", NULL)); + + must_pass(ensure_dir_path_normalized("/./testrepo.git", "/testrepo.git/")); + must_pass(ensure_dir_path_normalized("/./.git", "/.git/")); + must_pass(ensure_dir_path_normalized("/./git.", "/git./")); + must_pass(ensure_dir_path_normalized("/git./", "/git./")); + must_pass(ensure_dir_path_normalized("/", "/")); + must_pass(ensure_dir_path_normalized("//", "/")); + must_pass(ensure_dir_path_normalized("///", "/")); + must_pass(ensure_dir_path_normalized("/.", "/")); + must_pass(ensure_dir_path_normalized("/./", "/")); + must_fail(ensure_dir_path_normalized("/./..", NULL)); + must_fail(ensure_dir_path_normalized("/../.", NULL)); + must_fail(ensure_dir_path_normalized("/./.././/", NULL)); + must_pass(ensure_dir_path_normalized("/dir/..", "/")); + must_pass(ensure_dir_path_normalized("/dir/sub/../..", "/")); + must_fail(ensure_dir_path_normalized("/dir/sub/../../..", NULL)); + must_pass(ensure_dir_path_normalized("/dir", "/dir/")); + must_pass(ensure_dir_path_normalized("/dir//", "/dir/")); + must_pass(ensure_dir_path_normalized("/./dir", "/dir/")); + must_pass(ensure_dir_path_normalized("/dir/.", "/dir/")); + must_pass(ensure_dir_path_normalized("/dir///./", "/dir/")); + must_pass(ensure_dir_path_normalized("/dir//sub/..", "/dir/")); + must_pass(ensure_dir_path_normalized("/dir/sub/../", "/dir/")); + must_pass(ensure_dir_path_normalized("//dir/sub/../.", "/dir/")); + must_pass(ensure_dir_path_normalized("/dir/s1/../s2/", "/dir/s2/")); + must_pass(ensure_dir_path_normalized("/d1/s1///s2/..//../s3/", "/d1/s3/")); + must_pass(ensure_dir_path_normalized("/d1/s1//../s2/../../d2", "/d2/")); + must_fail(ensure_dir_path_normalized("/....", NULL)); + must_fail(ensure_dir_path_normalized("/...", NULL)); + must_fail(ensure_dir_path_normalized("/./...", NULL)); + must_fail(ensure_dir_path_normalized("/d1/...", NULL)); + must_fail(ensure_dir_path_normalized("/d1/.../", NULL)); + must_fail(ensure_dir_path_normalized("/d1/.../d2", NULL)); END_TEST