libutil: add simple NUL escaping/unescaping functions

Change-Id: Ia4cc7b8f1058439f312066422eebfaff1c2c0c6c
This commit is contained in:
eldritch horrors
2026-01-25 17:29:32 +01:00
parent ed6a1e58ea
commit c50a3a426f
3 changed files with 49 additions and 1 deletions
+23
View File
@@ -366,4 +366,27 @@ std::string showBytes(uint64_t bytes)
return fmt("%.2f MiB", bytes / (1024.0 * 1024.0));
}
std::string escapeNul(const std::string & in)
{
using std::operator""sv;
return replaceStrings(replaceStrings(std::move(in), R"(\)", R"(\\)"), "\0"sv, R"(\0)");
}
std::string unescapeNul(const std::string & in)
{
std::string result;
result.reserve(in.size());
bool inSequence = false;
for (auto c : in) {
if (inSequence) {
result += c == '0' ? '\0' : c;
inSequence = false;
} else if (c == '\\') {
inSequence = true;
} else {
result += c;
}
}
return result;
}
}
+9 -1
View File
@@ -141,7 +141,15 @@ inline std::string rewriteStrings(std::string s, const StringMap & rewrites)
return Rewriter(rewrites)(s);
}
/**
* Escape NUL bytes and `\` in a string by replacing them with `\0` and `\\` respectively.
*/
std::string escapeNul(const std::string & in);
/**
* Undo replacements done by `escapeNul`. Other escape sequences (eg `\n`) are replaced by
* the second character in the sequence (eg `n`), `\` at the end of the string is dropped.
*/
std::string unescapeNul(const std::string & in);
/**
* Parse a string into an integer.
+17
View File
@@ -817,4 +817,21 @@ namespace nix {
ASSERT_TRUE(e.is<std::invalid_argument>());
ASSERT_NE(e.as<std::invalid_argument>(), nullptr);
}
TEST(NulEscaping, function)
{
for (int c1 = CHAR_MIN; c1 < CHAR_MAX; c1++) {
for (int c2 = CHAR_MIN; c2 < CHAR_MAX; c2++) {
auto sequence = std::format("{}{}", char(c1), char(c2));
for (auto str : {sequence, "a" + sequence + "b"}) {
ASSERT_EQ(unescapeNul(escapeNul(str)), str);
}
}
}
// unknown escapes are character identity for simplicity of use
ASSERT_EQ(unescapeNul("\\a"), "a");
// escape of end-of-string is dropped silently
ASSERT_EQ(unescapeNul("a\\"), "a");
}
}