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