diff --git a/src/server/simple_wml.cpp b/src/server/simple_wml.cpp index e3df88be7d82..c5f4a2fb3989 100644 --- a/src/server/simple_wml.cpp +++ b/src/server/simple_wml.cpp @@ -191,6 +191,15 @@ char* string_span::duplicate() const return buf; } +int string_span::count(char ch) const +{ + int count = 0; + for(const char* c = begin(); c != end(); ++c) { + count += *c == ch ? 1 : 0; + } + return count; +} + error::error(const char* msg) : game::error(msg) { @@ -276,6 +285,7 @@ node::node(document& doc, node* parent, const char** str, int depth) : break; default: { const char* end = strchr(s, '='); + bool needs_escaping = false; if(end == nullptr) { ERR_SWML << "attribute: " << s << std::endl; throw error("did not find '=' after attribute"); @@ -309,6 +319,7 @@ node::node(document& doc, node* parent, const char** str, int depth) : #pragma warning (pop) #endif ++end; + needs_escaping = true; } if(end == nullptr) throw error("did not find end of attribute"); @@ -351,7 +362,13 @@ node::node(document& doc, node* parent, const char** str, int depth) : s = end + 1; - attr_.emplace_back(name, value); + if(!needs_escaping) { + attr_.emplace_back(name, value); + } else { + char* value_buf = unescape_value(value); + doc_->take_ownership_of_buffer(value_buf); + attr_.emplace_back(name, string_span(value_buf)); + } } } } @@ -659,6 +676,42 @@ int node::get_children(const string_span& name) return children_.size() - 1; } +char* node::unescape_value(const string_span& value) +{ + std::string buffer; + buffer.reserve(value.size()); + const char* begin = value.begin(); + const char* end; + // Find the next duplicated double quote. + while((end = strstr(begin, "\"\"")) != nullptr) { + // Copy characters up to and including the first double quote, but not the second. + buffer.append(begin, end - begin + 1); + begin = end + 2; + } + // Copy the rest. + buffer.append(begin, value.end() - begin); + + char* final_buffer = new char[buffer.size() + 1]; + buffer.copy(final_buffer, std::string::npos); + final_buffer[buffer.size()] = '\0'; + return final_buffer; +} + +void node::escape_value(const string_span& value, char** buffer) +{ + char* buf = *buffer; + for(const char* c = value.begin(); c != value.end(); ++c) + { + if(*c != '"') { + *buf++ = *c; + } else { + *buf++ = '"'; + *buf++ = '"'; + } + } + *buffer = buf; +} + node::child_map::const_iterator node::find_in_map(const child_map& m, const string_span& attr) { child_map::const_iterator i = m.begin(); @@ -702,7 +755,7 @@ int node::output_size() const int res = 0; for(attribute_list::const_iterator i = attr_.begin(); i != attr_.end(); ++i) { - res += i->key.size() + i->value.size() + 4; + res += i->key.size() + i->value.size() + i->value.count('"') + 4; } size_t count_children = 0; @@ -758,9 +811,13 @@ void node::output(char*& buf, CACHE_STATUS cache_status) buf += i->key.size(); *buf++ = '='; *buf++ = '"'; - memcpy(buf, i->value.begin(), i->value.size()); - i->value = string_span(buf, i->value.size()); - buf += i->value.size(); + if(memchr(i->value.begin(), '"', i->value.size()) == nullptr) { + memcpy(buf, i->value.begin(), i->value.size()); + i->value = string_span(buf, i->value.size()); + buf += i->value.size(); + } else { + escape_value(i->value, &buf); + } *buf++ = '"'; *buf++ = '\n'; } diff --git a/src/server/simple_wml.hpp b/src/server/simple_wml.hpp index f0137c031ed8..96f21c2c153d 100644 --- a/src/server/simple_wml.hpp +++ b/src/server/simple_wml.hpp @@ -100,6 +100,9 @@ class string_span //returns a duplicate of the string span in a new[] allocated buffer char* duplicate() const; + //counts how many copies of the given character the string span has + int count(char ch) const; + private: const char* str_; unsigned int size_; @@ -183,6 +186,9 @@ class node int get_children(const string_span& name); int get_children(const char* name); + static char* unescape_value(const string_span& value); + static void escape_value(const string_span& value, char** buffer); + void set_dirty(); void shift_buffers(ptrdiff_t offset);