diff options
Diffstat (limited to 'modules/rpc/xmlrpc.cpp')
-rw-r--r-- | modules/rpc/xmlrpc.cpp | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/modules/rpc/xmlrpc.cpp b/modules/rpc/xmlrpc.cpp new file mode 100644 index 000000000..2ecf00410 --- /dev/null +++ b/modules/rpc/xmlrpc.cpp @@ -0,0 +1,275 @@ +/* + * + * (C) 2010-2025 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + +#include "module.h" +#include "modules/rpc.h" +#include "modules/httpd.h" + +static struct special_chars final +{ + Anope::string character; + Anope::string replace; + + special_chars(const Anope::string &c, const Anope::string &r) : character(c), replace(r) { } +} +special[] = { + special_chars("&", "&"), + special_chars("\"", """), + special_chars("<", "<"), + special_chars(">", "&qt;"), + special_chars("'", "'"), + special_chars("\n", "
"), + special_chars("\002", ""), // bold + special_chars("\003", ""), // color + special_chars("\035", ""), // italics + special_chars("\037", ""), // underline + special_chars("\026", ""), // reverses + special_chars("", "") +}; + +class MyXMLRPCServiceInterface final + : public RPCServiceInterface + , public HTTPPage +{ + std::deque<RPCEvent *> events; + +public: + MyXMLRPCServiceInterface(Module *creator, const Anope::string &sname) : RPCServiceInterface(creator, sname), HTTPPage("/xmlrpc", "text/xml") { } + + void Register(RPCEvent *event) override + { + this->events.push_back(event); + } + + void Unregister(RPCEvent *event) override + { + std::deque<RPCEvent *>::iterator it = std::find(this->events.begin(), this->events.end(), event); + + if (it != this->events.end()) + this->events.erase(it); + } + + static Anope::string Sanitize(const Anope::string &string) + { + Anope::string ret = string; + for (int i = 0; !special[i].character.empty(); ++i) + ret = ret.replace_all_cs(special[i].character, special[i].replace); + return ret; + } + + static Anope::string Unescape(const Anope::string &string) + { + Anope::string ret = string; + for (int i = 0; !special[i].character.empty(); ++i) + if (!special[i].replace.empty()) + ret = ret.replace_all_cs(special[i].replace, special[i].character); + + for (size_t i, last = 0; (i = string.find("&#", last)) != Anope::string::npos;) + { + last = i + 1; + + size_t end = string.find(';', i); + if (end == Anope::string::npos) + break; + + Anope::string ch = string.substr(i + 2, end - (i + 2)); + + if (ch.empty()) + continue; + + long l; + if (!ch.empty() && ch[0] == 'x') + l = strtol(ch.substr(1).c_str(), NULL, 16); + else + l = strtol(ch.c_str(), NULL, 10); + + if (l > 0 && l < 256) + ret = ret.replace_all_cs("&#" + ch + ";", Anope::string(l)); + } + + return ret; + } + +private: + static bool GetData(Anope::string &content, Anope::string &tag, Anope::string &data) + { + if (content.empty()) + return false; + + Anope::string prev, cur; + bool istag; + + do + { + prev = cur; + cur.clear(); + + size_t len = 0; + istag = false; + + if (content[0] == '<') + { + len = content.find_first_of('>'); + istag = true; + } + else if (content[0] != '>') + { + len = content.find_first_of('<'); + } + + // len must advance + if (len == Anope::string::npos || len == 0) + break; + + if (istag) + { + cur = content.substr(1, len - 1); + content.erase(0, len + 1); + while (!content.empty() && content[0] == ' ') + content.erase(content.begin()); + } + else + { + cur = content.substr(0, len); + content.erase(0, len); + } + } + while (istag && !content.empty()); + + tag = Unescape(prev); + data = Unescape(cur); + return !istag && !data.empty(); + } + +public: + bool OnRequest(HTTPProvider *provider, const Anope::string &page_name, HTTPClient *client, HTTPMessage &message, HTTPReply &reply) override + { + Anope::string content = message.content, tname, data; + RPCRequest request(reply); + + while (GetData(content, tname, data)) + { + Log(LOG_DEBUG) << "xmlrpc: Tag name: " << tname << ", data: " << data; + if (tname == "methodName") + request.name = data; + else if (tname == "name" && data == "id") + { + GetData(content, tname, data); + request.id = data; + } + else if (tname == "string") + request.data.push_back(data); + } + + for (auto *e : this->events) + { + if (!e->Run(this, client, request)) + return false; + else if (!request.GetReplies().empty()) + { + this->Reply(request); + return true; + } + } + + reply.error = HTTP_PAGE_NOT_FOUND; + reply.Write("Unrecognized query"); + return true; + } + + void Reply(RPCRequest &request) override + { + if (!request.id.empty()) + request.Reply("id", request.id); + + Anope::string xml = + "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" + "<methodResponse>\n"; + + if (request.GetError()) + { + xml += + "<fault>\n" + " <value>\n" + " <struct>\n" + " <member>\n" + " <name>faultCode</name>\n" + " <value>\n" + " <int>" + Anope::ToString(request.GetError()->first) + "</int>\n" + " </value>\n" + " </member>\n" + " <member>\n" + " <name>faultString</name>\n" + " <value>\n" + " <string>" + this->Sanitize(request.GetError()->second) + "</string>\n" + " </value>\n" + " </member>\n" + " </struct>\n" + " </value>\n" + "</fault>\n"; + } + else + { + xml += + "<params>\n" + " <param>\n" + " <value>\n" + " <struct>\n"; + for (const auto &[name, value] : request.GetReplies()) + { + xml += + "<member>\n" + " <name>" + this->Sanitize(name) + "</name>\n" + " <value>\n" + " <string>" + this->Sanitize(value) + "</string>\n" + " </value>\n" + "</member>\n"; + } + xml += + " </struct>\n" + " </value>\n" + " </param>\n" + "</params>\n"; + } + xml += "</methodResponse>"; + + request.reply.Write(xml); + } +}; + +class ModuleXMLRPC final + : public Module +{ + ServiceReference<HTTPProvider> httpref; +public: + MyXMLRPCServiceInterface xmlrpcinterface; + + ModuleXMLRPC(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), + xmlrpcinterface(this, "rpc") + { + + } + + ~ModuleXMLRPC() override + { + if (httpref) + httpref->UnregisterPage(&xmlrpcinterface); + } + + void OnReload(Configuration::Conf *conf) override + { + if (httpref) + httpref->UnregisterPage(&xmlrpcinterface); + this->httpref = ServiceReference<HTTPProvider>("HTTPProvider", conf->GetModule(this)->Get<const Anope::string>("server", "httpd/main")); + if (!httpref) + throw ConfigException("Unable to find http reference, is httpd loaded?"); + httpref->RegisterPage(&xmlrpcinterface); + } +}; + +MODULE_INIT(ModuleXMLRPC) |