Commit dd748dbede1a36f1e929461ecbfcde749eb685bb

Edward Thomson 2021-11-01T13:04:40

fs_path: make empty component validation optional

diff --git a/src/fs_path.c b/src/fs_path.c
index fa27a6e..483b21c 100644
--- a/src/fs_path.c
+++ b/src/fs_path.c
@@ -1599,7 +1599,7 @@ static bool validate_component(
 	unsigned int flags)
 {
 	if (len == 0)
-		return false;
+		return !(flags & GIT_FS_PATH_REJECT_EMPTY_COMPONENT);
 
 	if ((flags & GIT_FS_PATH_REJECT_TRAVERSAL) &&
 	    len == 1 && component[0] == '.')
@@ -1644,6 +1644,9 @@ bool git_fs_path_is_valid_str_ext(
 	const char *start, *c;
 	size_t len = 0;
 
+	if (!flags)
+		return true;
+
 	for (start = c = path->ptr; *c && len < path->size; c++, len++) {
 		if (!validate_char(*c, flags))
 			return false;
diff --git a/src/fs_path.h b/src/fs_path.h
index 2f4bc9f..275b3d8 100644
--- a/src/fs_path.h
+++ b/src/fs_path.h
@@ -591,7 +591,8 @@ extern bool git_fs_path_is_local_file_url(const char *file_url);
 extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url_or_path);
 
 /* Flags to determine path validity in `git_fs_path_isvalid` */
-#define GIT_FS_PATH_REJECT_TRAVERSAL          (1 << 0)
+#define GIT_FS_PATH_REJECT_EMPTY_COMPONENT    (1 << 0)
+#define GIT_FS_PATH_REJECT_TRAVERSAL          (1 << 1)
 #define GIT_FS_PATH_REJECT_SLASH              (1 << 2)
 #define GIT_FS_PATH_REJECT_BACKSLASH          (1 << 3)
 #define GIT_FS_PATH_REJECT_TRAILING_DOT       (1 << 4)
@@ -608,6 +609,7 @@ extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url
  */
 #ifdef GIT_WIN32
 # define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \
+	GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \
 	GIT_FS_PATH_REJECT_TRAVERSAL | \
 	GIT_FS_PATH_REJECT_BACKSLASH | \
 	GIT_FS_PATH_REJECT_TRAILING_DOT | \
@@ -617,6 +619,7 @@ extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url
 	GIT_FS_PATH_REJECT_NT_CHARS
 #else
 # define GIT_FS_PATH_REJECT_FILESYSTEM_DEFAULTS \
+	GIT_FS_PATH_REJECT_EMPTY_COMPONENT | \
 	GIT_FS_PATH_REJECT_TRAVERSAL
 #endif
 
diff --git a/tests/path/core.c b/tests/path/core.c
index 6fa0450..ccb328b 100644
--- a/tests/path/core.c
+++ b/tests/path/core.c
@@ -68,41 +68,58 @@ void test_path_core__isvalid_standard(void)
 void test_path_core__isvalid_standard_str(void)
 {
 	git_str str = GIT_STR_INIT_CONST("foo/bar//zap", 0);
+	unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT;
 
 	str.size = 0;
-	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, 0));
+	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, flags));
 
 	str.size = 3;
-	cl_assert_equal_b(true, git_fs_path_is_valid_str(&str, 0));
+	cl_assert_equal_b(true, git_fs_path_is_valid_str(&str, flags));
 
 	str.size = 4;
-	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, 0));
+	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, flags));
 
 	str.size = 5;
-	cl_assert_equal_b(true, git_fs_path_is_valid_str(&str, 0));
+	cl_assert_equal_b(true, git_fs_path_is_valid_str(&str, flags));
 
 	str.size = 7;
-	cl_assert_equal_b(true, git_fs_path_is_valid_str(&str, 0));
+	cl_assert_equal_b(true, git_fs_path_is_valid_str(&str, flags));
 
 	str.size = 8;
-	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, 0));
+	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, flags));
 
 	str.size = strlen(str.ptr);
-	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, 0));
+	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, flags));
 }
 
 void test_path_core__isvalid_empty_dir_component(void)
 {
-	cl_assert_equal_b(false, git_fs_path_is_valid("foo//bar", 0));
+	unsigned int flags = GIT_FS_PATH_REJECT_EMPTY_COMPONENT;
+
+	/* empty component */
+	cl_assert_equal_b(true, git_fs_path_is_valid("foo//bar", 0));
+
+	/* leading slash */
+	cl_assert_equal_b(true, git_fs_path_is_valid("/", 0));
+	cl_assert_equal_b(true, git_fs_path_is_valid("/foo", 0));
+	cl_assert_equal_b(true, git_fs_path_is_valid("/foo/bar", 0));
+
+	/* trailing slash */
+	cl_assert_equal_b(true, git_fs_path_is_valid("foo/", 0));
+	cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/", 0));
+
+
+	/* empty component */
+	cl_assert_equal_b(false, git_fs_path_is_valid("foo//bar", flags));
 
 	/* leading slash */
-	cl_assert_equal_b(false, git_fs_path_is_valid("/", 0));
-	cl_assert_equal_b(false, git_fs_path_is_valid("/foo", 0));
-	cl_assert_equal_b(false, git_fs_path_is_valid("/foo/bar", 0));
+	cl_assert_equal_b(false, git_fs_path_is_valid("/", flags));
+	cl_assert_equal_b(false, git_fs_path_is_valid("/foo", flags));
+	cl_assert_equal_b(false, git_fs_path_is_valid("/foo/bar", flags));
 
 	/* trailing slash */
-	cl_assert_equal_b(false, git_fs_path_is_valid("foo/", 0));
-	cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar/", 0));
+	cl_assert_equal_b(false, git_fs_path_is_valid("foo/", flags));
+	cl_assert_equal_b(false, git_fs_path_is_valid("foo/bar/", flags));
 }
 
 void test_path_core__isvalid_dot_and_dotdot(void)