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
|
// 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
|
// Validate if a filename or path is safe to use
|
||||||
bool fs_validate_filename(const std::string & filename, bool allow_subdirs) {
|
bool fs_validate_filename(const std::string & filename, bool allow_subdirs) {
|
||||||
if (!filename.length()) {
|
if (!filename.length()) {
|
||||||
|
|
|
||||||
|
|
@ -707,6 +707,7 @@ std::string string_from(const struct llama_context * ctx, const struct llama_bat
|
||||||
// Filesystem utils
|
// 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_validate_filename(const std::string & filename, bool allow_subdirs = false);
|
||||||
bool fs_create_directory_with_parents(const std::string & path);
|
bool fs_create_directory_with_parents(const std::string & path);
|
||||||
bool fs_is_directory(const std::string & path);
|
bool fs_is_directory(const std::string & path);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,17 @@
|
||||||
static int n_tests = 0;
|
static int n_tests = 0;
|
||||||
static int n_failed = 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) {
|
static void test(const char * desc, bool expected, const std::string & filename, bool allow_subdirs = false) {
|
||||||
bool result = fs_validate_filename(filename, allow_subdirs);
|
bool result = fs_validate_filename(filename, allow_subdirs);
|
||||||
n_tests++;
|
n_tests++;
|
||||||
|
|
@ -40,6 +51,7 @@ int main(void) {
|
||||||
test("leading space", false, " foo");
|
test("leading space", false, " foo");
|
||||||
test("trailing space", false, "foo ");
|
test("trailing space", false, "foo ");
|
||||||
test("trailing dot", false, "foo.");
|
test("trailing dot", false, "foo.");
|
||||||
|
test("dot path", false, "./././");
|
||||||
|
|
||||||
// --- Double dots ---
|
// --- Double dots ---
|
||||||
test("contains double dot", true, "foo..bar");
|
test("contains double dot", true, "foo..bar");
|
||||||
|
|
@ -104,6 +116,7 @@ int main(void) {
|
||||||
test("trailing slash", true, "foo/bar/", true);
|
test("trailing slash", true, "foo/bar/", true);
|
||||||
test("colon in path", false, "foo/b:r/baz", true);
|
test("colon in path", false, "foo/b:r/baz", true);
|
||||||
test("control char in path", false, "foo/b\nar/baz", true);
|
test("control char in path", false, "foo/b\nar/baz", true);
|
||||||
|
test("dot path", false, "./././", true);
|
||||||
|
|
||||||
// --- Leading separators ---
|
// --- Leading separators ---
|
||||||
test("leading slash", false, "/foo/bar", true);
|
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 space before slash", false, "bar /baz", true);
|
||||||
test("trailing dot 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) {
|
if (n_failed) {
|
||||||
printf("\n%d/%d tests failed\n", n_failed, n_tests);
|
printf("\n%d/%d tests failed\n", n_failed, n_tests);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
|
||||||
|
|
@ -798,6 +798,7 @@ static void handle_media(
|
||||||
}
|
}
|
||||||
// load local image file
|
// load local image file
|
||||||
std::string file_path = url.substr(7); // remove "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;
|
raw_buffer data;
|
||||||
if (!fs_validate_filename(file_path, true)) {
|
if (!fs_validate_filename(file_path, true)) {
|
||||||
throw std::invalid_argument("file path is not allowed: " + file_path);
|
throw std::invalid_argument("file path is not allowed: " + file_path);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue