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".
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
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));