#include #include #include #include #include #include #include #define JSON_MAX_DEPTH 100 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-dynamic-exception-spec" POCO_IMPLEMENT_EXCEPTION(JSONException, Poco::Exception, "JSONException") // NOLINT(cert-err60-cpp, modernize-use-noexcept, hicpp-use-noexcept) #pragma clang diagnostic pop /// Read unsigned integer in a simple form from a non-0-terminated string. static UInt64 readUIntText(const char * buf, const char * end) { UInt64 x = 0; if (buf == end) throw JSONException("JSON: cannot parse unsigned integer: unexpected end of data."); while (buf != end) { switch (*buf) { case '+': break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': x *= 10; x += *buf - '0'; break; default: return x; } ++buf; } return x; } /// Read signed integer in a simple form from a non-0-terminated string. static Int64 readIntText(const char * buf, const char * end) { bool negative = false; UInt64 x = 0; if (buf == end) throw JSONException("JSON: cannot parse signed integer: unexpected end of data."); bool run = true; while (buf != end && run) { switch (*buf) { case '+': break; case '-': negative = true; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': x *= 10; x += *buf - '0'; break; default: run = false; break; } ++buf; } return negative ? -x : x; } /// Read floating point number in simple format, imprecisely, from a non-0-terminated string. static double readFloatText(const char * buf, const char * end) { bool negative = false; double x = 0; bool after_point = false; double power_of_ten = 1; if (buf == end) throw JSONException("JSON: cannot parse floating point number: unexpected end of data."); bool run = true; while (buf != end && run) { switch (*buf) { case '+': break; case '-': negative = true; break; case '.': after_point = true; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (after_point) { power_of_ten /= 10; x += (*buf - '0') * power_of_ten; } else { x *= 10; x += *buf - '0'; } break; case 'e': case 'E': { ++buf; auto exponent = readIntText(buf, end); x *= preciseExp10(static_cast(exponent)); run = false; break; } default: run = false; break; } ++buf; } if (negative) x = -x; return x; } void JSON::checkInit() const { if (!(ptr_begin < ptr_end)) throw JSONException("JSON: begin >= end."); if (level > JSON_MAX_DEPTH) throw JSONException("JSON: too deep."); } JSON::ElementType JSON::getType() const { switch (*ptr_begin) { case '{': return TYPE_OBJECT; case '[': return TYPE_ARRAY; case 't': case 'f': return TYPE_BOOL; case 'n': return TYPE_NULL; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return TYPE_NUMBER; case '"': { /// Is it a string or a name-value pair? Pos after_string = skipString(); if (after_string < ptr_end && *after_string == ':') return TYPE_NAME_VALUE_PAIR; else return TYPE_STRING; } default: throw JSONException(std::string("JSON: unexpected char ") + *ptr_begin + ", expected one of '{[tfn-0123456789\"'"); } } void JSON::checkPos(Pos pos) const { if (pos >= ptr_end || ptr_begin == nullptr) throw JSONException("JSON: unexpected end of data."); } JSON::Pos JSON::skipString() const { Pos pos = ptr_begin; checkPos(pos); if (*pos != '"') throw JSONException(std::string("JSON: expected \", got ") + *pos); ++pos; /// fast path: find next double quote. If it is not escaped by backslash - then it's an end of string (assuming JSON is valid). Pos closing_quote = reinterpret_cast(memchr(reinterpret_cast(pos), '\"', ptr_end - pos)); if (nullptr != closing_quote && closing_quote[-1] != '\\') return closing_quote + 1; /// slow path while (pos < ptr_end && *pos != '"') { if (*pos == '\\') { ++pos; checkPos(pos); if (*pos == 'u') { pos += 4; checkPos(pos); } } ++pos; } checkPos(pos); if (*pos != '"') throw JSONException(std::string("JSON: expected \", got ") + *pos); ++pos; return pos; } JSON::Pos JSON::skipNumber() const { Pos pos = ptr_begin; checkPos(pos); if (*pos == '-') ++pos; while (pos < ptr_end && *pos >= '0' && *pos <= '9') ++pos; if (pos < ptr_end && *pos == '.') ++pos; while (pos < ptr_end && *pos >= '0' && *pos <= '9') ++pos; if (pos < ptr_end && (*pos == 'e' || *pos == 'E')) ++pos; if (pos < ptr_end && *pos == '-') ++pos; while (pos < ptr_end && *pos >= '0' && *pos <= '9') ++pos; return pos; } JSON::Pos JSON::skipBool() const { Pos pos = ptr_begin; checkPos(pos); if (*ptr_begin == 't') pos += 4; else if (*ptr_begin == 'f') pos += 5; else throw JSONException("JSON: expected true or false."); return pos; } JSON::Pos JSON::skipNull() const { return ptr_begin + 4; } JSON::Pos JSON::skipNameValuePair() const { Pos pos = skipString(); checkPos(pos); if (*pos != ':') throw JSONException("JSON: expected :."); ++pos; return JSON(pos, ptr_end, level + 1).skipElement(); } JSON::Pos JSON::skipArray() const { if (!isArray()) throw JSONException("JSON: expected ["); Pos pos = ptr_begin; ++pos; checkPos(pos); if (*pos == ']') return ++pos; while (true) { pos = JSON(pos, ptr_end, level + 1).skipElement(); checkPos(pos); switch (*pos) { case ',': ++pos; break; case ']': return ++pos; default: throw JSONException(std::string("JSON: expected one of ',]', got ") + *pos); } } } JSON::Pos JSON::skipObject() const { if (!isObject()) throw JSONException("JSON: expected {"); Pos pos = ptr_begin; ++pos; checkPos(pos); if (*pos == '}') return ++pos; while (true) { pos = JSON(pos, ptr_end, level + 1).skipNameValuePair(); checkPos(pos); switch (*pos) { case ',': ++pos; break; case '}': return ++pos; default: throw JSONException(std::string("JSON: expected one of ',}', got ") + *pos); } } } JSON::Pos JSON::skipElement() const { ElementType type = getType(); switch (type) { case TYPE_NULL: return skipNull(); case TYPE_BOOL: return skipBool(); case TYPE_NUMBER: return skipNumber(); case TYPE_STRING: return skipString(); case TYPE_NAME_VALUE_PAIR: return skipNameValuePair(); case TYPE_ARRAY: return skipArray(); case TYPE_OBJECT: return skipObject(); default: throw JSONException("Logical error in JSON: unknown element type: " + std::to_string(type)); } } size_t JSON::size() const { size_t i = 0; for (const_iterator it = begin(); it != end(); ++it) ++i; return i; } bool JSON::empty() const { return size() == 0; } JSON JSON::operator[] (size_t n) const { ElementType type = getType(); if (type != TYPE_ARRAY) throw JSONException("JSON: not array when calling operator[](size_t) method."); Pos pos = ptr_begin; ++pos; checkPos(pos); size_t i = 0; const_iterator it = begin(); while (i < n && it != end()) { ++it; ++i; } if (i != n) throw JSONException("JSON: array index " + std::to_string(n) + " out of bounds."); return *it; } JSON::Pos JSON::searchField(const char * data, size_t size) const { ElementType type = getType(); if (type != TYPE_OBJECT) throw JSONException("JSON: not object when calling operator[](const char *) or has(const char *) method."); const_iterator it = begin(); for (; it != end(); ++it) { if (!it->hasEscapes()) { const auto current_name = it->getRawName(); if (current_name.size() == size && 0 == memcmp(current_name.data(), data, size)) break; } else { std::string current_name = it->getName(); if (current_name.size() == size && 0 == memcmp(current_name.data(), data, size)) break; } } if (it == end()) return nullptr; else return it->data(); } bool JSON::hasEscapes() const { Pos pos = ptr_begin + 1; while (pos < ptr_end && *pos != '"' && *pos != '\\') ++pos; if (*pos == '"') return false; else if (*pos == '\\') return true; throw JSONException("JSON: unexpected end of data."); } bool JSON::hasSpecialChars() const { Pos pos = ptr_begin + 1; while (pos < ptr_end && *pos != '"' && *pos != '\\' && *pos != '\r' && *pos != '\n' && *pos != '\t' && *pos != '\f' && *pos != '\b' && *pos != '\0' && *pos != '\'') ++pos; if (*pos == '"') return false; else if (pos < ptr_end) return true; throw JSONException("JSON: unexpected end of data."); } JSON JSON::operator[] (const std::string & name) const { Pos pos = searchField(name); if (!pos) throw JSONException("JSON: there is no element '" + std::string(name) + "' in object."); return JSON(pos, ptr_end, level + 1).getValue(); } bool JSON::has(const char * data, size_t size) const { return nullptr != searchField(data, size); } double JSON::getDouble() const { return readFloatText(ptr_begin, ptr_end); } Int64 JSON::getInt() const { return readIntText(ptr_begin, ptr_end); } UInt64 JSON::getUInt() const { return readUIntText(ptr_begin, ptr_end); } bool JSON::getBool() const { if (*ptr_begin == 't') return true; if (*ptr_begin == 'f') return false; throw JSONException("JSON: cannot parse boolean."); } std::string JSON::getString() const { Pos s = ptr_begin; if (*s != '"') throw JSONException(std::string("JSON: expected \", got ") + *s); ++s; checkPos(s); std::string buf; do { Pos p = find_first_symbols<'\\','"'>(s, ptr_end); if (p >= ptr_end) { break; } buf.append(s, p); s = p; switch (*s) { case '\\': ++s; checkPos(s); switch (*s) { case '"': buf += '"'; break; case '\\': buf += '\\'; break; case '/': buf += '/'; break; case 'b': buf += '\b'; break; case 'f': buf += '\f'; break; case 'n': buf += '\n'; break; case 'r': buf += '\r'; break; case 't': buf += '\t'; break; case 'u': { Poco::UTF8Encoding utf8; ++s; checkPos(s + 4); std::string hex(s, 4); s += 3; int unicode; try { unicode = Poco::NumberParser::parseHex(hex); } catch (const Poco::SyntaxException &) { throw JSONException("JSON: incorrect syntax: incorrect HEX code."); } buf.resize(buf.size() + 6); /// Max size of UTF-8 sequence, including pre-standard mapping of UCS-4 to UTF-8. int res = utf8.convert(unicode, reinterpret_cast(const_cast(buf.data())) + buf.size() - 6, 6); if (!res) throw JSONException("JSON: cannot convert unicode " + std::to_string(unicode) + " to UTF8."); buf.resize(buf.size() - 6 + res); break; } default: buf += *s; break; } ++s; break; case '"': return buf; default: throw JSONException("find_first_symbols<...>() failed in unexpected way"); } } while (s < ptr_end); throw JSONException("JSON: incorrect syntax (expected end of string, found end of JSON)."); } std::string JSON::getName() const { return getString(); } std::string_view JSON::getRawString() const { Pos s = ptr_begin; if (*s != '"') throw JSONException(std::string("JSON: expected \", got ") + *s); ++s; while (s != ptr_end && *s != '"') ++s; if (s != ptr_end) return std::string_view(ptr_begin + 1, s - ptr_begin - 1); throw JSONException("JSON: incorrect syntax (expected end of string, found end of JSON)."); } std::string_view JSON::getRawName() const { return getRawString(); } JSON JSON::getValue() const { Pos pos = skipString(); checkPos(pos); if (*pos != ':') throw JSONException("JSON: expected :."); ++pos; checkPos(pos); return JSON(pos, ptr_end, level + 1); } double JSON::toDouble() const { ElementType type = getType(); if (type == TYPE_NUMBER) return getDouble(); else if (type == TYPE_STRING) return JSON(ptr_begin + 1, ptr_end, level + 1).getDouble(); else throw JSONException("JSON: cannot convert value to double."); } Int64 JSON::toInt() const { ElementType type = getType(); if (type == TYPE_NUMBER) return getInt(); else if (type == TYPE_STRING) return JSON(ptr_begin + 1, ptr_end, level + 1).getInt(); else throw JSONException("JSON: cannot convert value to signed integer."); } UInt64 JSON::toUInt() const { ElementType type = getType(); if (type == TYPE_NUMBER) return getUInt(); else if (type == TYPE_STRING) return JSON(ptr_begin + 1, ptr_end, level + 1).getUInt(); else throw JSONException("JSON: cannot convert value to unsigned integer."); } std::string JSON::toString() const { ElementType type = getType(); if (type == TYPE_STRING) return getString(); else { Pos pos = skipElement(); return std::string(ptr_begin, pos - ptr_begin); } } JSON::iterator JSON::iterator::begin() const { ElementType type = getType(); if (type != TYPE_ARRAY && type != TYPE_OBJECT) throw JSONException("JSON: not array or object when calling begin() method."); Pos pos = ptr_begin + 1; checkPos(pos); if (*pos == '}' || *pos == ']') return end(); return JSON(pos, ptr_end, level + 1); } JSON::iterator JSON::iterator::end() const { return JSON(nullptr, ptr_end, level + 1); } JSON::iterator & JSON::iterator::operator++() { Pos pos = skipElement(); checkPos(pos); if (*pos != ',') ptr_begin = nullptr; else { ++pos; checkPos(pos); ptr_begin = pos; } return *this; } JSON::iterator JSON::iterator::operator++(int) // NOLINT { iterator copy(*this); ++*this; return copy; } template <> double JSON::get() const { return getDouble(); } template <> std::string JSON::get() const { return getString(); } template <> Int64 JSON::get() const { return getInt(); } template <> UInt64 JSON::get() const { return getUInt(); } template <> bool JSON::get() const { return getBool(); } template <> bool JSON::isType() const { return isString(); } template <> bool JSON::isType() const { return isNumber(); } template <> bool JSON::isType() const { return isNumber(); } template <> bool JSON::isType() const { return isBool(); }