Commit bef02d3e638ce8c95c9e63622b46d87a0f8ee2b2

Edward Thomson 2021-11-01T10:57:28

fs_path: introduce `str_is_valid` Provide a mechanism for users to limit the number of characters that are examined; `git_fs_path_str_is_valid` and friends will only examine up to `str->size` bytes. `git_fs_path_is_valid` delegates to these new functions by passing `SIZE_MAX` (instead of doing a `strlen`), which is a sentinel value meaning "look for a NUL terminator".

diff --git a/src/fs_path.c b/src/fs_path.c
index ceba02d..fa27a6e 100644
--- a/src/fs_path.c
+++ b/src/fs_path.c
@@ -1634,16 +1634,17 @@ static bool validate_component(
 	return true;
 }
 
-bool git_fs_path_is_valid_ext(
-	const char *path,
+bool git_fs_path_is_valid_str_ext(
+	const git_str *path,
 	unsigned int flags,
 	bool (*validate_char_cb)(char ch, void *payload),
 	bool (*validate_component_cb)(const char *component, size_t len, void *payload),
 	void *payload)
 {
 	const char *start, *c;
+	size_t len = 0;
 
-	for (start = c = path; *c; c++) {
+	for (start = c = path->ptr; *c && len < path->size; c++, len++) {
 		if (!validate_char(*c, flags))
 			return false;
 
@@ -1663,6 +1664,15 @@ bool git_fs_path_is_valid_ext(
 		start = c + 1;
 	}
 
+	/*
+	 * We want to support paths specified as either `const char *`
+	 * or `git_str *`; we pass size as `SIZE_MAX` when we use a
+	 * `const char *` to avoid a `strlen`.  Ensure that we didn't
+	 * have a NUL in the buffer if there was a non-SIZE_MAX length.
+	 */
+	if (path->size != SIZE_MAX && len != path->size)
+		return false;
+
 	if (!validate_component(start, (c - start), flags))
 		return false;
 
@@ -1673,11 +1683,6 @@ bool git_fs_path_is_valid_ext(
 	return true;
 }
 
-bool git_fs_path_is_valid(const char *path, unsigned int flags)
-{
-	return git_fs_path_is_valid_ext(path, flags, NULL, NULL, NULL);
-}
-
 #ifdef GIT_WIN32
 GIT_INLINE(bool) should_validate_longpaths(git_repository *repo)
 {
diff --git a/src/fs_path.h b/src/fs_path.h
index 991e7cd..2f4bc9f 100644
--- a/src/fs_path.h
+++ b/src/fs_path.h
@@ -621,25 +621,55 @@ extern int git_fs_path_from_url_or_path(git_str *local_path_out, const char *url
 #endif
 
 /**
- * Validate a filesystem path.  This ensures that the given path is legal
- * and does not contain any "unsafe" components like path traversal ('.'
- * or '..'), characters that are inappropriate for lesser filesystems
- * (trailing ' ' or ':' characters), or filenames ("component names")
- * that are not supported ('AUX', 'COM1").
- */
-extern bool git_fs_path_is_valid(const char *path, unsigned int flags);
-
-/**
  * Validate a filesystem path; with custom callbacks per-character and
  * per-path component.
  */
-extern bool git_fs_path_is_valid_ext(
-	const char *path,
+extern bool git_fs_path_is_valid_str_ext(
+	const git_str *path,
 	unsigned int flags,
 	bool (*validate_char_cb)(char ch, void *payload),
 	bool (*validate_component_cb)(const char *component, size_t len, void *payload),
 	void *payload);
 
+GIT_INLINE(bool) git_fs_path_is_valid_ext(
+	const char *path,
+	unsigned int flags,
+	bool (*validate_char_cb)(char ch, void *payload),
+	bool (*validate_component_cb)(const char *component, size_t len, void *payload),
+	void *payload)
+{
+	const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX);
+	return git_fs_path_is_valid_str_ext(
+		&str,
+		flags,
+		validate_char_cb,
+		validate_component_cb,
+		payload);
+}
+
+/**
+ * Validate a filesystem path.  This ensures that the given path is legal
+ * and does not contain any "unsafe" components like path traversal ('.'
+ * or '..'), characters that are inappropriate for lesser filesystems
+ * (trailing ' ' or ':' characters), or filenames ("component names")
+ * that are not supported ('AUX', 'COM1").
+ */
+GIT_INLINE(bool) git_fs_path_is_valid(
+	const char *path,
+	unsigned int flags)
+{
+	const git_str str = GIT_STR_INIT_CONST(path, SIZE_MAX);
+	return git_fs_path_is_valid_str_ext(&str, flags, NULL, NULL, NULL);
+}
+
+/** Validate a filesystem path in a `git_str`. */
+GIT_INLINE(bool) git_fs_path_is_valid_str(
+	const git_str *path,
+	unsigned int flags)
+{
+	return git_fs_path_is_valid_str_ext(path, flags, NULL, NULL, NULL);
+}
+
 /**
  * Validate an on-disk path, taking into account that it will have a
  * suffix appended (eg, `.lock`).
diff --git a/tests/path/core.c b/tests/path/core.c
index f48a769..6fa0450 100644
--- a/tests/path/core.c
+++ b/tests/path/core.c
@@ -64,6 +64,33 @@ void test_path_core__isvalid_standard(void)
 	cl_assert_equal_b(true, git_fs_path_is_valid("foo/bar/.file", 0));
 }
 
+/* Ensure that `is_valid_str` only reads str->size bytes */
+void test_path_core__isvalid_standard_str(void)
+{
+	git_str str = GIT_STR_INIT_CONST("foo/bar//zap", 0);
+
+	str.size = 0;
+	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, 0));
+
+	str.size = 3;
+	cl_assert_equal_b(true, git_fs_path_is_valid_str(&str, 0));
+
+	str.size = 4;
+	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, 0));
+
+	str.size = 5;
+	cl_assert_equal_b(true, git_fs_path_is_valid_str(&str, 0));
+
+	str.size = 7;
+	cl_assert_equal_b(true, git_fs_path_is_valid_str(&str, 0));
+
+	str.size = 8;
+	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, 0));
+
+	str.size = strlen(str.ptr);
+	cl_assert_equal_b(false, git_fs_path_is_valid_str(&str, 0));
+}
+
 void test_path_core__isvalid_empty_dir_component(void)
 {
 	cl_assert_equal_b(false, git_fs_path_is_valid("foo//bar", 0));