]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_rpc_json.cpp
76b5f0e2eb77cf2acf1a290d434487e5856803ed
[user/henk/code/inspircd.git] / src / modules / m_rpc_json.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include "httpd.h"
16 #include "rpc.h"
17 #include <exception>
18
19 /* $ModDesc: Encode and decode JSON-RPC requests for modules */
20 /* $ModDep: httpd.h rpc.h */
21
22 class JsonException : public std::exception
23 {
24  private:
25         std::string _what;
26  public:
27         JsonException(const std::string &swhat)
28                 : _what(swhat)
29         {
30         }
31
32         virtual ~JsonException() throw() { }
33
34         virtual const char *what() const throw()
35         {
36                 return _what.c_str();
37         }
38 };
39
40 class ModuleRpcJson : public Module
41 {
42  private:
43
44  public:
45         ModuleRpcJson(InspIRCd *Me) : Module(Me)
46         {
47                 ServerInstance->Modules->PublishInterface("RPC", this);
48                 Implementation eventlist[] = { I_OnEvent };
49                 ServerInstance->Modules->Attach(eventlist, this, 1);
50         }
51
52         virtual ~ModuleRpcJson()
53         {
54                 ServerInstance->Modules->UnpublishInterface("RPC", this);
55         }
56
57         virtual Version GetVersion()
58         {
59                 return Version("Encode and decode JSON-RPC requests for modules", VF_SERVICEPROVIDER | VF_VENDOR, API_VERSION);
60         }
61
62
63         virtual void OnEvent(Event *event)
64         {
65                 if (event->GetEventID() == "httpd_url")
66                 {
67                         HTTPRequest *req = (HTTPRequest*) event->GetData();
68
69                         if ((req->GetURI() == "/rpc/json") || (req->GetURI() == "/rpc/json/"))
70                         {
71                                 std::stringstream data;
72
73                                 RPCValue *reqobj = NULL;
74
75                                 try
76                                 {
77                                         reqobj = this->JSONParse(req->GetPostData());
78
79                                         if (!reqobj || (reqobj->GetType() != RPCObject))
80                                                 throw JsonException("RPC requests must be in the form of a single object");
81
82                                         RPCValue *method = reqobj->GetObject("method");
83                                         if (!method || method->GetType() != RPCString)
84                                                 throw JsonException("RPC requests must have a 'method' string field");
85
86                                         RPCValue *params = reqobj->GetObject("params");
87                                         if (!params || params->GetType() != RPCArray)
88                                                 throw JsonException("RPC requests must have a 'params' array field");
89
90                                         RPCRequest modreq("json", method->GetString(), params);
91                                         Event mev((char*) &modreq, this, "RPCMethod");
92                                         mev.Send(ServerInstance);
93
94                                         if (!modreq.claimed)
95                                                 throw JsonException("Unrecognized method");
96
97                                         if (!modreq.error.empty())
98                                         {
99                                                 data << "{\"result\":null,\"error\":\"" << modreq.error << "\"";
100                                         }
101                                         else
102                                         {
103                                                 data << "{\"result\":";
104                                                 this->JSONSerialize(modreq.result, data);
105                                                 data << ",\"error\":null";
106                                         }
107
108                                         if (reqobj->GetObject("id"))
109                                         {
110                                                 data << ",\"id\":";
111                                                 this->JSONSerialize(reqobj->GetObject("id"), data);
112                                         }
113                                         data << "}";
114
115                                         delete reqobj;
116                                         reqobj = NULL;
117                                 }
118                                 catch (std::exception &e)
119                                 {
120                                         if (reqobj)
121                                                 delete reqobj;
122                                         data << "{\"result\":null,\"error\":\"" << e.what() << "\"}";
123                                 }
124
125                                 HTTPDocument response(req->sock, &data, 200);
126                                 response.headers.SetHeader("X-Powered-By", "m_rpc_json.so");
127                                 response.headers.SetHeader("Content-Type", "application/json");
128                                 response.headers.SetHeader("Connection", "Keep-Alive");
129
130                                 Request rreq((char*) &response, (Module*) this, event->GetSource());
131                                 rreq.Send();
132                         }
133                 }
134         }
135
136         void AttachToParent(RPCValue *parent, RPCValue *child, const std::string &key = "")
137         {
138                 if (!parent || !child)
139                         return;
140
141                 if (parent->GetType() == RPCArray)
142                         parent->ArrayAdd(child);
143                 else if (parent->GetType() == RPCObject)
144                         parent->ObjectAdd(key, child);
145                 else
146                         throw JsonException("Cannot add a value to a non-container");
147         }
148
149         void AttachToParentReset(RPCValue *parent, RPCValue *&child, std::string &key)
150         {
151                 AttachToParent(parent, child, key);
152                 child = NULL;
153                 key.clear();
154         }
155
156         RPCValue *JSONParse(const std::string &data)
157         {
158                 bool pisobject = false;
159                 bool instring = false;
160                 std::string stmp;
161                 std::string vkey;
162                 std::string pvkey;
163                 RPCValue *aparent = NULL;
164                 RPCValue *value = NULL;
165
166                 for (std::string::const_iterator i = data.begin(); i != data.end(); i++)
167                 {
168                         if (instring)
169                         {
170                                 // TODO escape sequences
171                                 if (*i == '"')
172                                 {
173                                         instring = false;
174
175                                         if (pisobject && vkey.empty())
176                                                 vkey = stmp;
177                                         else
178                                                 value = new RPCValue(stmp);
179
180                                         stmp.clear();
181                                 }
182                                 else
183                                         stmp += *i;
184
185                                 continue;
186                         }
187
188                         if ((*i == ' ') || (*i == '\t') || (*i == '\r') || (*i == '\n'))
189                                 continue;
190
191                         if (*i == '{')
192                         {
193                                 // Begin object
194                                 if ((value) || (pisobject && vkey.empty()))
195                                         throw JsonException("Unexpected begin object token ('{')");
196
197                                 RPCValue *nobj = new RPCValue(RPCObject, aparent);
198                                 aparent = nobj;
199                                 pvkey = vkey;
200                                 vkey.clear();
201                                 pisobject = true;
202                         }
203                         else if (*i == '}')
204                         {
205                                 // End object
206                                 if ((!aparent) || (!pisobject) || (!vkey.empty() && !value))
207                                         throw JsonException("Unexpected end object token ('}')");
208
209                                 // End value
210                                 if (value)
211                                         AttachToParentReset(aparent, value, vkey);
212
213                                 if (!aparent->parent)
214                                         return aparent;
215
216                                 value = aparent;
217                                 aparent = aparent->parent;
218                                 vkey = pvkey;
219                                 pvkey.clear();
220                                 pisobject = (aparent->GetType() == RPCObject);
221                         }
222                         else if (*i == '"')
223                         {
224                                 // Begin string
225                                 if (value)
226                                         throw JsonException("Unexpected begin string token ('\"')");
227
228                                 instring = true;
229                         }
230                         else if (*i == ':')
231                         {
232                                 if ((!aparent) || (!pisobject) || (vkey.empty()) || (value))
233                                         throw JsonException("Unexpected object value token (':')");
234                         }
235                         else if (*i == ',')
236                         {
237                                 if ((!aparent) || (!value) || ((pisobject) && (vkey.empty())))
238                                         throw JsonException("Unexpected value seperator token (',')");
239
240                                 AttachToParentReset(aparent, value, vkey);
241                         }
242                         else if (*i == '[')
243                         {
244                                 // Begin array
245                                 if ((value) || (pisobject && vkey.empty()))
246                                         throw JsonException("Unexpected begin array token ('[')");
247
248                                 RPCValue *nar = new RPCValue(RPCArray, aparent);
249                                 aparent = nar;
250                                 pvkey = vkey;
251                                 vkey.clear();
252                                 pisobject = false;
253                         }
254                         else if (*i == ']')
255                         {
256                                 // End array (also an end value delimiter)
257                                 if (!aparent || pisobject)
258                                         throw JsonException("Unexpected end array token (']')");
259
260                                 if (value)
261                                         AttachToParentReset(aparent, value, vkey);
262
263                                 if (!aparent->parent)
264                                         return aparent;
265
266                                 value = aparent;
267                                 aparent = aparent->parent;
268                                 vkey = pvkey;
269                                 pvkey.clear();
270                                 pisobject = (aparent->GetType() == RPCObject);
271                         }
272                         else
273                         {
274                                 // Numbers, false, null, and true fall under this heading.
275                                 if ((*i == 't') && ((i + 3) < data.end()) && (*(i + 1) == 'r') && (*(i + 2) == 'u') && (*(i + 3) == 'e'))
276                                 {
277                                         value = new RPCValue(true);
278                                         i += 3;
279                                 }
280                                 else if ((*i == 'f') && ((i + 4) < data.end()) && (*(i + 1) == 'a') && (*(i + 2) == 'l') && (*(i + 3) == 's') && (*(i + 4) == 'e'))
281                                 {
282                                         value = new RPCValue(false);
283                                         i += 4;
284                                 }
285                                 else if ((*i == 'n') && ((i + 3) < data.end()) && (*(i + 1) == 'u') && (*(i + 2) == 'l') && (*(i + 3) == 'l'))
286                                 {
287                                         value = new RPCValue();
288                                         i += 3;
289                                 }
290                                 else if ((*i == '-') || (*i == '+') || (*i == '.') || ((*i >= '0') && (*i <= '9')))
291                                 {
292                                         std::string ds = std::string(i, data.end());
293                                         char *eds = NULL;
294
295                                         errno = 0;
296                                         double v = strtod(ds.c_str(), &eds);
297
298                                         if (errno != 0)
299                                                 throw JsonException("Error parsing numeric value");
300
301                                         value = new RPCValue(v);
302
303                                         i += eds - ds.c_str() - 1;
304                                 }
305                                 else
306                                         throw JsonException("Unknown data in value portion");
307                         }
308                 }
309
310                 if (instring)
311                         throw JsonException("Unterminated string");
312
313                 if (aparent && pisobject)
314                         throw JsonException("Unterminated object");
315                 else if (aparent && !pisobject)
316                         throw JsonException("Unterminated array");
317
318                 if (value)
319                         return value;
320                 else
321                         throw JsonException("No JSON data found");
322         }
323
324         void JSONSerialize(RPCValue *value, std::stringstream &re)
325         {
326                 int ac;
327                 switch (value->GetType())
328                 {
329                         case RPCNull:
330                                 re << "null";
331                                 break;
332                         case RPCBoolean:
333                                 re << ((value->GetBool()) ? "true" : "false");
334                                 break;
335                         case RPCInteger:
336                                 re << value->GetInt();
337                                 break;
338                         case RPCString:
339                                 re << "\"" << value->GetString() << "\"";
340                                 break;
341                         case RPCArray:
342                                 re << "[";
343                                 ac = value->ArraySize();
344                                 for (int i = 0; i < ac; i++)
345                                 {
346                                         this->JSONSerialize(value->GetArray(i), re);
347                                         if (i != (ac - 1))
348                                                 re << ",";
349                                 }
350                                 re << "]";
351                                 break;
352                         case RPCObject:
353                                 re << "{";
354                                 std::pair<RPCObjectContainer::iterator,RPCObjectContainer::iterator> its = value->GetObjectIterator();
355                                 while (its.first != its.second)
356                                 {
357                                         re << "\"" << its.first->first << "\":";
358                                         this->JSONSerialize(its.first->second, re);
359                                         if (++its.first != its.second)
360                                                 re << ",";
361                                 }
362                                 re << "}";
363                                 break;
364                 }
365         }
366 };
367
368 MODULE_INIT(ModuleRpcJson)