Merge branch 'dir-path-prettifying' of https://github.com/nulltoken/libgit2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
diff --git a/src/fileops.c b/src/fileops.c
index 1d680f3..1c480e7 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 fa446e3..d562dc7 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