// A small, strict JSON parser. It only has to read the request bodies that the // web UI sends, but it validates everything (depth limit, escapes, UTF-16 // surrogates) because those bodies arrive over a socket. #pragma once #include #include #include #include namespace lab { struct Json { enum class Type { Null, Bool, Number, String, Array, Object }; Type type = Type::Null; bool b = false; double n = 0; std::string s; std::vector a; std::vector> o; const Json* find(const std::string& key) const { if (type != Type::Object) return nullptr; for (const auto& kv : o) { if (kv.first == key) return &kv.second; } return nullptr; } std::string str(const std::string& key, const std::string& def = "") const { const Json* v = find(key); return (v && v->type == Type::String) ? v->s : def; } bool boolean(const std::string& key, bool def = false) const { const Json* v = find(key); return (v && v->type == Type::Bool) ? v->b : def; } double number(const std::string& key, double def = 0) const { const Json* v = find(key); return (v && v->type == Type::Number) ? v->n : def; } const std::vector* array(const std::string& key) const { const Json* v = find(key); return (v && v->type == Type::Array) ? &v->a : nullptr; } }; namespace detail { class JsonParser { public: explicit JsonParser(const std::string& text) : t_(text) {} bool parse(Json& out, std::string& error) { skipWs(); if (!value(out, 0)) { error = err_.empty() ? "invalid JSON" : err_; return false; } skipWs(); if (pos_ != t_.size()) { error = "unexpected text after the JSON value"; return false; } return true; } private: const std::string& t_; size_t pos_ = 0; std::string err_; bool fail(const char* msg) { if (err_.empty()) err_ = std::string(msg) + " at offset " + std::to_string(pos_); return false; } void skipWs() { while (pos_ < t_.size() && (t_[pos_] == ' ' || t_[pos_] == '\t' || t_[pos_] == '\n' || t_[pos_] == '\r')) ++pos_; } bool literal(const char* word) { size_t i = 0; while (word[i]) { if (pos_ + i >= t_.size() || t_[pos_ + i] != word[i]) return false; ++i; } pos_ += i; return true; } static void appendUtf8(std::string& out, uint32_t cp) { if (cp < 0x80) { out += static_cast(cp); } else if (cp < 0x800) { out += static_cast(0xC0 | (cp >> 6)); out += static_cast(0x80 | (cp & 0x3F)); } else if (cp < 0x10000) { out += static_cast(0xE0 | (cp >> 12)); out += static_cast(0x80 | ((cp >> 6) & 0x3F)); out += static_cast(0x80 | (cp & 0x3F)); } else { out += static_cast(0xF0 | (cp >> 18)); out += static_cast(0x80 | ((cp >> 12) & 0x3F)); out += static_cast(0x80 | ((cp >> 6) & 0x3F)); out += static_cast(0x80 | (cp & 0x3F)); } } bool hex4(uint32_t& v) { if (pos_ + 4 > t_.size()) return fail("truncated \\u escape"); v = 0; for (int i = 0; i < 4; ++i) { const char c = t_[pos_++]; v <<= 4; if (c >= '0' && c <= '9') v |= static_cast(c - '0'); else if (c >= 'a' && c <= 'f') v |= static_cast(c - 'a' + 10); else if (c >= 'A' && c <= 'F') v |= static_cast(c - 'A' + 10); else return fail("bad \\u escape"); } return true; } bool string(std::string& out) { if (pos_ >= t_.size() || t_[pos_] != '"') return fail("expected a string"); ++pos_; out.clear(); while (pos_ < t_.size()) { const unsigned char c = static_cast(t_[pos_++]); if (c == '"') return true; if (c < 0x20) return fail("control character in string"); if (c != '\\') { out += static_cast(c); continue; } if (pos_ >= t_.size()) break; const char e = t_[pos_++]; switch (e) { case '"': out += '"'; break; case '\\': out += '\\'; break; case '/': out += '/'; break; case 'b': out += '\b'; break; case 'f': out += '\f'; break; case 'n': out += '\n'; break; case 'r': out += '\r'; break; case 't': out += '\t'; break; case 'u': { uint32_t cp = 0; if (!hex4(cp)) return false; if (cp >= 0xD800 && cp <= 0xDBFF) { uint32_t lo = 0; if (pos_ + 1 < t_.size() && t_[pos_] == '\\' && t_[pos_ + 1] == 'u') { pos_ += 2; if (!hex4(lo)) return false; } if (lo >= 0xDC00 && lo <= 0xDFFF) cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); else cp = 0xFFFD; } else if (cp >= 0xDC00 && cp <= 0xDFFF) { cp = 0xFFFD; } appendUtf8(out, cp); break; } default: return fail("bad escape"); } } return fail("unterminated string"); } bool number(double& out) { const size_t start = pos_; if (pos_ < t_.size() && t_[pos_] == '-') ++pos_; bool digits = false; while (pos_ < t_.size() && ((t_[pos_] >= '0' && t_[pos_] <= '9') || t_[pos_] == '.' || t_[pos_] == 'e' || t_[pos_] == 'E' || t_[pos_] == '+' || t_[pos_] == '-')) { if (t_[pos_] >= '0' && t_[pos_] <= '9') digits = true; ++pos_; } if (!digits) return fail("bad number"); try { out = std::stod(t_.substr(start, pos_ - start)); } catch (...) { return fail("bad number"); } return true; } bool value(Json& out, int depth) { if (depth > 32) return fail("nesting too deep"); if (pos_ >= t_.size()) return fail("unexpected end"); const char c = t_[pos_]; if (c == '{') { ++pos_; out.type = Json::Type::Object; skipWs(); if (pos_ < t_.size() && t_[pos_] == '}') { ++pos_; return true; } for (;;) { skipWs(); std::string key; if (!string(key)) return false; skipWs(); if (pos_ >= t_.size() || t_[pos_] != ':') return fail("expected ':'"); ++pos_; skipWs(); Json v; if (!value(v, depth + 1)) return false; out.o.emplace_back(std::move(key), std::move(v)); skipWs(); if (pos_ < t_.size() && t_[pos_] == ',') { ++pos_; continue; } if (pos_ < t_.size() && t_[pos_] == '}') { ++pos_; return true; } return fail("expected ',' or '}'"); } } if (c == '[') { ++pos_; out.type = Json::Type::Array; skipWs(); if (pos_ < t_.size() && t_[pos_] == ']') { ++pos_; return true; } for (;;) { skipWs(); Json v; if (!value(v, depth + 1)) return false; out.a.push_back(std::move(v)); skipWs(); if (pos_ < t_.size() && t_[pos_] == ',') { ++pos_; continue; } if (pos_ < t_.size() && t_[pos_] == ']') { ++pos_; return true; } return fail("expected ',' or ']'"); } } if (c == '"') { out.type = Json::Type::String; return string(out.s); } if (c == 't') { out.type = Json::Type::Bool; out.b = true; return literal("true") || fail("bad literal"); } if (c == 'f') { out.type = Json::Type::Bool; out.b = false; return literal("false") || fail("bad literal"); } if (c == 'n') { out.type = Json::Type::Null; return literal("null") || fail("bad literal"); } out.type = Json::Type::Number; return number(out.n); } }; } // namespace detail inline bool parseJson(const std::string& text, Json& out, std::string& error) { out = Json(); detail::JsonParser p(text); return p.parse(out, error); } } // namespace lab