/// \file json.cpp Holds all JSON-related code. #include "json.h" #include #include #include #include //for uint64_t #include //for memcpy #include //for htonl int JSON::Value::c2hex(int c){ if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return 0; } std::string JSON::Value::read_string(int separator, std::istream & fromstream){ std::string out; bool escaped = false; while (fromstream.good()){ int c = fromstream.get(); if (c == '\\'){ escaped = true; continue; } if (escaped){ switch (c){ 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':{ int d1 = fromstream.get(); int d2 = fromstream.get(); int d3 = fromstream.get(); int d4 = fromstream.get(); c = c2hex(d4) + (c2hex(d3) << 4) + (c2hex(d2) << 8) + (c2hex(d1) << 16); } default: out += (char)c; break; } }else{ if (c == separator){ return out; }else{ out += (char)c; } } } return out; } std::string JSON::Value::string_escape(std::string val){ std::string out = "\""; for (unsigned int i = 0; i < val.size(); ++i){ switch (val[i]){ case '"': out += "\\\""; break; case '\\': out += "\\\\"; break; case '\n': out += "\\n"; break; case '\b': out += "\\b"; break; case '\f': out += "\\f"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: out += val[i]; } } out += "\""; return out; } /// Skips an std::istream forward until any of the following characters is seen: ,]} void JSON::Value::skipToEnd(std::istream & fromstream){ while (fromstream.good()){ char peek = fromstream.peek(); if (peek == ','){return;} if (peek == ']'){return;} if (peek == '}'){return;} peek = fromstream.get(); } } /// Sets this JSON::Value to null; JSON::Value::Value(){ null(); } /// Sets this JSON::Value to read from this position in the std::istream JSON::Value::Value(std::istream & fromstream){ null(); bool reading_object = false; bool reading_array = false; while (fromstream.good()){ int c = fromstream.peek(); switch (c){ case '{': reading_object = true; c = fromstream.get(); myType = OBJECT; break; case '[':{ reading_array = true; c = fromstream.get(); myType = ARRAY; Value tmp = JSON::Value(fromstream); if (tmp.myType != EMPTY){ append(tmp); } break; } case '\'': case '"': c = fromstream.get(); if (!reading_object){ myType = STRING; strVal = read_string(c, fromstream); return; }else{ std::string tmpstr = read_string(c, fromstream); (*this)[tmpstr] = JSON::Value(fromstream); } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': c = fromstream.get(); myType = INTEGER; intVal *= 10; intVal += c - '0'; break; case ',': if (!reading_object && !reading_array) return; c = fromstream.get(); if (reading_array){ append(JSON::Value(fromstream)); } break; case '}': if (reading_object){c = fromstream.get();} return; break; case ']': if (reading_array){c = fromstream.get();} return; break; case 't': case 'T': skipToEnd(fromstream); myType = BOOL; intVal = 1; return; break; case 'f': case 'F': skipToEnd(fromstream); myType = BOOL; intVal = 0; return; break; case 'n': case 'N': skipToEnd(fromstream); myType = EMPTY; return; break; default: c = fromstream.get();//ignore this character continue; break; } } } /// Sets this JSON::Value to the given string. JSON::Value::Value(const std::string & val){ myType = STRING; strVal = val; } /// Sets this JSON::Value to the given string. JSON::Value::Value(const char * val){ myType = STRING; strVal = val; } /// Sets this JSON::Value to the given integer. JSON::Value::Value(long long int val){ myType = INTEGER; intVal = val; } /// Compares a JSON::Value to another for equality. bool JSON::Value::operator==(const JSON::Value & rhs) const{ if (myType != rhs.myType) return false; if (myType == INTEGER || myType == BOOL){return intVal == rhs.intVal;} if (myType == STRING){return strVal == rhs.strVal;} if (myType == EMPTY){return true;} if (myType == OBJECT){ if (objVal.size() != rhs.objVal.size()) return false; for (std::map::const_iterator it = objVal.begin(); it != objVal.end(); ++it){ if (!rhs.isMember(it->first)){return false;} if (it->second != rhs.objVal.find(it->first)->second){return false;} } return true; } if (myType == ARRAY){ if (arrVal.size() != rhs.arrVal.size()) return false; int i = 0; for (std::deque::const_iterator it = arrVal.begin(); it != arrVal.end(); ++it){ if (*it != rhs.arrVal[i]){return false;} i++; } return true; } return true; } /// Compares a JSON::Value to another for equality. bool JSON::Value::operator!=(const JSON::Value & rhs) const{ return !((*this) == rhs); } /// Sets this JSON::Value to the given boolean. JSON::Value & JSON::Value::operator=(const bool &rhs){ null(); myType = BOOL; if (rhs) intVal = 1; return *this; } /// Sets this JSON::Value to the given string. JSON::Value & JSON::Value::operator=(const std::string &rhs){ null(); myType = STRING; strVal = rhs; return *this; } /// Sets this JSON::Value to the given string. JSON::Value & JSON::Value::operator=(const char * rhs){ return ((*this) = (std::string)rhs); } /// Sets this JSON::Value to the given integer. JSON::Value & JSON::Value::operator=(const long long int &rhs){ null(); myType = INTEGER; intVal = rhs; return *this; } /// Sets this JSON::Value to the given integer. JSON::Value & JSON::Value::operator=(const int &rhs){ return ((*this) = (long long int)rhs); } /// Sets this JSON::Value to the given integer. JSON::Value & JSON::Value::operator=(const unsigned int &rhs){ return ((*this) = (long long int)rhs); } /// Automatic conversion to long long int - returns 0 if not convertable. JSON::Value::operator long long int(){ if (myType == INTEGER){ return intVal; } if (myType == STRING){ return atoll(strVal.c_str()); } return 0; } /// Automatic conversion to std::string. /// Returns the raw string value if available, otherwise calls toString(). JSON::Value::operator std::string(){ if (myType == STRING){ return strVal; }else{ if (myType == EMPTY){ return ""; }else{ return toString(); } } } /// Automatic conversion to bool. /// Returns true if there is anything meaningful stored into this value. JSON::Value::operator bool(){ if (myType == STRING){return strVal != "";} if (myType == INTEGER){return intVal != 0;} if (myType == BOOL){return intVal != 0;} if (myType == OBJECT){return size() > 0;} if (myType == ARRAY){return size() > 0;} if (myType == EMPTY){return false;} return false;//unimplemented? should never happen... } /// Explicit conversion to std::string. const std::string JSON::Value::asString(){ return (std::string)*this; } /// Explicit conversion to long long int. const long long int JSON::Value::asInt(){ return (long long int)*this; } /// Explicit conversion to bool. const bool JSON::Value::asBool(){ return (bool)*this; } /// Retrieves or sets the JSON::Value at this position in the object. /// Converts destructively to object if not already an object. JSON::Value & JSON::Value::operator[](const std::string i){ if (myType != OBJECT){ null(); myType = OBJECT; } return objVal[i]; } /// Retrieves or sets the JSON::Value at this position in the object. /// Converts destructively to object if not already an object. JSON::Value & JSON::Value::operator[](const char * i){ if (myType != OBJECT){ null(); myType = OBJECT; } return objVal[i]; } /// Retrieves or sets the JSON::Value at this position in the array. /// Converts destructively to array if not already an array. JSON::Value & JSON::Value::operator[](unsigned int i){ if (myType != ARRAY){ null(); myType = ARRAY; } while (i >= arrVal.size()){ append(JSON::Value()); } return arrVal[i]; } /// Packs to a std::string for transfer over the network. /// If the object is a container type, this function will call itself recursively and contain all contents. /// As a side effect, this function clear the internal buffer of any object-types. std::string JSON::Value::toPacked(){ std::string r; if (isInt() || isNull() || isBool()){ r += 0x01; uint64_t numval = intVal; r += *(((char*)&numval)+7); r += *(((char*)&numval)+6); r += *(((char*)&numval)+5); r += *(((char*)&numval)+4); r += *(((char*)&numval)+3); r += *(((char*)&numval)+2); r += *(((char*)&numval)+1); r += *(((char*)&numval)); } if (isString()){ r += 0x02; r += strVal.size() / (256*256*256); r += strVal.size() / (256*256); r += strVal.size() / 256; r += strVal.size() % 256; r += strVal; } if (isObject()){ r += 0xE0; if (objVal.size() > 0){ for (JSON::ObjIter it = objVal.begin(); it != objVal.end(); it++){ r += it->first.size() / 256; r += it->first.size() % 256; r += it->first; r += it->second.toPacked(); } } r += (char)0x0; r += (char)0x0; r += (char)0xEE; strVal.clear(); } if (isArray()){ r += 0x0A; for (JSON::ArrIter it = arrVal.begin(); it != arrVal.end(); it++){ r += it->toPacked(); } r += (char)0x0; r += (char)0x0; r += (char)0xEE; } return r; };//toPacked /// Packs any object-type JSON::Value to a std::string for transfer over the network, including proper DTMI header. /// Non-object-types will print an error to stderr and return an empty string. /// This function returns a reference to an internal buffer where the prepared data is kept. /// The internal buffer is *not* made stale if any changes occur inside the object - subsequent calls to toPacked() will clear the buffer. std::string & JSON::Value::toNetPacked(){ static std::string emptystring; //check if this is legal if (myType != OBJECT){ fprintf(stderr, "Fatal error: Only objects may be NetPacked! Aborting.\n"); return emptystring; } //if sneaky storage doesn't contain correct data, re-calculate it if (strVal.size() == 0 || strVal[0] != 'D' || strVal[1] != 'T'){ std::string packed = toPacked(); strVal.resize(packed.size() + 8); //insert proper header for this type of data if (isMember("datatype")){ memcpy((void*)strVal.c_str(), "DTPD", 4); }else{ memcpy((void*)strVal.c_str(), "DTSC", 4); } //insert the packet length at bytes 4-7 unsigned int size = htonl(packed.size()); memcpy((void*)(strVal.c_str() + 4), (void*)&size, 4); //copy the rest of the string memcpy((void*)(strVal.c_str() + 8), packed.c_str(), packed.size()); } return strVal; } /// Converts this JSON::Value to valid JSON notation and returns it. /// Makes absolutely no attempts to pretty-print anything. :-) std::string JSON::Value::toString(){ switch (myType){ case INTEGER: { std::stringstream st; st << intVal; return st.str(); break; } case STRING: { return string_escape(strVal); break; } case ARRAY: { std::string tmp = "["; if (arrVal.size() > 0){ for (ArrIter it = ArrBegin(); it != ArrEnd(); it++){ tmp += it->toString(); if (it + 1 != ArrEnd()){tmp += ",";} } } tmp += "]"; return tmp; break; } case OBJECT: { std::string tmp2 = "{"; if (objVal.size() > 0){ ObjIter it3 = ObjEnd(); --it3; for (ObjIter it2 = ObjBegin(); it2 != ObjEnd(); it2++){ tmp2 += "\"" + it2->first + "\":"; tmp2 += it2->second.toString(); if (it2 != it3){tmp2 += ",";} } } tmp2 += "}"; return tmp2; break; } case EMPTY: default: return "null"; } return "null";//should never get here... } /// Converts this JSON::Value to valid JSON notation and returns it. /// Makes an attempt at pretty-printing. std::string JSON::Value::toPrettyString(int indentation){ switch (myType){ case INTEGER: { std::stringstream st; st << intVal; return st.str(); break; } case STRING: { for (unsigned int i = 0; i < 5 && i < strVal.size(); ++i){ if (strVal[i] < 32 || strVal[i] > 125){ return JSON::Value((long long int)strVal.size()).asString()+" bytes of binary data"; } } return string_escape(strVal); break; } case ARRAY: { if (arrVal.size() > 0){ std::string tmp = "[\n"; for (ArrIter it = ArrBegin(); it != ArrEnd(); it++){ tmp += std::string(indentation+2, ' ')+it->toPrettyString(indentation+2); if (it + 1 != ArrEnd()){tmp += ",\n";} } tmp += "\n"+std::string(indentation, ' ')+"]"; return tmp; }else{ return "[]"; } break; } case OBJECT: { if (objVal.size() > 0){ std::string tmp2 = "{\n"; ObjIter it3 = ObjEnd(); --it3; for (ObjIter it2 = ObjBegin(); it2 != ObjEnd(); it2++){ tmp2 += std::string(indentation+2, ' ')+"\"" + it2->first + "\":"; tmp2 += it2->second.toPrettyString(indentation+2); if (it2 != it3){tmp2 += ",\n";} } tmp2 += "\n"+std::string(indentation, ' ')+"}"; return tmp2; }else{ return "{}"; } break; } case EMPTY: default: return "null"; } return "null";//should never get here... } /// Appends the given value to the end of this JSON::Value array. /// Turns this value into an array if it is not already one. void JSON::Value::append(const JSON::Value & rhs){ if (myType != ARRAY){ null(); myType = ARRAY; } arrVal.push_back(rhs); } /// Prepends the given value to the beginning of this JSON::Value array. /// Turns this value into an array if it is not already one. void JSON::Value::prepend(const JSON::Value & rhs){ if (myType != ARRAY){ null(); myType = ARRAY; } arrVal.push_front(rhs); } /// For array and object JSON::Value objects, reduces them /// so they contain at most size elements, throwing away /// the first elements and keeping the last ones. /// Does nothing for other JSON::Value types, nor does it /// do anything if the size is already lower or equal to the /// given size. void JSON::Value::shrink(unsigned int size){ if (myType == ARRAY){ while (arrVal.size() > size){arrVal.pop_front();} return; } if (myType == OBJECT){ while (objVal.size() > size){objVal.erase(objVal.begin());} return; } } /// For object JSON::Value objects, removes the member with /// the given name, if it exists. Has no effect otherwise. void JSON::Value::removeMember(const std::string & name){ objVal.erase(name); } /// For object JSON::Value objects, returns true if the /// given name is a member. Returns false otherwise. bool JSON::Value::isMember(const std::string & name) const{ return objVal.count(name) > 0; } /// Returns true if this object is an integer. bool JSON::Value::isInt() const{ return (myType == INTEGER); } /// Returns true if this object is a string. bool JSON::Value::isString() const{ return (myType == STRING); } /// Returns true if this object is a bool. bool JSON::Value::isBool() const{ return (myType == BOOL); } /// Returns true if this object is an object. bool JSON::Value::isObject() const{ return (myType == OBJECT); } /// Returns true if this object is an array. bool JSON::Value::isArray() const{ return (myType == ARRAY); } /// Returns true if this object is null. bool JSON::Value::isNull() const{ return (myType == EMPTY); } /// Returns an iterator to the begin of the object map, if any. JSON::ObjIter JSON::Value::ObjBegin(){ return objVal.begin(); } /// Returns an iterator to the end of the object map, if any. JSON::ObjIter JSON::Value::ObjEnd(){ return objVal.end(); } /// Returns an iterator to the begin of the array, if any. JSON::ArrIter JSON::Value::ArrBegin(){ return arrVal.begin(); } /// Returns an iterator to the end of the array, if any. JSON::ArrIter JSON::Value::ArrEnd(){ return arrVal.end(); } /// Returns the total of the objects and array size combined. unsigned int JSON::Value::size(){ return objVal.size() + arrVal.size(); } /// Completely clears the contents of this value, /// changing its type to NULL in the process. void JSON::Value::null(){ objVal.clear(); arrVal.clear(); strVal.clear(); intVal = 0; myType = EMPTY; } /// Converts a std::string to a JSON::Value. JSON::Value JSON::fromString(std::string json){ std::istringstream is(json); return JSON::Value(is); } /// Converts a file to a JSON::Value. JSON::Value JSON::fromFile(std::string filename){ std::ifstream File; File.open(filename.c_str()); JSON::Value ret(File); File.close(); return ret; } /// Parses a single DTMI type - used recursively by the JSON::fromDTMI functions. /// This function updates i every call with the new position in the data. /// \param data The raw data to parse. /// \param len The size of the raw data. /// \param i Current parsing position in the raw data (defaults to 0). /// \returns A single JSON::Value, parsed from the raw data. JSON::Value JSON::fromDTMI(const unsigned char * data, unsigned int len, unsigned int &i){ #if DEBUG >= 10 fprintf(stderr, "Note: AMF type %hhx found. %i bytes left\n", data[i], len-i); #endif switch (data[i]){ case 0x01:{//integer unsigned char tmpdbl[8]; tmpdbl[7] = data[i+1]; tmpdbl[6] = data[i+2]; tmpdbl[5] = data[i+3]; tmpdbl[4] = data[i+4]; tmpdbl[3] = data[i+5]; tmpdbl[2] = data[i+6]; tmpdbl[1] = data[i+7]; tmpdbl[0] = data[i+8]; i+=9;//skip 8(an uint64_t)+1 forwards uint64_t * d = (uint64_t*)tmpdbl; return JSON::Value((long long int)*d); } break; case 0x02:{//string unsigned int tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to UTF-8-long length std::string tmpstr = std::string((const char *)data+i+5, (size_t)tmpi);//set the string data i += tmpi + 5;//skip length+size+1 forwards return JSON::Value(tmpstr); } break; case 0xFF://also object case 0xE0:{//object ++i; JSON::Value ret; while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x0000EE) unsigned int tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length std::string tmpstr = std::string((const char *)data+i+2, (size_t)tmpi);//set the string data i += tmpi + 2;//skip length+size forwards ret[tmpstr] = fromDTMI(data, len, i);//add content, recursively parsed, updating i, setting indice to tmpstr } i += 3;//skip 0x0000EE return ret; } break; case 0x0A:{//array JSON::Value ret; ++i; while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x0000EE) ret.append(fromDTMI(data, len, i));//add content, recursively parsed, updating i } i += 3;//skip 0x0000EE return ret; } break; } #if DEBUG >= 2 fprintf(stderr, "Error: Unimplemented DTMI type %hhx - returning.\n", data[i]); #endif return JSON::Value(); }//fromOneDTMI /// Parses a std::string to a valid JSON::Value. /// This function will find one DTMI object in the string and return it. JSON::Value JSON::fromDTMI(std::string data){ unsigned int i = 0; return fromDTMI((const unsigned char*)data.c_str(), data.size(), i); }//fromDTMI