summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/fileops.c76
-rw-r--r--src/fileops.h24
-rw-r--r--tests/t0005-path.c73
3 files changed, 173 insertions, 0 deletions
diff --git a/src/fileops.c b/src/fileops.c
index 1d680f354..1c480e727 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -364,5 +364,81 @@ 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 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;
}
diff --git a/src/fileops.h b/src/fileops.h
index fa446e36a..d562dc7ed 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