From: Lennart Poettering Date: Tue, 16 Jun 2026 14:49:38 +0000 (+0200) Subject: string-util: introduce STRING_FILENAME_PART flag for string_is_safe() X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dbd35fdc81ac9ae28ca417c25937b352042b1e0a;p=thirdparty%2Fsystemd.git string-util: introduce STRING_FILENAME_PART flag for string_is_safe() Whenever we are validating a string that shall appear in a filename eventually we want to use filename_part_is_valid() rather than file filename_is_valid(). Let's add explicit support for that to string_is_safe(), since it's actually a really common case. --- diff --git a/src/basic/string-util.c b/src/basic/string-util.c index cfeb2e3917a..86d1f2da2c1 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -1139,6 +1139,9 @@ bool string_is_safe(const char *p, StringSafeFlags flags) { if (FLAGS_SET(flags, STRING_FILENAME) && !filename_is_valid(p)) return false; + if (FLAGS_SET(flags, STRING_FILENAME_PART) && !filename_part_is_valid(p)) + return false; + return true; } diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 6242f559312..68ab38c3211 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -229,7 +229,8 @@ typedef enum StringSafeFlags { STRING_ALLOW_QUOTES = 1 << 4, /* Allow quotes (" or ') */ STRING_ALLOW_GLOBS = 1 << 5, /* Allow globs (?, * or [) */ STRING_FILENAME = 1 << 6, /* Verify the string is valid as regular filename */ - STRING_DISALLOW_WHITESPACE = 1 << 7, /* Refuse whitespace (space, tab, newline, …) */ + STRING_FILENAME_PART = 1 << 7, /* Verify the string is valid as part of a regular filename */ + STRING_DISALLOW_WHITESPACE = 1 << 8, /* Refuse whitespace (space, tab, newline, …) */ } StringSafeFlags; bool string_is_safe(const char *p, StringSafeFlags flags) _pure_; diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 648b8fa839d..368cd64b84a 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -1622,6 +1622,20 @@ TEST(string_is_safe) { ASSERT_TRUE(string_is_safe("a\nb", STRING_ALLOW_NEWLINES)); ASSERT_FALSE(string_is_safe("a\nb", STRING_ALLOW_NEWLINES | STRING_DISALLOW_WHITESPACE)); + /* STRING_FILENAME_PART: like STRING_FILENAME, but "." and ".." are accepted; '/' still rejected. */ + ASSERT_TRUE(string_is_safe("hello", STRING_FILENAME_PART)); + ASSERT_TRUE(string_is_safe("hello.txt", STRING_FILENAME_PART)); + ASSERT_TRUE(string_is_safe("...", STRING_FILENAME_PART)); + ASSERT_TRUE(string_is_safe(".hidden", STRING_FILENAME_PART)); + ASSERT_TRUE(string_is_safe(".", STRING_FILENAME_PART)); /* accepted, unlike STRING_FILENAME */ + ASSERT_TRUE(string_is_safe("..", STRING_FILENAME_PART)); /* accepted, unlike STRING_FILENAME */ + ASSERT_FALSE(string_is_safe("", STRING_FILENAME_PART)); /* empty still rejected by default */ + ASSERT_TRUE(string_is_safe("", STRING_FILENAME_PART | STRING_ALLOW_EMPTY)); /* ... unless explicitly allowed */ + ASSERT_FALSE(string_is_safe("/", STRING_FILENAME_PART)); + ASSERT_FALSE(string_is_safe("/foo", STRING_FILENAME_PART)); + ASSERT_FALSE(string_is_safe("foo/bar", STRING_FILENAME_PART)); + ASSERT_FALSE(string_is_safe("foo/", STRING_FILENAME_PART)); + /* Pairwise combinations. */ ASSERT_TRUE(string_is_safe("", STRING_ALLOW_EMPTY | STRING_ASCII)); ASSERT_FALSE(string_is_safe("über", STRING_ALLOW_EMPTY | STRING_ASCII));