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