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