From 383fbb8098ece332a0a50b00baf488a54245dcf0 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 30 Oct 2023 09:37:01 +0100 Subject: [PATCH] Fixed json output, fixes #348 --- src/libs/subsonic/impl/SubsonicResponse.cpp | 2 +- src/libs/utils/impl/String.cpp | 112 ++++++++++++-------- src/libs/utils/include/utils/String.hpp | 2 + src/libs/utils/test/String.cpp | 52 +++++---- 4 files changed, 105 insertions(+), 63 deletions(-) diff --git a/src/libs/subsonic/impl/SubsonicResponse.cpp b/src/libs/subsonic/impl/SubsonicResponse.cpp index ed28c49c..01762ecc 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.cpp +++ b/src/libs/subsonic/impl/SubsonicResponse.cpp @@ -365,7 +365,7 @@ namespace API::Subsonic void Response::JsonSerializer::serializeEscapedString(std::ostream& os, std::string_view str) { os << '\"'; - StringUtils::writeJSEscapedString(os, str); + StringUtils::writeJsonEscapedString(os, str); os << '\"'; } diff --git a/src/libs/utils/impl/String.cpp b/src/libs/utils/impl/String.cpp index 807ec72b..836dfa22 100644 --- a/src/libs/utils/impl/String.cpp +++ b/src/libs/utils/impl/String.cpp @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include @@ -33,6 +33,62 @@ namespace StringUtils { + namespace + { + constexpr std::pair jsEscapeChars[] + { + { '\\', "\\\\" }, + { '\n', "\\n" }, + { '\r', "\\r" }, + { '\t', "\\t" }, + { '"', "\\\"" }, + { '\'', "\\\'" }, + }; + + constexpr std::pair jsonEscapeChars[] + { + { '\\', "\\\\" }, + { '\n', "\\n" }, + { '\r', "\\r" }, + { '\t', "\\t" }, + { '"', "\\\"" }, + }; + + template + std::string escape(std::string_view str, const std::pair(&charsToEscape)[N]) + { + std::string escaped; + escaped.reserve(str.length()); + + for (const char c : str) + { + auto it{ std::find_if(std::cbegin(charsToEscape), std::cend(charsToEscape), [c](const auto& entry) { return entry.first == c; }) }; + if (it == std::cend(charsToEscape)) + { + escaped += c; + continue; + } + + escaped += it->second; + } + + return escaped; + } + + template + void writeEscapedString(std::ostream& os, std::string_view str, const std::pair(&charsToEscape)[N]) + { + for (const char c : str) + { + auto itEntry{ std::find_if(std::cbegin(charsToEscape), std::cend(charsToEscape), [=](const auto& entry) { return entry.first == c;}) }; + if (itEntry != std::cend(charsToEscape)) + os << itEntry->second; + else + os << c; + } + } + } + bool readList(const std::string& str, const std::string& separators, std::list& results) { std::string curStr; @@ -221,54 +277,22 @@ namespace StringUtils std::string jsEscape(std::string_view str) { - static const std::unordered_map escapeMap - { - { '\\', "\\\\" }, - { '\n', "\\n" }, - { '\r', "\\r" }, - { '\t', "\\t" }, - { '"', "\\\"" }, - { '\'', "\\\'" }, - }; - - std::string escaped; - escaped.reserve(str.length()); - - for (const char c : str) - { - auto it{ escapeMap.find(c) }; - if (it == std::cend(escapeMap)) - { - escaped += c; - continue; - } - - escaped += it->second; - } - - return escaped; + return escape(str, jsEscapeChars); } void writeJSEscapedString(std::ostream& os, std::string_view str) { - static constexpr std::pair charsToEscape[] - { - {'\\', "\\\\" }, - { '\n', "\\n" }, - { '\r', "\\r" }, - { '\t', "\\t" }, - { '"', "\\\"" }, - { '\'', "\\\'" }, - }; + writeEscapedString(os, str, jsEscapeChars); + } + + std::string jsonEscape(std::string_view str) + { + return escape(str, jsonEscapeChars); + } - for (const char c : str) - { - auto itEntry{ std::find_if(std::cbegin(charsToEscape), std::cend(charsToEscape), [=](const auto& entry) { return entry.first == c;}) }; - if (itEntry != std::cend(charsToEscape)) - os << itEntry->second; - else - os << c; - } + void writeJsonEscapedString(std::ostream& os, std::string_view str) + { + writeEscapedString(os, str, jsonEscapeChars); } std::string escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar) diff --git a/src/libs/utils/include/utils/String.hpp b/src/libs/utils/include/utils/String.hpp index 03709ac1..9cf8970a 100644 --- a/src/libs/utils/include/utils/String.hpp +++ b/src/libs/utils/include/utils/String.hpp @@ -84,7 +84,9 @@ namespace StringUtils { [[nodiscard]] std::string replaceInString(std::string_view str, const std::string& from, const std::string& to); [[nodiscard]] std::string jsEscape(std::string_view str); + [[nodiscard]] std::string jsonEscape(std::string_view str); void writeJSEscapedString(std::ostream& os, std::string_view str); + void writeJsonEscapedString(std::ostream& os, std::string_view str); [[nodiscard]] std::string escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar); diff --git a/src/libs/utils/test/String.cpp b/src/libs/utils/test/String.cpp index eb5281f6..abfb53c2 100644 --- a/src/libs/utils/test/String.cpp +++ b/src/libs/utils/test/String.cpp @@ -27,50 +27,50 @@ TEST(StringUtils, splitString) { { - const std::string test {"a"}; + const std::string test{ "a" }; - const std::vector strings {StringUtils::splitString(test, "")}; + const std::vector strings{ StringUtils::splitString(test, "") }; ASSERT_EQ(strings.size(), 1); - EXPECT_EQ(strings.front() , "a"); + EXPECT_EQ(strings.front(), "a"); } { - const std::string test {"a b"}; + const std::string test{ "a b" }; - const std::vector strings {StringUtils::splitString(test, "|")}; + const std::vector strings{ StringUtils::splitString(test, "|") }; ASSERT_EQ(strings.size(), 1); EXPECT_EQ(strings.front(), "a b"); } { - const std::string test {" a"}; + const std::string test{ " a" }; - const std::vector strings {StringUtils::splitString(test, " ")}; + const std::vector strings{ StringUtils::splitString(test, " ") }; ASSERT_EQ(strings.size(), 1); EXPECT_EQ(strings.front(), "a"); } { - const std::string test {"a "}; + const std::string test{ "a " }; - const std::vector strings {StringUtils::splitString(test, " ")}; + const std::vector strings{ StringUtils::splitString(test, " ") }; ASSERT_EQ(strings.size(), 1); EXPECT_EQ(strings.front(), "a"); } { - const std::string test {"a b"}; + const std::string test{ "a b" }; - const std::vector strings {StringUtils::splitString(test, " ")}; + const std::vector strings{ StringUtils::splitString(test, " ") }; ASSERT_EQ(strings.size(), 2); EXPECT_EQ(strings.front(), "a"); EXPECT_EQ(strings.back(), "b"); } { - const std::string test {"a b,c|defgh "}; + const std::string test{ "a b,c|defgh " }; - const std::vector strings {StringUtils::splitString(test, " ,|")}; + const std::vector strings{ StringUtils::splitString(test, " ,|") }; ASSERT_EQ(strings.size(), 4); EXPECT_EQ(strings[0], "a"); EXPECT_EQ(strings[1], "b"); @@ -83,24 +83,40 @@ TEST(StringUtils, splitString) TEST(StringUtils, splitStringCopy) { { - const std::string test {"test=foo"}; + const std::string test{ "test=foo" }; - const std::vector strings {StringUtils::splitStringCopy(test, "=")}; + const std::vector strings{ StringUtils::splitStringCopy(test, "=") }; ASSERT_EQ(strings.size(), 2); EXPECT_EQ(strings[0], "test"); EXPECT_EQ(strings[1], "foo"); } { - const std::string test {"test=foo bar"}; + const std::string test{ "test=foo bar" }; - const std::vector strings {StringUtils::splitStringCopy(test, "=")}; + const std::vector strings{ StringUtils::splitStringCopy(test, "=") }; ASSERT_EQ(strings.size(), 2); EXPECT_EQ(strings[0], "test"); EXPECT_EQ(strings[1], "foo bar"); } } +TEST(StringUtils, escapeJSString) +{ + EXPECT_EQ(StringUtils::jsEscape(""), ""); + EXPECT_EQ(StringUtils::jsEscape(R"(Test'.mp3)"), R"(Test\'.mp3)"); + EXPECT_EQ(StringUtils::jsEscape(R"(Test"".mp3)"), R"(Test\"\".mp3)"); + EXPECT_EQ(StringUtils::jsEscape(R"(\Test\.mp3)"), R"(\\Test\\.mp3)"); +} + +TEST(StringUtils, escapeJsonString) +{ + EXPECT_EQ(StringUtils::jsonEscape(""), ""); + EXPECT_EQ(StringUtils::jsonEscape(R"(Test'.mp3)"), R"(Test'.mp3)"); + EXPECT_EQ(StringUtils::jsonEscape(R"(Test"".mp3)"), R"(Test\"\".mp3)"); + EXPECT_EQ(StringUtils::jsonEscape(R"(\Test\.mp3)"), R"(\\Test\\.mp3)"); +} + TEST(StringUtils, escapeString) { EXPECT_EQ(StringUtils::escapeString("", "*", ' '), ""); @@ -145,7 +161,7 @@ TEST(StringUtils, capitalize) for (const TestCase& test : tests) { - std::string str {test.input}; + std::string str{ test.input }; StringUtils::capitalize(str); EXPECT_EQ(str, test.expectedOutput) << " str was '" << test.input << "'"; }