/* +------------------------------------+ * | Inspire Internet Relay Chat Daemon | * +------------------------------------+ * * InspIRCd: (C) 2002-2009 InspIRCd Development Team * See: http://wiki.inspircd.org/Credits * * This program is free but copyrighted software; see * the file COPYING for details. * * --------------------------------------------------- */ #include "inspircd.h" #include "httpd.h" #include "rpc.h" #include /* $ModDesc: Encode and decode JSON-RPC requests for modules */ /* $ModDep: httpd.h rpc.h */ class JsonException : public std::exception { private: std::string _what; public: JsonException(const std::string &swhat) : _what(swhat) { } virtual ~JsonException() throw() { } virtual const char *what() const throw() { return _what.c_str(); } }; class ModuleRpcJson : public Module { private: public: ModuleRpcJson(InspIRCd *Me) : Module(Me) { ServerInstance->Modules->PublishInterface("RPC", this); Implementation eventlist[] = { I_OnEvent }; ServerInstance->Modules->Attach(eventlist, this, 1); } virtual ~ModuleRpcJson() { ServerInstance->Modules->UnpublishInterface("RPC", this); } virtual Version GetVersion() { return Version("Encode and decode JSON-RPC requests for modules", VF_SERVICEPROVIDER | VF_VENDOR, API_VERSION); } virtual void OnEvent(Event *event) { if (event->GetEventID() == "httpd_url") { HTTPRequest *req = (HTTPRequest*) event->GetData(); if ((req->GetURI() == "/rpc/json") || (req->GetURI() == "/rpc/json/")) { std::stringstream data; RPCValue *reqobj = NULL; try { reqobj = this->JSONParse(req->GetPostData()); if (!reqobj || (reqobj->GetType() != RPCObject)) throw JsonException("RPC requests must be in the form of a single object"); RPCValue *method = reqobj->GetObject("method"); if (!method || method->GetType() != RPCString) throw JsonException("RPC requests must have a 'method' string field"); RPCValue *params = reqobj->GetObject("params"); if (!params || params->GetType() != RPCArray) throw JsonException("RPC requests must have a 'params' array field"); RPCRequest modreq("json", method->GetString(), params); Event mev((char*) &modreq, this, "RPCMethod"); mev.Send(ServerInstance); if (!modreq.claimed) throw JsonException("Unrecognized method"); if (!modreq.error.empty()) { data << "{\"result\":null,\"error\":\"" << modreq.error << "\""; } else { data << "{\"result\":"; this->JSONSerialize(modreq.result, data); data << ",\"error\":null"; } if (reqobj->GetObject("id")) { data << ",\"id\":"; this->JSONSerialize(reqobj->GetObject("id"), data); } data << "}"; delete reqobj; reqobj = NULL; } catch (std::exception &e) { if (reqobj) delete reqobj; data << "{\"result\":null,\"error\":\"" << e.what() << "\"}"; } HTTPDocument response(req->sock, &data, 200); response.headers.SetHeader("X-Powered-By", "m_rpc_json.so"); response.headers.SetHeader("Content-Type", "application/json"); response.headers.SetHeader("Connection", "Keep-Alive"); Request rreq((char*) &response, (Module*) this, event->GetSource()); rreq.Send(); } } } void AttachToParent(RPCValue *parent, RPCValue *child, const std::string &key = "") { if (!parent || !child) return; if (parent->GetType() == RPCArray) parent->ArrayAdd(child); else if (parent->GetType() == RPCObject) parent->ObjectAdd(key, child); else throw JsonException("Cannot add a value to a non-container"); } void AttachToParentReset(RPCValue *parent, RPCValue *&child, std::string &key) { AttachToParent(parent, child, key); child = NULL; key.clear(); } RPCValue *JSONParse(const std::string &data) { bool pisobject = false; bool instring = false; std::string stmp; std::string vkey; std::string pvkey; RPCValue *aparent = NULL; RPCValue *value = NULL; for (std::string::const_iterator i = data.begin(); i != data.end(); i++) { if (instring) { // TODO escape sequences if (*i == '"') { instring = false; if (pisobject && vkey.empty()) vkey = stmp; else value = new RPCValue(stmp); stmp.clear(); } else stmp += *i; continue; } if ((*i == ' ') || (*i == '\t') || (*i == '\r') || (*i == '\n')) continue; if (*i == '{') { // Begin object if ((value) || (pisobject && vkey.empty())) throw JsonException("Unexpected begin object token ('{')"); RPCValue *nobj = new RPCValue(RPCObject, aparent); aparent = nobj; pvkey = vkey; vkey.clear(); pisobject = true; } else if (*i == '}') { // End object if ((!aparent) || (!pisobject) || (!vkey.empty() && !value)) throw JsonException("Unexpected end object token ('}')"); // End value if (value) AttachToParentReset(aparent, value, vkey); if (!aparent->parent) return aparent; value = aparent; aparent = aparent->parent; vkey = pvkey; pvkey.clear(); pisobject = (aparent->GetType() == RPCObject); } else if (*i == '"') { // Begin string if (value) throw JsonException("Unexpected begin string token ('\"')"); instring = true; } else if (*i == ':') { if ((!aparent) || (!pisobject) || (vkey.empty()) || (value)) throw JsonException("Unexpected object value token (':')"); } else if (*i == ',') { if ((!aparent) || (!value) || ((pisobject) && (vkey.empty()))) throw JsonException("Unexpected value seperator token (',')"); AttachToParentReset(aparent, value, vkey); } else if (*i == '[') { // Begin array if ((value) || (pisobject && vkey.empty())) throw JsonException("Unexpected begin array token ('[')"); RPCValue *nar = new RPCValue(RPCArray, aparent); aparent = nar; pvkey = vkey; vkey.clear(); pisobject = false; } else if (*i == ']') { // End array (also an end value delimiter) if (!aparent || pisobject) throw JsonException("Unexpected end array token (']')"); if (value) AttachToParentReset(aparent, value, vkey); if (!aparent->parent) return aparent; value = aparent; aparent = aparent->parent; vkey = pvkey; pvkey.clear(); pisobject = (aparent->GetType() == RPCObject); } else { // Numbers, false, null, and true fall under this heading. if ((*i == 't') && ((i + 3) < data.end()) && (*(i + 1) == 'r') && (*(i + 2) == 'u') && (*(i + 3) == 'e')) { value = new RPCValue(true); i += 3; } else if ((*i == 'f') && ((i + 4) < data.end()) && (*(i + 1) == 'a') && (*(i + 2) == 'l') && (*(i + 3) == 's') && (*(i + 4) == 'e')) { value = new RPCValue(false); i += 4; } else if ((*i == 'n') && ((i + 3) < data.end()) && (*(i + 1) == 'u') && (*(i + 2) == 'l') && (*(i + 3) == 'l')) { value = new RPCValue(); i += 3; } else if ((*i == '-') || (*i == '+') || (*i == '.') || ((*i >= '0') && (*i <= '9'))) { std::string ds = std::string(i, data.end()); char *eds = NULL; errno = 0; double v = strtod(ds.c_str(), &eds); if (errno != 0) throw JsonException("Error parsing numeric value"); value = new RPCValue(v); i += eds - ds.c_str() - 1; } else throw JsonException("Unknown data in value portion"); } } if (instring) throw JsonException("Unterminated string"); if (aparent && pisobject) throw JsonException("Unterminated object"); else if (aparent && !pisobject) throw JsonException("Unterminated array"); if (value) return value; else throw JsonException("No JSON data found"); } void JSONSerialize(RPCValue *value, std::stringstream &re) { int ac; switch (value->GetType()) { case RPCNull: re << "null"; break; case RPCBoolean: re << ((value->GetBool()) ? "true" : "false"); break; case RPCInteger: re << value->GetInt(); break; case RPCString: re << "\"" << value->GetString() << "\""; break; case RPCArray: re << "["; ac = value->ArraySize(); for (int i = 0; i < ac; i++) { this->JSONSerialize(value->GetArray(i), re); if (i != (ac - 1)) re << ","; } re << "]"; break; case RPCObject: re << "{"; std::pair its = value->GetObjectIterator(); while (its.first != its.second) { re << "\"" << its.first->first << "\":"; this->JSONSerialize(its.first->second, re); if (++its.first != its.second) re << ","; } re << "}"; break; } } }; MODULE_INIT(ModuleRpcJson)