The InspIRCd Project
Home | Developers | Wiki | Forums | Bug Tracker | SVN | Download
Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members

m_rpc_json.cpp

Go to the documentation of this file.
00001 /*       +------------------------------------+
00002  *       | Inspire Internet Relay Chat Daemon |
00003  *       +------------------------------------+
00004  *
00005  *  InspIRCd: (C) 2002-2008 InspIRCd Development Team
00006  * See: http://www.inspircd.org/wiki/index.php/Credits
00007  *
00008  * This program is free but copyrighted software; see
00009  *            the file COPYING for details.
00010  *
00011  * ---------------------------------------------------
00012  */
00013 
00014 #include "inspircd.h"
00015 #include "httpd.h"
00016 #include "rpc.h"
00017 #include <exception>
00018 
00019 /* $ModDesc: Encode and decode JSON-RPC requests for modules */
00020 /* $ModDep: httpd.h rpc.h */
00021 
00022 class JsonException : public std::exception
00023 {
00024  private:
00025         std::string _what;
00026  public:
00027         JsonException(const std::string &swhat)
00028                 : _what(swhat)
00029         {
00030         }
00031 
00032         virtual ~JsonException() throw() { }
00033 
00034         virtual const char *what() const throw()
00035         {
00036                 return _what.c_str();
00037         }
00038 };
00039 
00040 class ModuleRpcJson : public Module
00041 {
00042  private:
00043 
00044  public:
00045         ModuleRpcJson(InspIRCd *Me) : Module(Me)
00046         {
00047                 ServerInstance->Modules->PublishInterface("RPC", this);
00048                 Implementation eventlist[] = { I_OnEvent };
00049                 ServerInstance->Modules->Attach(eventlist, this, 1);
00050         }
00051 
00052         virtual ~ModuleRpcJson()
00053         {
00054                 ServerInstance->Modules->UnpublishInterface("RPC", this);
00055         }
00056 
00057         virtual Version GetVersion()
00058         {
00059                 return Version("$Id: m_rpc_json.cpp 10622 2008-10-04 21:27:52Z brain $", VF_SERVICEPROVIDER | VF_VENDOR, API_VERSION);
00060         }
00061 
00062 
00063         virtual void OnEvent(Event *event)
00064         {
00065                 if (event->GetEventID() == "httpd_url")
00066                 {
00067                         HTTPRequest *req = (HTTPRequest*) event->GetData();
00068 
00069                         if ((req->GetURI() == "/rpc/json") || (req->GetURI() == "/rpc/json/"))
00070                         {
00071                                 std::stringstream data;
00072 
00073                                 RPCValue *reqobj = NULL;
00074 
00075                                 try
00076                                 {
00077                                         reqobj = this->JSONParse(req->GetPostData());
00078 
00079                                         if (!reqobj || (reqobj->GetType() != RPCObject))
00080                                                 throw JsonException("RPC requests must be in the form of a single object");
00081 
00082                                         RPCValue *method = reqobj->GetObject("method");
00083                                         if (!method || method->GetType() != RPCString)
00084                                                 throw JsonException("RPC requests must have a 'method' string field");
00085 
00086                                         RPCValue *params = reqobj->GetObject("params");
00087                                         if (!params || params->GetType() != RPCArray)
00088                                                 throw JsonException("RPC requests must have a 'params' array field");
00089 
00090                                         RPCRequest modreq("json", method->GetString(), params);
00091                                         Event mev((char*) &modreq, this, "RPCMethod");
00092                                         mev.Send(ServerInstance);
00093 
00094                                         if (!modreq.claimed)
00095                                                 throw JsonException("Unrecognized method");
00096 
00097                                         if (!modreq.error.empty())
00098                                         {
00099                                                 data << "{\"result\":null,\"error\":\"" << modreq.error << "\"";
00100                                         }
00101                                         else
00102                                         {
00103                                                 data << "{\"result\":";
00104                                                 this->JSONSerialize(modreq.result, data);
00105                                                 data << ",\"error\":null";
00106                                         }
00107 
00108                                         if (reqobj->GetObject("id"))
00109                                         {
00110                                                 data << ",\"id\":";
00111                                                 this->JSONSerialize(reqobj->GetObject("id"), data);
00112                                         }
00113                                         data << "}";
00114 
00115                                         delete reqobj;
00116                                         reqobj = NULL;
00117                                 }
00118                                 catch (std::exception &e)
00119                                 {
00120                                         if (reqobj)
00121                                                 delete reqobj;
00122                                         data << "{\"result\":null,\"error\":\"" << e.what() << "\"}";
00123                                 }
00124 
00125                                 HTTPDocument response(req->sock, &data, 200);
00126                                 response.headers.SetHeader("X-Powered-By", "m_rpc_json.so");
00127                                 response.headers.SetHeader("Content-Type", "application/json");
00128                                 response.headers.SetHeader("Connection", "Keep-Alive");
00129 
00130                                 Request rreq((char*) &response, (Module*) this, event->GetSource());
00131                                 rreq.Send();
00132                         }
00133                 }
00134         }
00135 
00136         void AttachToParent(RPCValue *parent, RPCValue *child, const std::string &key = "")
00137         {
00138                 if (!parent || !child)
00139                         return;
00140 
00141                 if (parent->GetType() == RPCArray)
00142                         parent->ArrayAdd(child);
00143                 else if (parent->GetType() == RPCObject)
00144                         parent->ObjectAdd(key, child);
00145                 else
00146                         throw JsonException("Cannot add a value to a non-container");
00147         }
00148 
00149         void AttachToParentReset(RPCValue *parent, RPCValue *&child, std::string &key)
00150         {
00151                 AttachToParent(parent, child, key);
00152                 child = NULL;
00153                 key.clear();
00154         }
00155 
00156         RPCValue *JSONParse(const std::string &data)
00157         {
00158                 bool pisobject = false;
00159                 bool instring = false;
00160                 std::string stmp;
00161                 std::string vkey;
00162                 std::string pvkey;
00163                 RPCValue *aparent = NULL;
00164                 RPCValue *value = NULL;
00165 
00166                 for (std::string::const_iterator i = data.begin(); i != data.end(); i++)
00167                 {
00168                         if (instring)
00169                         {
00170                                 // TODO escape sequences
00171                                 if (*i == '"')
00172                                 {
00173                                         instring = false;
00174 
00175                                         if (pisobject && vkey.empty())
00176                                                 vkey = stmp;
00177                                         else
00178                                                 value = new RPCValue(stmp);
00179 
00180                                         stmp.clear();
00181                                 }
00182                                 else
00183                                         stmp += *i;
00184 
00185                                 continue;
00186                         }
00187 
00188                         if ((*i == ' ') || (*i == '\t') || (*i == '\r') || (*i == '\n'))
00189                                 continue;
00190 
00191                         if (*i == '{')
00192                         {
00193                                 // Begin object
00194                                 if ((value) || (pisobject && vkey.empty()))
00195                                         throw JsonException("Unexpected begin object token ('{')");
00196 
00197                                 RPCValue *nobj = new RPCValue(RPCObject, aparent);
00198                                 aparent = nobj;
00199                                 pvkey = vkey;
00200                                 vkey.clear();
00201                                 pisobject = true;
00202                         }
00203                         else if (*i == '}')
00204                         {
00205                                 // End object
00206                                 if ((!aparent) || (!pisobject) || (!vkey.empty() && !value))
00207                                         throw JsonException("Unexpected end object token ('}')");
00208 
00209                                 // End value
00210                                 if (value)
00211                                         AttachToParentReset(aparent, value, vkey);
00212 
00213                                 if (!aparent->parent)
00214                                         return aparent;
00215 
00216                                 value = aparent;
00217                                 aparent = aparent->parent;
00218                                 vkey = pvkey;
00219                                 pvkey.clear();
00220                                 pisobject = (aparent->GetType() == RPCObject);
00221                         }
00222                         else if (*i == '"')
00223                         {
00224                                 // Begin string
00225                                 if (value)
00226                                         throw JsonException("Unexpected begin string token ('\"')");
00227 
00228                                 instring = true;
00229                         }
00230                         else if (*i == ':')
00231                         {
00232                                 if ((!aparent) || (!pisobject) || (vkey.empty()) || (value))
00233                                         throw JsonException("Unexpected object value token (':')");
00234                         }
00235                         else if (*i == ',')
00236                         {
00237                                 if ((!aparent) || (!value) || ((pisobject) && (vkey.empty())))
00238                                         throw JsonException("Unexpected value seperator token (',')");
00239 
00240                                 AttachToParentReset(aparent, value, vkey);
00241                         }
00242                         else if (*i == '[')
00243                         {
00244                                 // Begin array
00245                                 if ((value) || (pisobject && vkey.empty()))
00246                                         throw JsonException("Unexpected begin array token ('[')");
00247 
00248                                 RPCValue *nar = new RPCValue(RPCArray, aparent);
00249                                 aparent = nar;
00250                                 pvkey = vkey;
00251                                 vkey.clear();
00252                                 pisobject = false;
00253                         }
00254                         else if (*i == ']')
00255                         {
00256                                 // End array (also an end value delimiter)
00257                                 if (!aparent || pisobject)
00258                                         throw JsonException("Unexpected end array token (']')");
00259 
00260                                 if (value)
00261                                         AttachToParentReset(aparent, value, vkey);
00262 
00263                                 if (!aparent->parent)
00264                                         return aparent;
00265 
00266                                 value = aparent;
00267                                 aparent = aparent->parent;
00268                                 vkey = pvkey;
00269                                 pvkey.clear();
00270                                 pisobject = (aparent->GetType() == RPCObject);
00271                         }
00272                         else
00273                         {
00274                                 // Numbers, false, null, and true fall under this heading.
00275                                 if ((*i == 't') && ((i + 3) < data.end()) && (*(i + 1) == 'r') && (*(i + 2) == 'u') && (*(i + 3) == 'e'))
00276                                 {
00277                                         value = new RPCValue(true);
00278                                         i += 3;
00279                                 }
00280                                 else if ((*i == 'f') && ((i + 4) < data.end()) && (*(i + 1) == 'a') && (*(i + 2) == 'l') && (*(i + 3) == 's') && (*(i + 4) == 'e'))
00281                                 {
00282                                         value = new RPCValue(false);
00283                                         i += 4;
00284                                 }
00285                                 else if ((*i == 'n') && ((i + 3) < data.end()) && (*(i + 1) == 'u') && (*(i + 2) == 'l') && (*(i + 3) == 'l'))
00286                                 {
00287                                         value = new RPCValue();
00288                                         i += 3;
00289                                 }
00290                                 else if ((*i == '-') || (*i == '+') || (*i == '.') || ((*i >= '0') && (*i <= '9')))
00291                                 {
00292                                         std::string ds = std::string(i, data.end());
00293                                         char *eds = NULL;
00294 
00295                                         errno = 0;
00296                                         double v = strtod(ds.c_str(), &eds);
00297 
00298                                         if (errno != 0)
00299                                                 throw JsonException("Error parsing numeric value");
00300 
00301                                         value = new RPCValue(v);
00302 
00303                                         i += eds - ds.c_str() - 1;
00304                                 }
00305                                 else
00306                                         throw JsonException("Unknown data in value portion");
00307                         }
00308                 }
00309 
00310                 if (instring)
00311                         throw JsonException("Unterminated string");
00312 
00313                 if (aparent && pisobject)
00314                         throw JsonException("Unterminated object");
00315                 else if (aparent && !pisobject)
00316                         throw JsonException("Unterminated array");
00317 
00318                 if (value)
00319                         return value;
00320                 else
00321                         throw JsonException("No JSON data found");
00322         }
00323 
00324         void JSONSerialize(RPCValue *value, std::stringstream &re)
00325         {
00326                 int ac;
00327                 switch (value->GetType())
00328                 {
00329                         case RPCNull:
00330                                 re << "null";
00331                                 break;
00332                         case RPCBoolean:
00333                                 re << ((value->GetBool()) ? "true" : "false");
00334                                 break;
00335                         case RPCInteger:
00336                                 re << value->GetInt();
00337                                 break;
00338                         case RPCString:
00339                                 re << "\"" << value->GetString() << "\"";
00340                                 break;
00341                         case RPCArray:
00342                                 re << "[";
00343                                 ac = value->ArraySize();
00344                                 for (int i = 0; i < ac; i++)
00345                                 {
00346                                         this->JSONSerialize(value->GetArray(i), re);
00347                                         if (i != (ac - 1))
00348                                                 re << ",";
00349                                 }
00350                                 re << "]";
00351                                 break;
00352                         case RPCObject:
00353                                 re << "{";
00354                                 std::pair<RPCObjectContainer::iterator,RPCObjectContainer::iterator> its = value->GetObjectIterator();
00355                                 while (its.first != its.second)
00356                                 {
00357                                         re << "\"" << its.first->first << "\":";
00358                                         this->JSONSerialize(its.first->second, re);
00359                                         if (++its.first != its.second)
00360                                                 re << ",";
00361                                 }
00362                                 re << "}";
00363                                 break;
00364                 }
00365         }
00366 };
00367 
00368 MODULE_INIT(ModuleRpcJson)