Commit 37fc44ddff9d8d41e3a8c29c8ce06a253a6bc20f

Russell Belfer 2013-09-03T12:27:56

Merge pull request #1825 from nvloff/resolve_relative path: properly resolve relative paths

diff --git a/src/path.c b/src/path.c
index a753a73..50a990b 100644
--- a/src/path.c
+++ b/src/path.c
@@ -646,12 +646,33 @@ int git_path_resolve_relative(git_buf *path, size_t ceiling)
 			/* do nothing with singleton dot */;
 
 		else if (len == 2 && from[0] == '.' && from[1] == '.') {
-			while (to > base && to[-1] == '/') to--;
-			while (to > base && to[-1] != '/') to--;
-		}
+			/* error out if trying to up one from a hard base */
+			if (to == base && ceiling != 0) {
+				giterr_set(GITERR_INVALID,
+					"Cannot strip root component off url");
+				return -1;
+			}
+
+			/* no more path segments to strip,
+			 * use '../' as a new base path */
+			if (to == base) {
+				if (*next == '/')
+					len++;
 
-		else {
-			if (*next == '/')
+				if (to != from)
+					memmove(to, from, len);
+
+				to += len;
+				/* this is now the base, can't back up from a
+				 * relative prefix */
+				base = to;
+			} else {
+				/* back up a path segment */
+				while (to > base && to[-1] == '/') to--;
+				while (to > base && to[-1] != '/') to--;
+			}
+		} else {
+			if (*next == '/' && *from != '/')
 				len++;
 
 			if (to != from)
diff --git a/tests-clar/core/path.c b/tests-clar/core/path.c
index 407770b..d35a5bd 100644
--- a/tests-clar/core/path.c
+++ b/tests-clar/core/path.c
@@ -446,16 +446,15 @@ void test_core_path__14_apply_relative(void)
 	cl_git_pass(git_path_apply_relative(&p, "../../../../../.."));
 	cl_assert_equal_s("/this/", p.ptr);
 
-	cl_git_pass(git_path_apply_relative(&p, "../../../../../"));
+	cl_git_pass(git_path_apply_relative(&p, "../"));
 	cl_assert_equal_s("/", p.ptr);
 
-	cl_git_pass(git_path_apply_relative(&p, "../../../../.."));
-	cl_assert_equal_s("/", p.ptr);
+	cl_git_fail(git_path_apply_relative(&p, "../../.."));
 
 
 	cl_git_pass(git_buf_sets(&p, "d:/another/test"));
 
-	cl_git_pass(git_path_apply_relative(&p, "../../../../.."));
+	cl_git_pass(git_path_apply_relative(&p, "../.."));
 	cl_assert_equal_s("d:/", p.ptr);
 
 	cl_git_pass(git_path_apply_relative(&p, "from/here/to/../and/./back/."));
@@ -473,8 +472,97 @@ void test_core_path__14_apply_relative(void)
 	cl_git_pass(git_path_apply_relative(&p, ".."));
 	cl_assert_equal_s("https://my.url.com/full/path/", p.ptr);
 
-	cl_git_pass(git_path_apply_relative(&p, "../../../../../"));
+	cl_git_pass(git_path_apply_relative(&p, "../../../"));
 	cl_assert_equal_s("https://", p.ptr);
 
+
+	cl_git_pass(git_buf_sets(&p, "../../this/is/relative"));
+
+	cl_git_pass(git_path_apply_relative(&p, "../../preserves/the/prefix"));
+	cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr);
+
+	cl_git_pass(git_path_apply_relative(&p, "../../../../that"));
+	cl_assert_equal_s("../../that", p.ptr);
+
+	cl_git_pass(git_path_apply_relative(&p, "../there"));
+	cl_assert_equal_s("../../there", p.ptr);
 	git_buf_free(&p);
 }
+
+static inline void assert_resolve_relative(git_buf *buf, const char *expected, const char *path)
+{
+	cl_git_pass(git_buf_sets(buf, path));
+	cl_git_pass(git_path_resolve_relative(buf, 0));
+	cl_assert_equal_s(expected, buf->ptr);
+}
+
+void test_core_path__15_resolve_relative(void)
+{
+	git_buf buf = GIT_BUF_INIT;
+
+	assert_resolve_relative(&buf, "", "");
+	assert_resolve_relative(&buf, "", ".");
+	assert_resolve_relative(&buf, "", "./");
+	assert_resolve_relative(&buf, "..", "..");
+	assert_resolve_relative(&buf, "../", "../");
+	assert_resolve_relative(&buf, "..", "./..");
+	assert_resolve_relative(&buf, "../", "./../");
+	assert_resolve_relative(&buf, "../", "../.");
+	assert_resolve_relative(&buf, "../", ".././");
+	assert_resolve_relative(&buf, "../..", "../..");
+	assert_resolve_relative(&buf, "../../", "../../");
+
+	assert_resolve_relative(&buf, "/", "/");
+	assert_resolve_relative(&buf, "/", "/.");
+
+	assert_resolve_relative(&buf, "", "a/..");
+	assert_resolve_relative(&buf, "", "a/../");
+	assert_resolve_relative(&buf, "", "a/../.");
+
+	assert_resolve_relative(&buf, "/a", "/a");
+	assert_resolve_relative(&buf, "/a/", "/a/.");
+	assert_resolve_relative(&buf, "/", "/a/../");
+	assert_resolve_relative(&buf, "/", "/a/../.");
+	assert_resolve_relative(&buf, "/", "/a/.././");
+
+	assert_resolve_relative(&buf, "a", "a");
+	assert_resolve_relative(&buf, "a/", "a/");
+	assert_resolve_relative(&buf, "a/", "a/.");
+	assert_resolve_relative(&buf, "a/", "a/./");
+
+	assert_resolve_relative(&buf, "a/b", "a//b");
+	assert_resolve_relative(&buf, "a/b/c", "a/b/c");
+	assert_resolve_relative(&buf, "b/c", "./b/c");
+	assert_resolve_relative(&buf, "a/c", "a/./c");
+	assert_resolve_relative(&buf, "a/b/", "a/b/.");
+
+	assert_resolve_relative(&buf, "/a/b/c", "///a/b/c");
+	assert_resolve_relative(&buf, "/a/b/c", "//a/b/c");
+	assert_resolve_relative(&buf, "/", "////");
+	assert_resolve_relative(&buf, "/a", "///a");
+	assert_resolve_relative(&buf, "/", "///.");
+	assert_resolve_relative(&buf, "/", "///a/..");
+
+	assert_resolve_relative(&buf, "../../path", "../../test//../././path");
+	assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d");
+
+	cl_git_pass(git_buf_sets(&buf, "/.."));
+	cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+	cl_git_pass(git_buf_sets(&buf, "/./.."));
+	cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+	cl_git_pass(git_buf_sets(&buf, "/.//.."));
+	cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+	cl_git_pass(git_buf_sets(&buf, "/../."));
+	cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+	cl_git_pass(git_buf_sets(&buf, "/../.././../a"));
+	cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+	cl_git_pass(git_buf_sets(&buf, "////.."));
+	cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+	git_buf_free(&buf);
+}