summaryrefslogtreecommitdiff
path: root/modules/rpc/xmlrpc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/rpc/xmlrpc.cpp')
-rw-r--r--modules/rpc/xmlrpc.cpp275
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("<", "&lt;"),
+ special_chars(">", "&qt;"),
+ special_chars("'", "&#39;"),
+ special_chars("\n", "&#xA;"),
+ 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)