Commit 170d3f2fbbaf529afc209f78fc7ee88b5680eb5d

nulltoken 2011-01-11T20:12:53

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.

diff --git a/src/fileops.c b/src/fileops.c
index 68f45c2..37affe0 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 6656cdf..30dc0b8 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 0000000..b00989f
--- /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