From b2294ced34a61b4173742e58beb7c0c21e9c27b0 Mon Sep 17 00:00:00 2001 From: cjkas Date: Sun, 11 Oct 2026 13:30:42 +0200 Subject: [PATCH] fix: escape control characters and count JSON string lengths correctly calcEscapedLength looped from strlen(raw) down to 1, counting the terminator instead of the first character. A value starting with a character that needs escaping was counted one byte short, so the fit check could let it be written one byte past the buffer. Control characters other than \b\f\n\r\t were copied raw, which isn't valid JSON. Both now go through one escapeChar helper, which also escapes the remaining control characters as \u00XX. Co-Authored-By: Claude Opus 5.5 --- src/JsonFormatter.cpp | 79 +++++++++++++++---------------------------- src/JsonFormatter.h | 3 ++ 2 files changed, 31 insertions(+), 51 deletions(-) diff --git a/src/JsonFormatter.cpp b/src/JsonFormatter.cpp index 714f64b..d54d81c 100644 --- a/src/JsonFormatter.cpp +++ b/src/JsonFormatter.cpp @@ -94,59 +94,36 @@ void JsonFormatter::_safecat(const char *val, bool escape) { if(escape) strcat(this->buff, "\""); } void JsonFormatter::_appendNumber(const char *name) { this->appendElem(name); this->_safecat(this->_numbuff); } -uint32_t JsonFormatter::calcEscapedLength(const char *raw) { - uint32_t len = 0; - for(size_t i = strlen(raw); i > 0; i--) { - switch(raw[i]) { - case '"': - case '/': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - case '\\': - len += 2; - break; - default: - len++; - break; - } +size_t JsonFormatter::escapeChar(char c, char *out) { + const char *esc = nullptr; + switch(c) { + case '"': esc = "\\\""; break; + case '/': esc = "\\/"; break; + case '\b': esc = "\\b"; break; + case '\f': esc = "\\f"; break; + case '\n': esc = "\\n"; break; + case '\r': esc = "\\r"; break; + case '\t': esc = "\\t"; break; + case '\\': esc = "\\\\"; break; + default: break; } + if(esc) { + strcpy(out, esc); + return 2; + } + // Any other control character is not allowed raw in a JSON string. + if((unsigned char)c < 0x20) return snprintf(out, 7, "\\u%04x", (unsigned char)c); + out[0] = c; + out[1] = 0x00; + return 1; +} +uint32_t JsonFormatter::calcEscapedLength(const char *raw) { + char tmp[7]; + uint32_t len = 0; + for(size_t i = 0; raw[i]; i++) len += escapeChar(raw[i], tmp); return len; } void JsonFormatter::escapeString(const char *raw, char *escaped) { - for(uint32_t i = 0; i < strlen(raw); i++) { - switch(raw[i]) { - case '"': - strcat(escaped, "\\\""); - break; - case '/': - strcat(escaped, "\\/"); - break; - case '\b': - strcat(escaped, "\\b"); - break; - case '\f': - strcat(escaped, "\\f"); - break; - case '\n': - strcat(escaped, "\\n"); - break; - case '\r': - strcat(escaped, "\\r"); - break; - case '\t': - strcat(escaped, "\\t"); - break; - case '\\': - strcat(escaped, "\\\\"); - break; - default: - size_t len = strlen(escaped); - escaped[len] = raw[i]; - escaped[len+1] = 0x00; - break; - } - } + char *out = escaped + strlen(escaped); + for(size_t i = 0; raw[i]; i++) out += escapeChar(raw[i], out); } diff --git a/src/JsonFormatter.h b/src/JsonFormatter.h index 5afe9c4..6554870 100644 --- a/src/JsonFormatter.h +++ b/src/JsonFormatter.h @@ -16,6 +16,9 @@ class JsonFormatter { virtual void _safecat(const char *val, bool escape = false); void _appendNumber(const char *name); public: + // Writes c as it must appear inside a JSON string (out needs 7 bytes) and + // returns its length. + static size_t escapeChar(char c, char *out); void escapeString(const char *raw, char *escaped); uint32_t calcEscapedLength(const char *raw); void beginObject(const char *name = nullptr);