diff --git a/src/basic/basic-forward.h b/src/basic/basic-forward.h index 064af284a6..5edd494f78 100644 --- a/src/basic/basic-forward.h +++ b/src/basic/basic-forward.h @@ -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; diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 4d93083923..b972f7a42c 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -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 == '\\') diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 17eaf5d9a6..8cddc051d5 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -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_; diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 6e07d72763..4a8656f411 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -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 */