diff --git a/lix/libutil/strings.cc b/lix/libutil/strings.cc index 8ea3e5132..9eb2842b0 100644 --- a/lix/libutil/strings.cc +++ b/lix/libutil/strings.cc @@ -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; +} } diff --git a/lix/libutil/strings.hh b/lix/libutil/strings.hh index bc3d9285b..03f32ce14 100644 --- a/lix/libutil/strings.hh +++ b/lix/libutil/strings.hh @@ -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. diff --git a/tests/unit/libutil/tests.cc b/tests/unit/libutil/tests.cc index ff96b565b..24ddbb56f 100644 --- a/tests/unit/libutil/tests.cc +++ b/tests/unit/libutil/tests.cc @@ -817,4 +817,21 @@ namespace nix { ASSERT_TRUE(e.is()); ASSERT_NE(e.as(), 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"); + } }