]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
string-util: introduce STRING_ALLOW_NEWLINES switch for string_is_safe()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 17 May 2026 15:18:23 +0000 (00:18 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 17 May 2026 17:55:34 +0000 (02:55 +0900)
This also make StringSafeFlags defined in basic-forward.h so other
headers can use the enum.

src/basic/basic-forward.h
src/basic/string-util.c
src/basic/string-util.h
src/test/test-string-util.c

index 064af284a654cfb982ad6eabf0750984abd4619a..5edd494f783c69010c0c8724d1a891ed5b41087d 100644 (file)
@@ -103,6 +103,7 @@ typedef enum Glyph Glyph;
 typedef enum ImageClass ImageClass;
 typedef enum JobMode JobMode;
 typedef enum RuntimeScope RuntimeScope;
+typedef enum StringSafeFlags StringSafeFlags;
 typedef enum TimestampStyle TimestampStyle;
 typedef enum UnitActiveState UnitActiveState;
 typedef enum UnitDependency UnitDependency;
index 4d930839232d707545258ec4b99f99261c90cae7..b972f7a42c92ef06c46f4fbbdcb15e20f4caca1c 100644 (file)
@@ -1113,7 +1113,11 @@ bool string_is_safe(const char *p, StringSafeFlags flags) {
                 return false;
 
         for (const char *t = p; *t; t++) {
-                if ((*t > 0 && *t < ' ') || *t == 0x7f) /* never allow control characters */
+                /* never allow control characters, except for new line */
+                if ((*t > 0 && *t < ' ' && *t != '\n') || *t == 0x7f)
+                        return false;
+
+                if (!FLAGS_SET(flags, STRING_ALLOW_NEWLINES) && *t == '\n')
                         return false;
 
                 if (!FLAGS_SET(flags, STRING_ALLOW_BACKSLASHES) && *t == '\\')
index 17eaf5d9a6a8249985acca5b7d734516168a1d54..8cddc051d5d3d8ec0ad98c8802abfe9975d84ab1 100644 (file)
@@ -223,10 +223,11 @@ static inline int strdup_to(char **ret, const char *src) {
 typedef enum StringSafeFlags {
         STRING_ASCII             = 1 << 0, /* Verify string is 7-Bit ASCII (rather than just UTF-8) */
         STRING_ALLOW_EMPTY       = 1 << 1, /* Allow empty strings */
-        STRING_ALLOW_BACKSLASHES = 1 << 2, /* Allow backslashes (\) */
-        STRING_ALLOW_QUOTES      = 1 << 3, /* Allow quotes (" or ') */
-        STRING_ALLOW_GLOBS       = 1 << 4, /* Allow globs (?, * or [) */
-        STRING_FILENAME          = 1 << 5, /* Verify the string is valid as regular filename */
+        STRING_ALLOW_NEWLINES    = 1 << 2, /* Allow newlines (\n) */
+        STRING_ALLOW_BACKSLASHES = 1 << 3, /* Allow backslashes (\) */
+        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 */
 } StringSafeFlags;
 
 bool string_is_safe(const char *p, StringSafeFlags flags) _pure_;
index 6e07d727636eef03c32e378e9996a337089e4fec..4a8656f41101e6c38a49fdc405a4120f184452cf 100644 (file)
@@ -1547,6 +1547,18 @@ TEST(string_is_safe) {
         ASSERT_FALSE(string_is_safe("a\"b", STRING_ASCII));
         ASSERT_FALSE(string_is_safe("a*b", STRING_ASCII));
 
+        /* STRING_ALLOW_NEWLINES: newlines allowed, quotes/globs still rejected. */
+        ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_NEWLINES));
+        ASSERT_TRUE(string_is_safe("hello world", STRING_ALLOW_NEWLINES));
+        ASSERT_TRUE(string_is_safe("\n", STRING_ALLOW_NEWLINES));
+        ASSERT_TRUE(string_is_safe("a\nb", STRING_ALLOW_NEWLINES));
+        ASSERT_TRUE(string_is_safe("foo\n", STRING_ALLOW_NEWLINES));
+        ASSERT_TRUE(string_is_safe("\nfoo", STRING_ALLOW_NEWLINES));
+        ASSERT_TRUE(string_is_safe("foo\nbar", STRING_ALLOW_NEWLINES));
+        ASSERT_FALSE(string_is_safe("foo\\nbar", STRING_ALLOW_NEWLINES)); /* literal backslash, not newline, rejected */
+        ASSERT_FALSE(string_is_safe("\"", STRING_ALLOW_NEWLINES));        /* quotes still rejected */
+        ASSERT_FALSE(string_is_safe("*", STRING_ALLOW_NEWLINES));         /* globs still rejected */
+
         /* STRING_ALLOW_BACKSLASHES: backslashes allowed, quotes/globs still rejected. */
         ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_BACKSLASHES));
         ASSERT_TRUE(string_is_safe("hello world", STRING_ALLOW_BACKSLASHES));
@@ -1555,6 +1567,7 @@ TEST(string_is_safe) {
         ASSERT_TRUE(string_is_safe("foo\\", STRING_ALLOW_BACKSLASHES));
         ASSERT_TRUE(string_is_safe("\\foo", STRING_ALLOW_BACKSLASHES));
         ASSERT_TRUE(string_is_safe("foo\\nbar", STRING_ALLOW_BACKSLASHES)); /* literal backslash, not newline */
+        ASSERT_FALSE(string_is_safe("foo\nbar", STRING_ALLOW_BACKSLASHES)); /* newline still rejected */
         ASSERT_FALSE(string_is_safe("\"", STRING_ALLOW_BACKSLASHES));       /* quotes still rejected */
         ASSERT_FALSE(string_is_safe("*", STRING_ALLOW_BACKSLASHES));        /* globs still rejected */
 
@@ -1565,6 +1578,7 @@ TEST(string_is_safe) {
         ASSERT_TRUE(string_is_safe("'", STRING_ALLOW_QUOTES));
         ASSERT_TRUE(string_is_safe("hello\"world", STRING_ALLOW_QUOTES));
         ASSERT_TRUE(string_is_safe("it's", STRING_ALLOW_QUOTES));
+        ASSERT_FALSE(string_is_safe("a\nb", STRING_ALLOW_QUOTES));          /* newline still rejected */
         ASSERT_FALSE(string_is_safe("a\\b", STRING_ALLOW_QUOTES));          /* backslashes still rejected */
         ASSERT_FALSE(string_is_safe("*", STRING_ALLOW_QUOTES));             /* globs still rejected */
 
@@ -1577,6 +1591,7 @@ TEST(string_is_safe) {
         ASSERT_TRUE(string_is_safe("foo*bar", STRING_ALLOW_GLOBS));
         ASSERT_TRUE(string_is_safe("foo?bar", STRING_ALLOW_GLOBS));
         ASSERT_TRUE(string_is_safe("foo[bar", STRING_ALLOW_GLOBS));
+        ASSERT_FALSE(string_is_safe("foo\nbar", STRING_ALLOW_GLOBS));       /* newline still rejected */
         ASSERT_FALSE(string_is_safe("\"", STRING_ALLOW_GLOBS));             /* quotes still rejected */
         ASSERT_FALSE(string_is_safe("a\\b", STRING_ALLOW_GLOBS));           /* backslashes still rejected */
 
@@ -1618,10 +1633,18 @@ TEST(string_is_safe) {
         ASSERT_TRUE(string_is_safe("foo\"bar", STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES));
 
         /* All allow flags combined: only baseline (control chars, invalid UTF-8) and STRING_FILENAME apply. */
-        StringSafeFlags all = STRING_ALLOW_EMPTY | STRING_ASCII | STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS | STRING_FILENAME;
+        StringSafeFlags all =
+                STRING_ASCII |
+                STRING_ALLOW_EMPTY |
+                STRING_ALLOW_NEWLINES |
+                STRING_ALLOW_BACKSLASHES |
+                STRING_ALLOW_QUOTES |
+                STRING_ALLOW_GLOBS |
+                STRING_FILENAME;
         ASSERT_TRUE(string_is_safe("hello.txt", all));
         ASSERT_TRUE(string_is_safe("foo-bar_baz.conf", all));
         ASSERT_TRUE(string_is_safe("a", all));
+        ASSERT_TRUE(string_is_safe("foo\nbar", all));            /* newline allowed */
         ASSERT_TRUE(string_is_safe("foo\\bar", all));            /* backslash allowed */
         ASSERT_TRUE(string_is_safe("foo\"bar", all));            /* quote allowed */
         ASSERT_TRUE(string_is_safe("foo'bar", all));             /* quote allowed */