summaryrefslogtreecommitdiff
path: root/modules/extra/rest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/extra/rest.cpp')
-rw-r--r--modules/extra/rest.cpp426
1 files changed, 426 insertions, 0 deletions
diff --git a/modules/extra/rest.cpp b/modules/extra/rest.cpp
new file mode 100644
index 000000000..187b2147f
--- /dev/null
+++ b/modules/extra/rest.cpp
@@ -0,0 +1,426 @@
+/*
+ * Anope IRC Services
+ *
+ * Copyright (C) 2016 Anope Team <team@anope.org>
+ *
+ * This file is part of Anope. Anope is free software; you can
+ * redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software
+ * Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see see <http://www.gnu.org/licenses/>.
+ */
+
+#include "module.h"
+#include "modules/httpd.h"
+
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/json_parser.hpp>
+
+class RESTService : public HTTPPage
+{
+ public:
+ RESTService(Module *creator, const Anope::string &sname) : HTTPPage("/rest", "application/json")
+ {
+ }
+
+ bool OnRequest(HTTPProvider *provider, const Anope::string &page_name, HTTPClient *client, HTTPMessage &message, HTTPReply &reply) override
+ {
+ sepstream sep(page_name, '/');
+ Anope::string token;
+
+ sep.GetToken(token); // /rest
+ sep.GetToken(token); // type
+
+ if (token == "types")
+ {
+ GetTypes(provider, client, message, reply);
+ return true;
+ }
+
+ Serialize::TypeBase *type = Serialize::TypeBase::Find(token);
+ if (type == nullptr)
+ {
+ reply.error = HTTP_PAGE_NOT_FOUND;
+ reply.Write("Unknown type");
+ return true;
+ }
+
+ if (message.method == httpd::Method::POST)
+ {
+ Post(provider, client, message, reply, type, sep);
+ }
+ else if (message.method == httpd::Method::GET)
+ {
+ Get(provider, client, message, reply, type, sep);
+ }
+ else if (message.method == httpd::Method::PUT)
+ {
+ Put(provider, client, message, reply, type, sep);
+ }
+ else if (message.method == httpd::Method::DELETE)
+ {
+ Delete(provider, client, message, reply, type, sep);
+ }
+ else
+ {
+ reply.error = HTTP_NOT_SUPPORTED;
+ reply.Write("Not Supported");
+ }
+
+ return true;
+ }
+
+ private:
+ /** Get all object type names
+ */
+ void GetTypes(HTTPProvider *provider, HTTPClient *client, HTTPMessage &message, HTTPReply &reply)
+ {
+ boost::property_tree::ptree tree, children;
+
+ for (Serialize::TypeBase *stype : Serialize::TypeBase::GetTypes())
+ {
+ boost::property_tree::ptree child;
+ child.put("", stype->GetName().str());
+ children.push_back(std::make_pair("", child));
+ }
+
+ tree.add_child("types", children);
+
+ std::stringstream ss;
+ boost::property_tree::write_json(ss, tree);
+
+ reply.Write(ss.str());
+ }
+
+ void Post(HTTPProvider *provider, HTTPClient *client, HTTPMessage &message, HTTPReply &reply, Serialize::TypeBase *type, sepstream &sep)
+ {
+ boost::property_tree::ptree tree;
+ std::stringstream ss(message.content.str());
+
+ try
+ {
+ boost::property_tree::read_json(ss, tree);
+ }
+ catch (const boost::property_tree::json_parser::json_parser_error &)
+ {
+ reply.error = HTTP_BAD_REQUEST;
+ reply.Write("Unable to parse JSON");
+ return;
+ }
+
+ for (auto &p : tree)
+ {
+ const std::string &key = p.first;
+
+ Serialize::FieldBase *field = type->GetField(key);
+ if (field == nullptr)
+ {
+ reply.error = HTTP_BAD_REQUEST;
+ reply.Write("No such field " + field->serialize_name);
+ return;
+ }
+ }
+
+ Serialize::Object *object = type->Create();
+ if (object == nullptr)
+ {
+ reply.error = HTTP_INTERNAL_SERVER_ERROR;
+ reply.Write("Unable to create new object");
+ return;
+ }
+
+ for (auto &p : tree)
+ {
+ const std::string &key = p.first;
+ const boost::property_tree::ptree &pt = p.second;
+
+ Serialize::FieldBase *field = type->GetField(key);
+ if (field == nullptr)
+ {
+ reply.error = HTTP_INTERNAL_SERVER_ERROR;
+ reply.Write("Missing field");
+ return;
+ }
+
+ field->UnserializeFromString(object, pt.get_value<std::string>());
+ }
+
+ reply.error = HTTP_CREATED;
+ reply.Write(stringify(object->id));
+ }
+
+ void Get(HTTPProvider *provider, HTTPClient *client, HTTPMessage &message, HTTPReply &reply, Serialize::TypeBase *type, sepstream &sep)
+ {
+ Anope::string token;
+ sep.GetToken(token);
+
+ if (token.empty())
+ {
+ GetList(reply, type);
+ return;
+ }
+
+ if (token.is_pos_number_only())
+ {
+ GetID(message, reply, type, token);
+ return;
+ }
+
+ GetField(reply, type, token, sep);
+ }
+
+ boost::property_tree::ptree ToJson(Serialize::Object *object)
+ {
+ boost::property_tree::ptree tree;
+
+ for (Serialize::FieldBase *field : object->GetSerializableType()->GetFields())
+ {
+ if (field->HasFieldS(object)) // for ext
+ tree.put(field->serialize_name.str(), field->SerializeToString(object).str());
+ }
+
+ tree.put("id", object->id);
+ return tree;
+ }
+
+ /** Get a list of all objects of a type
+ */
+ void GetList(HTTPReply &reply, Serialize::TypeBase *type)
+ {
+ boost::property_tree::ptree tree, children;
+
+ for (Serialize::Object *object : Serialize::GetObjects_<Serialize::Object *>(type))
+ {
+ boost::property_tree::ptree child = ToJson(object);;
+ children.push_back(std::make_pair("", child));
+ }
+
+ tree.add_child(type->GetName().str(), children);
+
+ std::stringstream ss;
+ boost::property_tree::write_json(ss, tree);
+
+ reply.Write(ss.str());
+ }
+
+ /** Get object by id
+ */
+ void GetID(HTTPMessage &message, HTTPReply &reply, Serialize::TypeBase *type, const Anope::string &token)
+ {
+ Serialize::ID id;
+ try
+ {
+ id = convertTo<Serialize::ID>(token);
+ }
+ catch (const ConvertException &ex)
+ {
+ reply.error = HTTP_BAD_REQUEST;
+ reply.Write("Invalid id");
+ return;
+ }
+
+ if (message.get_data.count("refs") > 0)
+ {
+ Serialize::TypeBase *ref_type = Serialize::TypeBase::Find(message.get_data["refs"]);
+
+ if (ref_type == nullptr)
+ {
+ reply.error = HTTP_PAGE_NOT_FOUND;
+ reply.Write("Unknown reference type");
+ return;
+ }
+
+ GetRefs(reply, type, id, ref_type);
+ return;
+ }
+
+ GetID(reply, type, id);
+ }
+
+ void GetID(HTTPReply &reply, Serialize::TypeBase *type, Serialize::ID id)
+ {
+ Serialize::Object *object = type->Require(id);
+ if (object == nullptr)
+ {
+ /* This has to be a cached object with type/id mismatch */
+ reply.error = HTTP_PAGE_NOT_FOUND;
+ reply.Write("Type/id mismatch");
+ return;
+ }
+
+ boost::property_tree::ptree tree = ToJson(object);
+
+ std::stringstream ss;
+ boost::property_tree::write_json(ss, tree);
+
+ reply.Write(ss.str());
+ }
+
+ /** Get object by field and value
+ */
+ void GetField(HTTPReply &reply, Serialize::TypeBase *type, const Anope::string &token, sepstream &sep)
+ {
+ Serialize::FieldBase *field = type->GetField(token);
+ if (field == nullptr)
+ {
+ reply.error = HTTP_PAGE_NOT_FOUND;
+ reply.Write("No such field");
+ return;
+ }
+
+ Anope::string value;
+ sep.GetToken(value);
+
+ Serialize::ID id;
+ EventReturn result = EventManager::Get()->Dispatch(&Event::SerializeEvents::OnSerializeFind, type, field, value, id);
+ if (result != EVENT_ALLOW)
+ {
+ reply.error = HTTP_PAGE_NOT_FOUND;
+ reply.Write("Not found");
+ return;
+ }
+
+ GetID(reply, type, id);
+ }
+
+ /** Find refs of type ref_type to object id of type type
+ */
+ void GetRefs(HTTPReply &reply, Serialize::TypeBase *type, Serialize::ID id, Serialize::TypeBase *ref_type)
+ {
+ Serialize::Object *object = type->Require(id);
+ if (object == nullptr)
+ {
+ reply.error = HTTP_PAGE_NOT_FOUND;
+ reply.Write("Type/id mismatch");
+ return;
+ }
+
+ boost::property_tree::ptree tree, children;
+
+ for (Serialize::Object *ref : object->GetRefs(ref_type))
+ {
+ boost::property_tree::ptree child = ToJson(ref);
+ children.push_back(std::make_pair("", child));
+ }
+
+ tree.add_child(type->GetName().str(), children);
+
+ std::stringstream ss;
+ boost::property_tree::write_json(ss, tree);
+
+ reply.Write(ss.str());
+ }
+
+ void Put(HTTPProvider *provider, HTTPClient *client, HTTPMessage &message, HTTPReply &reply, Serialize::TypeBase *type, sepstream &sep)
+ {
+ Anope::string token;
+ Anope::string fieldname;
+
+ if (!sep.GetToken(token) || !sep.GetToken(fieldname))
+ {
+ reply.error = HTTP_BAD_REQUEST;
+ reply.Write("No id and/or field");
+ return;
+ }
+
+ Serialize::ID id;
+ try
+ {
+ id = convertTo<Serialize::ID>(token);
+ }
+ catch (const ConvertException &ex)
+ {
+ reply.error = HTTP_BAD_REQUEST;
+ reply.Write("Invalid id");
+ return;
+ }
+
+ Serialize::Object *object = type->Require(id);
+ if (object == nullptr)
+ {
+ reply.error = HTTP_PAGE_NOT_FOUND;
+ reply.Write("Type/id mismatch");
+ return;
+ }
+
+ Serialize::FieldBase *field = type->GetField(fieldname);
+ if (field == nullptr)
+ {
+ reply.error = HTTP_BAD_REQUEST;
+ reply.Write("No such field");
+ return;
+ }
+
+ field->UnserializeFromString(object, message.content);
+ }
+
+ void Delete(HTTPProvider *provider, HTTPClient *client, HTTPMessage &message, HTTPReply &reply, Serialize::TypeBase *type, sepstream &sep)
+ {
+ Anope::string token;
+ sep.GetToken(token);
+
+ Serialize::ID id;
+ try
+ {
+ id = convertTo<Serialize::ID>(token);
+ }
+ catch (const ConvertException &ex)
+ {
+ reply.error = HTTP_BAD_REQUEST;
+ reply.Write("Invalid id");
+ return;
+ }
+
+ Serialize::Object *object = type->Require(id);
+ if (object == nullptr)
+ {
+ reply.error = HTTP_PAGE_NOT_FOUND;
+ reply.Write("Type/id mismatch");
+ return;
+ }
+
+ object->Delete();
+ }
+};
+
+class ModuleREST : public Module
+{
+ ServiceReference<HTTPProvider> httpref;
+
+ public:
+ RESTService restinterface;
+
+ ModuleREST(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR)
+ , restinterface(this, "rest")
+ {
+
+ }
+
+ ~ModuleREST()
+ {
+ if (httpref)
+ httpref->UnregisterPage(&restinterface);
+ }
+
+ void OnReload(Configuration::Conf *conf) override
+ {
+ if (httpref)
+ httpref->UnregisterPage(&restinterface);
+
+ this->httpref = ServiceReference<HTTPProvider>(conf->GetModule(this)->Get<Anope::string>("server", "httpd/main"));
+
+ if (!httpref)
+ throw ConfigException("Unable to find http reference, is m_httpd loaded?");
+
+ httpref->RegisterPage(&restinterface);
+ }
+};
+
+MODULE_INIT(ModuleREST)