From 603994ce61d419162e7daba536b7214e03503062 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 6 Mar 2021 14:13:46 +0100 Subject: [PATCH] Make sure gdt values are escaped --- src/ObjCommon/Obj/Gdt/GdtStream.cpp | 84 +++++++++++++++++++++++- src/ObjCommon/Obj/Gdt/GdtStream.h | 1 + src/ObjCommon/Utils/InfoString.h | 3 +- test/ObjCommonTests/Obj/Gdt/GdtTests.cpp | 37 +++++++++++ 4 files changed, 120 insertions(+), 5 deletions(-) diff --git a/src/ObjCommon/Obj/Gdt/GdtStream.cpp b/src/ObjCommon/Obj/Gdt/GdtStream.cpp index 25cea25c..871c1596 100644 --- a/src/ObjCommon/Obj/Gdt/GdtStream.cpp +++ b/src/ObjCommon/Obj/Gdt/GdtStream.cpp @@ -63,9 +63,36 @@ bool GdtReader::ReadStringContent(std::string& str) } auto c = m_stream.get(); - while (c != '"' && c != '\n' && c != EOF) + auto escaped = false; + while ((escaped || c != '"' && c != '\n') && c != EOF) { - ss << static_cast(c); + if (escaped) + { + switch (c) + { + case '\n': + case 'n': + ss << '\n'; + break; + + case 'r': + ss << '\r'; + break; + + default: + ss << static_cast(c); + break; + } + escaped = false; + } + else if(c == '\\') + { + escaped = true; + } + else + { + ss << static_cast(c); + } c = m_stream.get(); } @@ -267,6 +294,55 @@ void GdtOutputStream::WriteVersion(const GdtVersion& gdtVersion) WriteEntry(versionEntry); } +void GdtOutputStream::WriteEscaped(const std::string& str) const +{ + auto wroteBefore = false; + for(auto i = 0u; i < str.size(); i++) + { + auto needsEscape = false; + auto c = str[i]; + switch(c) + { + case '\r': + needsEscape = true; + c = 'r'; + break; + + case '\n': + needsEscape = true; + c = 'n'; + break; + + case '\\': + needsEscape = true; + break; + + default: + break; + } + + if(needsEscape) + { + if(!wroteBefore) + { + wroteBefore = true; + m_stream << std::string(str, 0, i); + } + + m_stream << '\\' << c; + } + else if(wroteBefore) + { + m_stream << c; + } + } + + if(!wroteBefore) + { + m_stream << str; + } +} + void GdtOutputStream::WriteEntry(const GdtEntry& entry) { DoIntendation(); @@ -283,7 +359,9 @@ void GdtOutputStream::WriteEntry(const GdtEntry& entry) for (const auto& [propertyKey, propertyValue] : entry.m_properties) { DoIntendation(); - m_stream << "\"" << propertyKey << "\" \"" << propertyValue << "\"\n"; + m_stream << "\"" << propertyKey << "\" \""; + WriteEscaped(propertyValue); + m_stream << "\"\n"; } m_intendation_level--; diff --git a/src/ObjCommon/Obj/Gdt/GdtStream.h b/src/ObjCommon/Obj/Gdt/GdtStream.h index 2cf6b251..3eb04681 100644 --- a/src/ObjCommon/Obj/Gdt/GdtStream.h +++ b/src/ObjCommon/Obj/Gdt/GdtStream.h @@ -36,6 +36,7 @@ public: void BeginStream(); void WriteVersion(const GdtVersion& gdtVersion); + void WriteEscaped(const std::string& str) const; void WriteEntry(const GdtEntry& entry); void EndStream(); diff --git a/src/ObjCommon/Utils/InfoString.h b/src/ObjCommon/Utils/InfoString.h index 22ffb0b2..dddbd215 100644 --- a/src/ObjCommon/Utils/InfoString.h +++ b/src/ObjCommon/Utils/InfoString.h @@ -4,8 +4,7 @@ #include #include - -#include "Obj/GDT/GdtEntry.h" +#include "Obj/Gdt/GdtEntry.h" #include "Zone/ZoneTypes.h" class InfoString diff --git a/test/ObjCommonTests/Obj/Gdt/GdtTests.cpp b/test/ObjCommonTests/Obj/Gdt/GdtTests.cpp index 0043345a..2c7607d5 100644 --- a/test/ObjCommonTests/Obj/Gdt/GdtTests.cpp +++ b/test/ObjCommonTests/Obj/Gdt/GdtTests.cpp @@ -300,4 +300,41 @@ namespace obj::gdt REQUIRE(entry.m_properties.at("idk") == "whattotypeanymore"); } } + + TEST_CASE("Gdt: Ensure can write gdt with escape values and parse it again", "[gdt]") + { + Gdt gdt; + gdt.m_version.m_game = "whatagame"; + gdt.m_version.m_version = 6969; + + { + auto entry = std::make_unique("sickentry", "verycool.gdf"); + entry->m_properties["hello"] = "very\nkewl\\stuff"; + gdt.m_entries.emplace_back(std::move(entry)); + } + + std::stringstream ss; + GdtOutputStream::WriteGdt(gdt, ss); + + std::cout << ss.str(); + + Gdt gdt2; + GdtReader reader(ss); + REQUIRE(reader.Read(gdt2)); + + REQUIRE(gdt2.m_version.m_game == "whatagame"); + REQUIRE(gdt2.m_version.m_version == 6969); + + REQUIRE(gdt2.m_entries.size() == 1); + + { + const auto& entry = *gdt2.m_entries[0]; + REQUIRE(entry.m_name == "sickentry"); + REQUIRE(entry.m_gdf_name == "verycool.gdf"); + REQUIRE(entry.m_parent == nullptr); + REQUIRE(entry.m_properties.size() == 1); + + REQUIRE(entry.m_properties.at("hello") == "very\nkewl\\stuff"); + } + } }