- File: stat() only after a successful open() (previously ran first with its result ignored, feeding a missing file's garbage st_size into a realloc - could crash the whole APIServer on a request for a nonexistent static asset) - JString::setString(): always quote+escape the value instead of going through set()'s literal-value guess, which misclassified any string starting with digits (e.g. a UUID) as a bare numeric literal and wrote it unquoted, corrupting later edits to the same JSON document - MString::remove() (both overloads): fixed an off-by-one that read one byte past the buffer on a completely full string (found via ASan under concurrent load) - CGIFormData: added fields() so callers can iterate every parsed field without knowing names in advance (used to merge POST body into jet::Global::variables the same way query-string params already are)
245 lines
7.6 KiB
C++
245 lines
7.6 KiB
C++
#ifndef __JString_h__
|
|
#define __JString_h__
|
|
|
|
#include "MString.h"
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
namespace coreutils {
|
|
|
|
///
|
|
/// Use the JString object when you need a JSON interface to C++. JString uses
|
|
/// an MString to store a JSON string representing the object(s) contained.
|
|
/// The [] operator is overriden from the ZString and provides a method to
|
|
/// access the objects elements using a named path as the index to the items.
|
|
///
|
|
/// JString default constructor will create an empty JString object pointing
|
|
/// to an empty object ("{}").
|
|
///
|
|
/// Use a constructor or assignment operator to initialize a JString to an
|
|
/// initial JSON string.
|
|
///
|
|
/// Several constructors are available including a handle to a std::FILE
|
|
/// object to initialize the JSON string. On destruction the file is saved back.
|
|
///
|
|
/// The [] operator can be used to access elements within the JSON string.
|
|
/// Assigning a value dispatches on the C++ type of the right-hand side, so the
|
|
/// resulting JSON is correctly typed rather than always being a quoted string:
|
|
///
|
|
/// jstring["name"] = "value"; // JSON string, escaped correctly
|
|
/// jstring["count"] = 42; // JSON number (integer)
|
|
/// jstring["price"] = 19.95; // JSON number
|
|
/// jstring["active"] = true; // JSON boolean
|
|
/// jstring["notes"].setNull(); // JSON null
|
|
///
|
|
/// std::cout << jstring["name"];
|
|
///
|
|
/// Typed read access is available directly on the path (no need to go through
|
|
/// operator[] first):
|
|
///
|
|
/// std::string name = jstring.getString("name");
|
|
/// double price = jstring.getNumber("price");
|
|
/// bool active = jstring.getBool("active");
|
|
/// bool missing = !jstring.exists("notfound");
|
|
/// JSONType t = jstring.getType("count"); // JSONType::NUMBER
|
|
///
|
|
|
|
enum class JSONType {
|
|
STRING, NUMBER, BOOLEAN, JNULL, ARRAY, OBJECT, NOT_FOUND
|
|
};
|
|
|
|
class JString : virtual public MString {
|
|
|
|
public:
|
|
|
|
JString();
|
|
|
|
void set(ZString &path, ZString &value);
|
|
|
|
///
|
|
/// Inserts/replaces path with already-formatted, valid JSON text
|
|
/// (a quoted+escaped string, or a bare number/bool/null literal) -
|
|
/// the locate()/skipValue()/insert() mechanics set() and setString()
|
|
/// both share, factored out so setString() can force string quoting
|
|
/// unconditionally instead of going through set()'s own
|
|
/// isLiteralValue() guess (see setString()'s own comment in the
|
|
/// .cpp for why that guess is wrong for a string setter).
|
|
///
|
|
|
|
void setFormatted(ZString path, MString formatted);
|
|
|
|
///
|
|
/// Use the find method to look through an object for member specified by
|
|
/// the key in parameter 1. The cursor must be pointing to the { character
|
|
/// that begins the object's list. Returns the raw (still-escaped, for
|
|
/// strings) span of the value - use getString()/getNumber()/getBool()/
|
|
/// isNull() for decoded, typed access instead.
|
|
///
|
|
|
|
ZString find(ZString path);
|
|
|
|
bool locate(ZString path);
|
|
bool notfirst;
|
|
ZString path;
|
|
|
|
///
|
|
/// Type-aware access. All of these call locate() internally, so path uses
|
|
/// the same dotted/bracketed syntax as find()/set() (e.g. "a.b[2].c").
|
|
///
|
|
|
|
JSONType getType(ZString path);
|
|
bool exists(ZString path);
|
|
bool isNull(ZString path);
|
|
|
|
std::string getString(ZString path); ///< Escape-decoded string content.
|
|
double getNumber(ZString path);
|
|
long getInt(ZString path);
|
|
bool getBool(ZString path);
|
|
|
|
void setString(ZString path, std::string value); ///< Escapes value automatically.
|
|
void setNumber(ZString path, double value);
|
|
void setInt(ZString path, long value);
|
|
void setBool(ZString path, bool value);
|
|
void setNull(ZString path);
|
|
|
|
///
|
|
/// JSON string escape/unescape helpers (exposed as static utilities since
|
|
/// they operate on plain text, not on this object's own JSON document).
|
|
///
|
|
|
|
static std::string jsonEscape(ZString value);
|
|
static std::string jsonUnescape(ZString raw);
|
|
|
|
class Proxy {
|
|
|
|
public:
|
|
Proxy(JString &jstring, ZString key) : jstring(jstring), key(key) {}
|
|
|
|
friend std::ostream &operator<<(std::ostream &os, const Proxy &proxy);
|
|
|
|
///
|
|
/// Proxy offers several implicit conversion operators below (to
|
|
/// ZString, MString, std::string, double, bool) so a single $[...]-
|
|
/// style access can be used wherever any of those types is expected.
|
|
/// That flexibility makes "MString << Proxy" genuinely ambiguous on
|
|
/// its own - operator ZString() and operator MString() (via its own
|
|
/// implicit upcast to ZString) both satisfy MString::operator<<(ZString),
|
|
/// and operator double()/operator bool() both satisfy
|
|
/// MString::operator<<(int) via a standard numeric conversion - so
|
|
/// the compiler has no way to prefer one path over another. This
|
|
/// exact-match overload (mirroring the ostream one above, which
|
|
/// solves the same problem for std::cout << proxy) resolves it by
|
|
/// giving MString a candidate that needs no conversion at all.
|
|
///
|
|
|
|
friend MString &operator<<(MString &ms, const Proxy &proxy);
|
|
|
|
operator coreutils::ZString () {
|
|
return jstring.find(key);
|
|
}
|
|
|
|
operator coreutils::MString () {
|
|
return jstring.find(key);
|
|
}
|
|
|
|
operator std::string () {
|
|
return jstring.getString(key);
|
|
}
|
|
|
|
operator double () {
|
|
return jstring.getNumber(key);
|
|
}
|
|
|
|
operator bool () {
|
|
return jstring.getBool(key);
|
|
}
|
|
|
|
Proxy& operator=(coreutils::MString value) {
|
|
jstring.setString(key, value.str());
|
|
jstring.changed(key, value);
|
|
return *this;
|
|
}
|
|
|
|
Proxy& operator=(const char *value) {
|
|
jstring.setString(key, std::string(value));
|
|
jstring.changed(key, ZString(value));
|
|
return *this;
|
|
}
|
|
|
|
Proxy& operator=(double value) {
|
|
jstring.setNumber(key, value);
|
|
return *this;
|
|
}
|
|
|
|
Proxy& operator=(int value) {
|
|
jstring.setInt(key, value);
|
|
return *this;
|
|
}
|
|
|
|
Proxy& operator=(long value) {
|
|
jstring.setInt(key, value);
|
|
return *this;
|
|
}
|
|
|
|
Proxy& operator=(bool value) {
|
|
jstring.setBool(key, value);
|
|
return *this;
|
|
}
|
|
|
|
void setNull() {
|
|
jstring.setNull(key);
|
|
}
|
|
|
|
private:
|
|
JString &jstring;
|
|
ZString key;
|
|
};
|
|
|
|
Proxy operator[](coreutils::ZString key) {
|
|
return Proxy(*this, key);
|
|
}
|
|
|
|
JString & operator=(coreutils:: ZString value);
|
|
|
|
MString pretty();
|
|
|
|
virtual void changed(ZString key, ZString value);
|
|
|
|
protected:
|
|
|
|
///
|
|
/// Strips whitespace between JSON structural tokens (indentation,
|
|
/// newlines, spaces after ':'/',') - the parser expects compact JSON
|
|
/// internally. operator=(ZString) already calls this for in-memory
|
|
/// assignment; JFile's constructor also calls it after loading a
|
|
/// file's raw bytes, which otherwise bypasses this entirely (see
|
|
/// JFile.cpp). Protected rather than private specifically so JFile
|
|
/// can reach it.
|
|
///
|
|
|
|
void removeWhitespace();
|
|
|
|
private:
|
|
|
|
///
|
|
/// Structural helpers used internally by locate()/set() to correctly skip
|
|
/// past a value of any JSON type without being confused by JSON-structural
|
|
/// characters (quotes, braces, brackets) appearing inside string literals.
|
|
///
|
|
|
|
ZString findRelative(ZString path);
|
|
JSONType typeAtCursor();
|
|
void skipValue();
|
|
void skipString();
|
|
void skipContainer();
|
|
void skipNumber();
|
|
ZString captureString();
|
|
ZString captureContainer();
|
|
bool isLiteralValue(ZString value);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|