From a2c09c29d8c2d21bc3337239ea8d88547742e377 Mon Sep 17 00:00:00 2001 From: Alex Fabijanic Date: Thu, 13 Oct 2022 11:50:53 +0200 Subject: [PATCH] feat(DynamicStruct): toString() escaping #3833 --- Foundation/include/Poco/Dynamic/Struct.h | 132 +++++++------------- Foundation/include/Poco/Dynamic/VarHolder.h | 8 +- Foundation/src/VarHolder.cpp | 4 +- Foundation/testsuite/src/VarTest.cpp | 9 ++ Foundation/testsuite/src/VarTest.h | 1 + 5 files changed, 65 insertions(+), 89 deletions(-) diff --git a/Foundation/include/Poco/Dynamic/Struct.h b/Foundation/include/Poco/Dynamic/Struct.h index 317493f6e..ef9f11432 100644 --- a/Foundation/include/Poco/Dynamic/Struct.h +++ b/Foundation/include/Poco/Dynamic/Struct.h @@ -32,6 +32,37 @@ namespace Poco { namespace Dynamic { +template +std::string structToString(const S& data, bool wrap = true) + /// Utility function for converting DynamicStruct to std::string. + /// Set wrap to false in order to prevent string values wrapping + /// (useful to prevent JSON fragments from being treated as strings). +{ + std::string val; + val.append("{ "); + I it = data.begin(); + I itEnd = data.end(); + if (!data.empty()) + { + Var key(it->first); + Impl::appendJSONKey(val, key); + val.append(": "); + Impl::appendJSONValue(val, it->second, wrap); + ++it; + } + for (; it != itEnd; ++it) + { + val.append(", "); + Var key(it->first); + Impl::appendJSONKey(val, key); + val.append(": "); + Impl::appendJSONValue(val, it->second, wrap); + } + val.append(" }"); + return val; +} + + template , typename S = std::set> class Struct /// Struct allows to define a named collection of Var objects. @@ -226,11 +257,20 @@ public: return it->second; } - std::string toString() const + std::string toString(bool wrap = true) const + /// Returns the DynamicStruct as string. + /// + /// To prevent unwanted string wrapping + /// (eg. when a value is JSON string), + /// `wrap` should be false. Note, however, + /// that wrap argument is of a limited utility + /// because it applies to the entire Struct, + /// so it should not be relied on when mixed content + /// (ie. plain string, which should be wrapped, + /// and JSON-as-string entries, which shouldn't) + /// is held. { - std::string str; - Var(*this).template convert(str); - return str; + return structToString(_data, wrap); } private: @@ -332,26 +372,7 @@ public: void convert(std::string& val) const { - val.append("{ "); - ValueType::ConstIterator it = _val.begin(); - ValueType::ConstIterator itEnd = _val.end(); - if (!_val.empty()) - { - Var key(it->first); - Impl::appendJSONKey(val, key); - val.append(": "); - Impl::appendJSONValue(val, it->second); - ++it; - } - for (; it != itEnd; ++it) - { - val.append(", "); - Var key(it->first); - Impl::appendJSONKey(val, key); - val.append(": "); - Impl::appendJSONValue(val, it->second); - } - val.append(" }"); + val = structToString(_val); } void convert(Poco::DateTime&) const @@ -518,26 +539,7 @@ public: void convert(std::string& val) const { - val.append("{ "); - ValueType::ConstIterator it = _val.begin(); - ValueType::ConstIterator itEnd = _val.end(); - if (!_val.empty()) - { - Var key(it->first); - Impl::appendJSONKey(val, key); - val.append(": "); - Impl::appendJSONValue(val, it->second); - ++it; - } - for (; it != itEnd; ++it) - { - val.append(", "); - Var key(it->first); - Impl::appendJSONKey(val, key); - val.append(": "); - Impl::appendJSONValue(val, it->second); - } - val.append(" }"); + val = structToString(_val); } void convert(Poco::DateTime&) const @@ -704,26 +706,7 @@ public: void convert(std::string& val) const { - val.append("{ "); - ValueType::ConstIterator it = _val.begin(); - ValueType::ConstIterator itEnd = _val.end(); - if (!_val.empty()) - { - Var key(it->first); - Impl::appendJSONKey(val, key); - val.append(": "); - Impl::appendJSONValue(val, it->second); - ++it; - } - for (; it != itEnd; ++it) - { - val.append(", "); - Var key(it->first); - Impl::appendJSONKey(val, key); - val.append(": "); - Impl::appendJSONValue(val, it->second); - } - val.append(" }"); + val = structToString(_val); } void convert(Poco::DateTime&) const @@ -890,26 +873,7 @@ public: void convert(std::string& val) const { - val.append("{ "); - ValueType::ConstIterator it = _val.begin(); - ValueType::ConstIterator itEnd = _val.end(); - if (!_val.empty()) - { - Var key(it->first); - Impl::appendJSONKey(val, key); - val.append(": "); - Impl::appendJSONValue(val, it->second); - ++it; - } - for (; it != itEnd; ++it) - { - val.append(", "); - Var key(it->first); - Impl::appendJSONKey(val, key); - val.append(": "); - Impl::appendJSONValue(val, it->second); - } - val.append(" }"); + val = structToString(_val); } void convert(Poco::DateTime&) const diff --git a/Foundation/include/Poco/Dynamic/VarHolder.h b/Foundation/include/Poco/Dynamic/VarHolder.h index 16b99d0a1..88b9c8bd5 100644 --- a/Foundation/include/Poco/Dynamic/VarHolder.h +++ b/Foundation/include/Poco/Dynamic/VarHolder.h @@ -67,10 +67,12 @@ void Foundation_API appendJSONString(std::string& val, const Var& any); /// regardless of the underlying type) and appends it to val. -void Foundation_API appendJSONValue(std::string& val, const Var& any); +void Foundation_API appendJSONValue(std::string& val, const Var& any, bool wrap = true); /// Converts the any to a JSON value (if underlying type qualifies - /// as string - see isJSONString() - , it is wrapped into double quotes) - /// and appends it to val + /// as string - see isJSONString() - it is wrapped into double quotes) + /// and appends it to val. + /// Wrapping can be prevented (useful for appending JSON fragments) by setting + /// the wrap argument to false. template diff --git a/Foundation/src/VarHolder.cpp b/Foundation/src/VarHolder.cpp index c76df001d..68076b340 100644 --- a/Foundation/src/VarHolder.cpp +++ b/Foundation/src/VarHolder.cpp @@ -66,7 +66,7 @@ void appendJSONKey(std::string& val, const Var& any) } -void appendJSONValue(std::string& val, const Var& any) +void appendJSONValue(std::string& val, const Var& any, bool wrap) { if (any.isEmpty()) { @@ -74,7 +74,7 @@ void appendJSONValue(std::string& val, const Var& any) } else { - bool isStr = isJSONString(any); + bool isStr = wrap && isJSONString(any); if (isStr) { appendJSONString(val, any.convert()); diff --git a/Foundation/testsuite/src/VarTest.cpp b/Foundation/testsuite/src/VarTest.cpp index d922e4a39..3077fefdc 100644 --- a/Foundation/testsuite/src/VarTest.cpp +++ b/Foundation/testsuite/src/VarTest.cpp @@ -2286,6 +2286,14 @@ void VarTest::testOrderedDynamicStructBasics() } +void VarTest::testDynamicStructNoEscapeString() +{ + DynamicStruct aStruct; + aStruct["Birthday"] = "{ \"Day\": 12, \"Month\": \"May\", \"Year\": 2005 }"; + assertEqual(aStruct.toString(false), "{ \"Birthday\": { \"Day\": 12, \"Month\": \"May\", \"Year\": 2005 } }"); +} + + void VarTest::testDynamicStructString() { DynamicStruct aStruct; @@ -3131,6 +3139,7 @@ CppUnit::Test* VarTest::suite() CppUnit_addTest(pSuite, VarTest, testDynamicPair); CppUnit_addTest(pSuite, VarTest, testDynamicStructBasics); CppUnit_addTest(pSuite, VarTest, testOrderedDynamicStructBasics); + CppUnit_addTest(pSuite, VarTest, testDynamicStructNoEscapeString); CppUnit_addTest(pSuite, VarTest, testDynamicStructString); CppUnit_addTest(pSuite, VarTest, testOrderedDynamicStructString); CppUnit_addTest(pSuite, VarTest, testDynamicStructInt); diff --git a/Foundation/testsuite/src/VarTest.h b/Foundation/testsuite/src/VarTest.h index fc8a1d8fd..939b9ef62 100644 --- a/Foundation/testsuite/src/VarTest.h +++ b/Foundation/testsuite/src/VarTest.h @@ -57,6 +57,7 @@ public: void testDynamicStructBasics(); void testOrderedDynamicStructBasics(); void testDynamicStructString(); + void testDynamicStructNoEscapeString(); void testOrderedDynamicStructString(); void testDynamicStructInt(); void testOrderedDynamicStructInt();