common : normalize path to treat root separator coherently in validation
This commit is contained in:
parent
09879b214e
commit
42dd51df99
|
|
@ -692,6 +692,49 @@ bool string_parse_kv_override(const char * data, std::vector<llama_model_kv_over
|
|||
// Filesystem utils
|
||||
//
|
||||
|
||||
// Normalizes a relative filepath
|
||||
// - Replaces backslashes and forward slashes with the system path separator
|
||||
// - Trims leading './' or '.\' segments
|
||||
// - Trims leading '/' or '\' (treat 'root' as relative)
|
||||
// - Trims duplicate directory separators
|
||||
// - Does not resolve '..' segments
|
||||
// - Does not ensure the path is valid or safe
|
||||
// Use in conjunction with `fs_validate_filename`, calling `fs_validate_filename` after `fs_normalize_filepath`
|
||||
std::string fs_normalize_filepath(const std::string & path) {
|
||||
std::string result;
|
||||
result.reserve(path.size());
|
||||
|
||||
bool leading = true;
|
||||
char prev = 0;
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
char c = path[i];
|
||||
if (c == '/' || c == '\\') {
|
||||
c = DIRECTORY_SEPARATOR;
|
||||
}
|
||||
if (leading) {
|
||||
if (c == DIRECTORY_SEPARATOR) {
|
||||
continue; // Skip leading separators
|
||||
} else if (c == '.') {
|
||||
if (i + 1 < path.size()) {
|
||||
char next = path[i + 1];
|
||||
if (next == '/' || next == '\\') {
|
||||
++i;
|
||||
continue; // Skip leading dot segments
|
||||
}
|
||||
}
|
||||
}
|
||||
leading = false;
|
||||
}
|
||||
if (prev == DIRECTORY_SEPARATOR && c == DIRECTORY_SEPARATOR) {
|
||||
continue; // Skip duplicate separators
|
||||
}
|
||||
prev = c;
|
||||
result += c;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Validate if a filename or path is safe to use
|
||||
bool fs_validate_filename(const std::string & filename, bool allow_subdirs) {
|
||||
if (!filename.length()) {
|
||||
|
|
|
|||
|
|
@ -707,6 +707,7 @@ std::string string_from(const struct llama_context * ctx, const struct llama_bat
|
|||
// Filesystem utils
|
||||
//
|
||||
|
||||
std::string fs_normalize_filepath(const std::string & path);
|
||||
bool fs_validate_filename(const std::string & filename, bool allow_subdirs = false);
|
||||
bool fs_create_directory_with_parents(const std::string & path);
|
||||
bool fs_is_directory(const std::string & path);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,17 @@
|
|||
static int n_tests = 0;
|
||||
static int n_failed = 0;
|
||||
|
||||
static const char SEP = DIRECTORY_SEPARATOR;
|
||||
|
||||
static void test_normalize(const char * desc, const std::string & expected, const std::string & input) {
|
||||
std::string result = fs_normalize_filepath(input);
|
||||
n_tests++;
|
||||
if (result != expected) {
|
||||
n_failed++;
|
||||
printf(" FAIL: %s (got \"%s\", expected \"%s\")\n", desc, result.c_str(), expected.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void test(const char * desc, bool expected, const std::string & filename, bool allow_subdirs = false) {
|
||||
bool result = fs_validate_filename(filename, allow_subdirs);
|
||||
n_tests++;
|
||||
|
|
@ -40,6 +51,7 @@ int main(void) {
|
|||
test("leading space", false, " foo");
|
||||
test("trailing space", false, "foo ");
|
||||
test("trailing dot", false, "foo.");
|
||||
test("dot path", false, "./././");
|
||||
|
||||
// --- Double dots ---
|
||||
test("contains double dot", true, "foo..bar");
|
||||
|
|
@ -104,6 +116,7 @@ int main(void) {
|
|||
test("trailing slash", true, "foo/bar/", true);
|
||||
test("colon in path", false, "foo/b:r/baz", true);
|
||||
test("control char in path", false, "foo/b\nar/baz", true);
|
||||
test("dot path", false, "./././", true);
|
||||
|
||||
// --- Leading separators ---
|
||||
test("leading slash", false, "/foo/bar", true);
|
||||
|
|
@ -125,6 +138,23 @@ int main(void) {
|
|||
test("trailing space before slash", false, "bar /baz", true);
|
||||
test("trailing dot before slash", false, "bar./baz", true);
|
||||
|
||||
// --- fs_normalize_filepath ---
|
||||
test_normalize("passthrough simple", "foo.txt", "foo.txt");
|
||||
test_normalize("passthrough subdir", std::string("foo") + SEP + "bar.txt", "foo/bar.txt");
|
||||
test_normalize("backslash to sep", std::string("foo") + SEP + "bar.txt", "foo\\bar.txt");
|
||||
test_normalize("mixed separators", std::string("a") + SEP + "b" + SEP + "c", "a/b\\c");
|
||||
test_normalize("duplicate slashes", std::string("foo") + SEP + "bar", "foo//bar");
|
||||
test_normalize("duplicate backslashes", std::string("foo") + SEP + "bar", "foo\\\\bar");
|
||||
test_normalize("triple slashes", std::string("foo") + SEP + "bar", "foo///bar");
|
||||
test_normalize("leading slash stripped", "foo", "/foo");
|
||||
test_normalize("leading backslash stripped", "foo", "\\foo");
|
||||
test_normalize("multiple leading slashes", "foo", "///foo");
|
||||
test_normalize("leading dot-slash stripped", "foo", "./foo");
|
||||
test_normalize("leading dot-backslash stripped", "foo", ".\\foo");
|
||||
test_normalize("deep path normalized",
|
||||
std::string("a") + SEP + "b" + SEP + "c" + SEP + "d.txt",
|
||||
"/a//b\\c/d.txt");
|
||||
|
||||
if (n_failed) {
|
||||
printf("\n%d/%d tests failed\n", n_failed, n_tests);
|
||||
fflush(stdout);
|
||||
|
|
|
|||
|
|
@ -798,6 +798,7 @@ static void handle_media(
|
|||
}
|
||||
// load local image file
|
||||
std::string file_path = url.substr(7); // remove "file://"
|
||||
file_path = fs_normalize_filepath(file_path); // remove any leading './' and normalize separators
|
||||
raw_buffer data;
|
||||
if (!fs_validate_filename(file_path, true)) {
|
||||
throw std::invalid_argument("file path is not allowed: " + file_path);
|
||||
|
|
|
|||
Loading…
Reference in New Issue