From 170d3f2fbbaf529afc209f78fc7ee88b5680eb5d Mon Sep 17 00:00:00 2001 From: nulltoken Date: Tue, 11 Jan 2011 20:12:53 +0100 Subject: [PATCH] Added git_prettify_dir_path(). Clean up a provided absolute or relative directory 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. If not empty, the returned path ends with a forward slash. 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. --- src/fileops.c | 79 +++++++++++++++++++++++++++++++++++++++++++++- src/fileops.h | 24 ++++++++++++++ tests/t0005-path.c | 73 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 tests/t0005-path.c diff --git a/src/fileops.c b/src/fileops.c index 68f45c25a..37affe00f 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -364,5 +364,82 @@ int gitfo_mkdir_recurs(const char *path, int mode) free(path_copy); return error; - } + +static int retrieve_previous_path_component_start(const char *path) +{ + int error = GIT_SUCCESS; + int offset, len, start = 0; + + len = strlen(path); + offset = len - 1; + + /* Skip leading slash */ + if (path[start] == '/') + start++; + + /* Skip trailing slash */ + if (path[offset] == '/') + offset--; + + if (offset < 0) + return GIT_ERROR; + + while (offset > start && path[offset-1] != '/') { + offset--; + } + + return offset; +} + +int git_prettify_dir_path(char *buffer_out, const char *path) +{ + int len = 0; + char *current; + const char *buffer_out_start, *buffer_end; + + buffer_out_start = buffer_out; + current = (char *)path; + buffer_end = path + strlen(path); + + while (current < buffer_end) { + /* Prevent multiple slashes from being added to the output */ + if (*current == '/' && len > 0 && buffer_out_start[len - 1] == '/') { + current++; + continue; + } + + /* Skip current directory */ + if (*current == '.') { + current++; + + /* Handle the double-dot upward directory navigation */ + if (*current == '.') { + current++; + + *buffer_out ='\0'; + len = retrieve_previous_path_component_start(buffer_out_start); + if (len < GIT_SUCCESS) + return GIT_ERROR; + + buffer_out = (char *)buffer_out_start + len; + } + + if (*current == '/') + current++; + + continue; + } + + *buffer_out++ = *current++; + len++; + } + + /* Add a trailing slash if required */ + if (len > 0 && buffer_out_start[len-1] != '/') + *buffer_out++ = '/'; + + *buffer_out = '\0'; + + return GIT_SUCCESS; +} \ No newline at end of file diff --git a/src/fileops.h b/src/fileops.h index 6656cdf43..30dc0b849 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -131,4 +131,28 @@ extern int gitfo_write_cached(gitfo_cache *ioc, void *buf, size_t len); extern int gitfo_flush_cached(gitfo_cache *ioc); extern int gitfo_close_cached(gitfo_cache *ioc); +/** + * Clean up a provided absolute or relative directory 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. + * + * If not empty, the returned path ends with a forward slash. + * + * 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 directory 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_dir_path(char *buffer_out, const char *path); + #endif /* INCLUDE_fileops_h__ */ diff --git a/tests/t0005-path.c b/tests/t0005-path.c new file mode 100644 index 000000000..b00989fca --- /dev/null +++ b/tests/t0005-path.c @@ -0,0 +1,73 @@ +#include "test_lib.h" +#include "fileops.h" + +static int ensure_normalized(const char *input_path, const char *expected_path) +{ + int error = GIT_SUCCESS; + char buffer_out[GIT_PATH_MAX]; + + error = git_prettify_dir_path(buffer_out, input_path); + if (error < GIT_SUCCESS) + return error; + + if (expected_path == NULL) + return error; + + if (strcmp(buffer_out, expected_path)) + error = GIT_ERROR; + + return error; +} + +BEGIN_TEST(path_prettifying) + 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_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/")); +END_TEST