1 /* +------------------------------------+
2 * | Inspire Internet Relay Chat Daemon |
3 * +------------------------------------+
5 * InspIRCd: (C) 2002-2007 InspIRCd Development Team
6 * See: http://www.inspircd.org/wiki/index.php/Credits
8 * This program is free but copyrighted software; see
9 * the file COPYING for details.
11 * ---------------------------------------------------
22 /* $ModDesc: Encode and decode JSON-RPC requests for modules */
23 /* $ModDep: httpd.h rpc.h */
25 class JsonException : public std::exception
30 JsonException(const std::string &what)
35 virtual ~JsonException() throw() { }
37 virtual const char *what() const throw()
43 class ModuleRpcJson : public Module
48 ModuleRpcJson(InspIRCd *Me) : Module(Me)
50 ServerInstance->Modules->PublishInterface("RPC", this);
51 Implementation eventlist[] = { I_OnEvent };
52 ServerInstance->Modules->Attach(eventlist, this, 1);
55 virtual ~ModuleRpcJson()
57 ServerInstance->Modules->UnpublishInterface("RPC", this);
60 virtual Version GetVersion()
62 return Version(1, 1, 0, 0, VF_SERVICEPROVIDER | VF_VENDOR, API_VERSION);
65 void Implements(char *List)
70 virtual void OnEvent(Event *event)
72 if (event->GetEventID() == "httpd_url")
74 HTTPRequest *req = (HTTPRequest*) event->GetData();
76 if ((req->GetURI() == "/rpc/json") || (req->GetURI() == "/rpc/json/"))
78 std::stringstream data;
80 RPCValue *reqobj = NULL;
84 reqobj = this->JSONParse(req->GetPostData());
86 if (!reqobj || (reqobj->GetType() != RPCObject))
87 throw JsonException("RPC requests must be in the form of a single object");
89 RPCValue *method = reqobj->GetObject("method");
90 if (!method || method->GetType() != RPCString)
91 throw JsonException("RPC requests must have a 'method' string field");
93 RPCValue *params = reqobj->GetObject("params");
94 if (!params || params->GetType() != RPCArray)
95 throw JsonException("RPC requests must have a 'params' array field");
97 RPCRequest modreq("json", method->GetString(), params);
98 Event mev((char*) &modreq, this, "RPCMethod");
99 mev.Send(ServerInstance);
102 throw JsonException("Unrecognized method");
104 if (!modreq.error.empty())
106 data << "{\"result\":null,\"error\":\"" << modreq.error << "\"";
110 data << "{\"result\":";
111 this->JSONSerialize(modreq.result, data);
112 data << ",\"error\":null";
115 if (reqobj->GetObject("id"))
118 this->JSONSerialize(reqobj->GetObject("id"), data);
125 catch (std::exception &e)
129 data << "{\"result\":null,\"error\":\"" << e.what() << "\"}";
132 HTTPDocument response(req->sock, &data, 200);
133 response.headers.SetHeader("X-Powered-By", "m_rpc_json.so");
134 response.headers.SetHeader("Content-Type", "application/json");
135 response.headers.SetHeader("Connection", "Keep-Alive");
137 Request rreq((char*) &response, (Module*) this, event->GetSource());
143 void AttachToParent(RPCValue *parent, RPCValue *child, const std::string &key = "")
145 if (!parent || !child)
148 if (parent->GetType() == RPCArray)
149 parent->ArrayAdd(child);
150 else if (parent->GetType() == RPCObject)
151 parent->ObjectAdd(key, child);
153 throw JsonException("Cannot add a value to a non-container");
156 void AttachToParentReset(RPCValue *parent, RPCValue *&child, std::string &key)
158 AttachToParent(parent, child, key);
163 RPCValue *JSONParse(const std::string &data)
165 bool pisobject = false;
166 bool instring = false;
170 RPCValue *aparent = NULL;
171 RPCValue *value = NULL;
173 for (std::string::const_iterator i = data.begin(); i != data.end(); i++)
177 // TODO escape sequences
182 if (pisobject && vkey.empty())
185 value = new RPCValue(stmp);
195 if ((*i == ' ') || (*i == '\t') || (*i == '\r') || (*i == '\n'))
201 if ((value) || (pisobject && vkey.empty()))
202 throw JsonException("Unexpected begin object token ('{')");
204 RPCValue *nobj = new RPCValue(RPCObject, aparent);
213 if ((!aparent) || (!pisobject) || (!vkey.empty() && !value))
214 throw JsonException("Unexpected end object token ('}')");
218 AttachToParentReset(aparent, value, vkey);
220 if (!aparent->parent)
224 aparent = aparent->parent;
227 pisobject = (aparent->GetType() == RPCObject);
233 throw JsonException("Unexpected begin string token ('\"')");
239 if ((!aparent) || (!pisobject) || (vkey.empty()) || (value))
240 throw JsonException("Unexpected object value token (':')");
244 if ((!aparent) || (!value) || ((pisobject) && (vkey.empty())))
245 throw JsonException("Unexpected value seperator token (',')");
247 AttachToParentReset(aparent, value, vkey);
252 if ((value) || (pisobject && vkey.empty()))
253 throw JsonException("Unexpected begin array token ('[')");
255 RPCValue *nar = new RPCValue(RPCArray, aparent);
263 // End array (also an end value delimiter)
264 if (!aparent || pisobject)
265 throw JsonException("Unexpected end array token (']')");
268 AttachToParentReset(aparent, value, vkey);
270 if (!aparent->parent)
274 aparent = aparent->parent;
277 pisobject = (aparent->GetType() == RPCObject);
281 // Numbers, false, null, and true fall under this heading.
282 if ((*i == 't') && ((i + 3) < data.end()) && (*(i + 1) == 'r') && (*(i + 2) == 'u') && (*(i + 3) == 'e'))
284 value = new RPCValue(true);
287 else if ((*i == 'f') && ((i + 4) < data.end()) && (*(i + 1) == 'a') && (*(i + 2) == 'l') && (*(i + 3) == 's') && (*(i + 4) == 'e'))
289 value = new RPCValue(false);
292 else if ((*i == 'n') && ((i + 3) < data.end()) && (*(i + 1) == 'u') && (*(i + 2) == 'l') && (*(i + 3) == 'l'))
294 value = new RPCValue();
297 else if ((*i == '-') || (*i == '+') || (*i == '.') || ((*i >= '0') && (*i <= '9')))
299 std::string ds = std::string(i, data.end());
303 double v = strtod(ds.c_str(), &eds);
306 throw JsonException("Error parsing numeric value");
308 value = new RPCValue(v);
310 i += eds - ds.c_str() - 1;
313 throw JsonException("Unknown data in value portion");
318 throw JsonException("Unterminated string");
320 if (aparent && pisobject)
321 throw JsonException("Unterminated object");
322 else if (aparent && !pisobject)
323 throw JsonException("Unterminated array");
328 throw JsonException("No JSON data found");
331 void JSONSerialize(RPCValue *value, std::stringstream &re)
334 switch (value->GetType())
340 re << ((value->GetBool()) ? "true" : "false");
343 re << value->GetInt();
346 re << "\"" << value->GetString() << "\"";
350 ac = value->ArraySize();
351 for (int i = 0; i < ac; i++)
353 this->JSONSerialize(value->GetArray(i), re);
361 std::pair<RPCObjectContainer::iterator,RPCObjectContainer::iterator> its = value->GetObjectIterator();
362 while (its.first != its.second)
364 re << "\"" << its.first->first << "\":";
365 this->JSONSerialize(its.first->second, re);
366 if (++its.first != its.second)
375 MODULE_INIT(ModuleRpcJson)