mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 17:27:00 +00:00
Escape json key and values
This commit is contained in:
@@ -27,6 +27,58 @@ concept HasToString = requires(std::remove_cvref_t<T> const& t) {
|
||||
{ to_string(t) } -> std::convertible_to<std::string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Escape a string for safe embedding in a JSON value.
|
||||
*
|
||||
* Escapes backslash, double-quote, and control characters (U+0000-U+001F)
|
||||
* according to RFC 8259 section 7.
|
||||
*/
|
||||
inline void
|
||||
appendEscapedJsonString(std::string& dest, std::string_view sv)
|
||||
{
|
||||
dest.reserve(dest.size() + sv.size() + 2);
|
||||
dest += '"';
|
||||
for (char const c : sv)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
dest += "\\\"";
|
||||
break;
|
||||
case '\\':
|
||||
dest += "\\\\";
|
||||
break;
|
||||
case '\b':
|
||||
dest += "\\b";
|
||||
break;
|
||||
case '\f':
|
||||
dest += "\\f";
|
||||
break;
|
||||
case '\n':
|
||||
dest += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
dest += "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
dest += "\\t";
|
||||
break;
|
||||
default:
|
||||
if (static_cast<unsigned char>(c) < 0x20)
|
||||
{
|
||||
fmt::format_to(
|
||||
std::back_inserter(dest), "\\u{:04x}", static_cast<unsigned int>(c));
|
||||
}
|
||||
else
|
||||
{
|
||||
dest += c;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
dest += '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Append a value formatted as a JSON fragment to @p dest.
|
||||
*
|
||||
@@ -58,11 +110,11 @@ appendJsonValue(std::string& dest, T const& value)
|
||||
}
|
||||
else if constexpr (HasToString<T>)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(dest), "\"{}\"", to_string(value));
|
||||
appendEscapedJsonString(dest, to_string(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::format_to(std::back_inserter(dest), "\"{}\"", value);
|
||||
appendEscapedJsonString(dest, fmt::format("{}", value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +130,8 @@ appendJsonField(std::string& dest, std::string_view key, T const& value)
|
||||
{
|
||||
if (!dest.empty())
|
||||
dest += ',';
|
||||
fmt::format_to(std::back_inserter(dest), "\"{}\":", key);
|
||||
appendEscapedJsonString(dest, key);
|
||||
dest += ':';
|
||||
appendJsonValue(dest, value);
|
||||
}
|
||||
|
||||
@@ -111,15 +164,22 @@ class JsonLoggingPatternBuilder
|
||||
{
|
||||
std::string fields_;
|
||||
|
||||
static constexpr std::string_view kSUFFIX = ", \"message\": %v }";
|
||||
static constexpr std::string_view kMESSAGE_FIELD = "\"message\": %v";
|
||||
|
||||
static std::string
|
||||
extractFields(std::string_view pattern)
|
||||
{
|
||||
if (!pattern.empty() && pattern.front() == '{')
|
||||
pattern.remove_prefix(1);
|
||||
if (auto pos = pattern.rfind(kSUFFIX); pos != std::string_view::npos)
|
||||
pattern = pattern.substr(0, pos);
|
||||
// Strip the trailing message field: `"message": %v }` or `"message": %v }`
|
||||
if (auto pos = pattern.rfind(kMESSAGE_FIELD); pos != std::string_view::npos)
|
||||
{
|
||||
// Also strip the leading ", " separator if present
|
||||
auto end = pos;
|
||||
if (end >= 2 && pattern.substr(end - 2, 2) == ", ")
|
||||
end -= 2;
|
||||
pattern = pattern.substr(0, end);
|
||||
}
|
||||
return std::string(pattern);
|
||||
}
|
||||
|
||||
@@ -143,7 +203,13 @@ public:
|
||||
[[nodiscard]] std::string
|
||||
build() const
|
||||
{
|
||||
return "{" + fields_ + std::string(kSUFFIX);
|
||||
std::string result = "{";
|
||||
result += fields_;
|
||||
if (!fields_.empty())
|
||||
result += ", ";
|
||||
result += kMESSAGE_FIELD;
|
||||
result += " }";
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -61,12 +61,106 @@ TEST(AppendJsonValue, appends_to_existing)
|
||||
EXPECT_EQ(dest, "prefix:99");
|
||||
}
|
||||
|
||||
// -- detail::appendEscapedJsonString ------------------------------------------
|
||||
|
||||
TEST(AppendEscapedJsonString, plain_string)
|
||||
{
|
||||
std::string dest;
|
||||
detail::appendEscapedJsonString(dest, "hello");
|
||||
EXPECT_EQ(dest, "\"hello\"");
|
||||
}
|
||||
|
||||
TEST(AppendEscapedJsonString, escapes_double_quote)
|
||||
{
|
||||
std::string dest;
|
||||
detail::appendEscapedJsonString(dest, R"(say "hi")");
|
||||
EXPECT_EQ(dest, R"("say \"hi\"")");
|
||||
}
|
||||
|
||||
TEST(AppendEscapedJsonString, escapes_backslash)
|
||||
{
|
||||
std::string dest;
|
||||
detail::appendEscapedJsonString(dest, R"(a\b)");
|
||||
EXPECT_EQ(dest, R"("a\\b")");
|
||||
}
|
||||
|
||||
TEST(AppendEscapedJsonString, escapes_control_characters)
|
||||
{
|
||||
std::string dest;
|
||||
detail::appendEscapedJsonString(dest, "line1\nline2\ttab");
|
||||
EXPECT_EQ(dest, R"("line1\nline2\ttab")");
|
||||
}
|
||||
|
||||
TEST(AppendEscapedJsonString, escapes_all_named_controls)
|
||||
{
|
||||
std::string dest;
|
||||
detail::appendEscapedJsonString(dest, "\b\f\r");
|
||||
EXPECT_EQ(dest, R"("\b\f\r")");
|
||||
}
|
||||
|
||||
TEST(AppendEscapedJsonString, escapes_low_control_char_as_unicode)
|
||||
{
|
||||
std::string dest;
|
||||
std::string input(1, '\x01');
|
||||
detail::appendEscapedJsonString(dest, input);
|
||||
EXPECT_EQ(dest, R"("\u0001")");
|
||||
}
|
||||
|
||||
TEST(AppendEscapedJsonString, empty_string)
|
||||
{
|
||||
std::string dest;
|
||||
detail::appendEscapedJsonString(dest, "");
|
||||
EXPECT_EQ(dest, R"("")");
|
||||
}
|
||||
|
||||
// -- appendJsonValue with escaping -------------------------------------------
|
||||
|
||||
TEST(AppendJsonValue, string_with_quotes_escaped)
|
||||
{
|
||||
std::string dest;
|
||||
std::string const val = R"(say "hi")";
|
||||
detail::appendJsonValue(dest, val);
|
||||
EXPECT_EQ(dest, R"("say \"hi\"")");
|
||||
}
|
||||
|
||||
TEST(AppendJsonValue, string_with_backslash_escaped)
|
||||
{
|
||||
std::string dest;
|
||||
std::string const val = R"(path\to\file)";
|
||||
detail::appendJsonValue(dest, val);
|
||||
EXPECT_EQ(dest, R"("path\\to\\file")");
|
||||
}
|
||||
|
||||
TEST(AppendJsonValue, string_with_newline_escaped)
|
||||
{
|
||||
std::string dest;
|
||||
std::string const val = "line1\nline2";
|
||||
detail::appendJsonValue(dest, val);
|
||||
EXPECT_EQ(dest, R"("line1\nline2")");
|
||||
}
|
||||
|
||||
// -- appendJsonField with key escaping ----------------------------------------
|
||||
|
||||
TEST(AppendJsonField, key_with_quotes_escaped)
|
||||
{
|
||||
std::string dest;
|
||||
detail::appendJsonField(dest, R"(my"key)", 42);
|
||||
EXPECT_EQ(dest, R"("my\"key":42)");
|
||||
}
|
||||
|
||||
TEST(AppendJsonField, key_with_backslash_escaped)
|
||||
{
|
||||
std::string dest;
|
||||
detail::appendJsonField(dest, R"(a\b)", true);
|
||||
EXPECT_EQ(dest, R"("a\\b":true)");
|
||||
}
|
||||
|
||||
// -- buildJsonPattern --------------------------------------------------------
|
||||
|
||||
TEST(BuildJsonPattern, no_params)
|
||||
{
|
||||
auto const pattern = buildJsonPattern("");
|
||||
EXPECT_EQ(pattern, "{, \"message\": %v }");
|
||||
EXPECT_EQ(pattern, "{\"message\": %v }");
|
||||
}
|
||||
|
||||
TEST(BuildJsonPattern, single_string_field)
|
||||
|
||||
Reference in New Issue
Block a user