diff options
Diffstat (limited to 'louloulibs/xmpp')
-rw-r--r-- | louloulibs/xmpp/adhoc_command.cpp | 80 | ||||
-rw-r--r-- | louloulibs/xmpp/adhoc_command.hpp | 44 | ||||
-rw-r--r-- | louloulibs/xmpp/adhoc_commands_handler.cpp | 111 | ||||
-rw-r--r-- | louloulibs/xmpp/adhoc_commands_handler.hpp | 71 | ||||
-rw-r--r-- | louloulibs/xmpp/adhoc_session.cpp | 35 | ||||
-rw-r--r-- | louloulibs/xmpp/adhoc_session.hpp | 88 | ||||
-rw-r--r-- | louloulibs/xmpp/auth.cpp | 8 | ||||
-rw-r--r-- | louloulibs/xmpp/auth.hpp | 6 | ||||
-rw-r--r-- | louloulibs/xmpp/body.hpp | 12 | ||||
-rw-r--r-- | louloulibs/xmpp/jid.cpp | 153 | ||||
-rw-r--r-- | louloulibs/xmpp/jid.hpp | 49 | ||||
-rw-r--r-- | louloulibs/xmpp/xmpp_component.cpp | 672 | ||||
-rw-r--r-- | louloulibs/xmpp/xmpp_component.hpp | 245 | ||||
-rw-r--r-- | louloulibs/xmpp/xmpp_parser.cpp | 172 | ||||
-rw-r--r-- | louloulibs/xmpp/xmpp_parser.hpp | 133 | ||||
-rw-r--r-- | louloulibs/xmpp/xmpp_stanza.cpp | 229 | ||||
-rw-r--r-- | louloulibs/xmpp/xmpp_stanza.hpp | 160 |
17 files changed, 0 insertions, 2268 deletions
diff --git a/louloulibs/xmpp/adhoc_command.cpp b/louloulibs/xmpp/adhoc_command.cpp deleted file mode 100644 index 825cc92..0000000 --- a/louloulibs/xmpp/adhoc_command.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include <xmpp/adhoc_command.hpp> -#include <xmpp/xmpp_component.hpp> -#include <utils/reload.hpp> - -using namespace std::string_literals; - -AdhocCommand::AdhocCommand(std::vector<AdhocStep>&& callbacks, const std::string& name, const bool admin_only): - name(name), - callbacks(std::move(callbacks)), - admin_only(admin_only) -{ -} - -bool AdhocCommand::is_admin_only() const -{ - return this->admin_only; -} - -void PingStep1(XmppComponent&, AdhocSession&, XmlNode& command_node) -{ - XmlSubNode note(command_node, "note"); - note["type"] = "info"; - note.set_inner("Pong"); -} - -void HelloStep1(XmppComponent&, AdhocSession&, XmlNode& command_node) -{ - XmlSubNode x(command_node, "jabber:x:data:x"); - x["type"] = "form"; - XmlSubNode title(x, "title"); - title.set_inner("Configure your name."); - XmlSubNode instructions(x, "instructions"); - instructions.set_inner("Please provide your name."); - XmlSubNode name_field(x, "field"); - name_field["var"] = "name"; - name_field["type"] = "text-single"; - name_field["label"] = "Your name"; - XmlSubNode required(name_field, "required"); -} - -void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) -{ - // Find out if the name was provided in the form. - if (const XmlNode* x = command_node.get_child("x", "jabber:x:data")) - { - const XmlNode* name_field = nullptr; - for (const XmlNode* field: x->get_children("field", "jabber:x:data")) - if (field->get_tag("var") == "name") - { - name_field = field; - break; - } - if (name_field) - { - if (const XmlNode* value = name_field->get_child("value", "jabber:x:data")) - { - const std::string value_str = value->get_inner(); - command_node.delete_all_children(); - XmlSubNode note(command_node, "note"); - note["type"] = "info"; - note.set_inner("Hello "s + value_str + "!"s); - return; - } - } - } - command_node.delete_all_children(); - XmlSubNode error(command_node, ADHOC_NS":error"); - error["type"] = "modify"; - XmlSubNode condition(error, STANZA_NS":bad-request"); - session.terminate(); -} - -void Reload(XmppComponent&, AdhocSession&, XmlNode& command_node) -{ - ::reload_process(); - command_node.delete_all_children(); - XmlSubNode note(command_node, "note"); - note["type"] = "info"; - note.set_inner("Configuration reloaded."); -} diff --git a/louloulibs/xmpp/adhoc_command.hpp b/louloulibs/xmpp/adhoc_command.hpp deleted file mode 100644 index 7c4de47..0000000 --- a/louloulibs/xmpp/adhoc_command.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -/** - * Describe an ad-hoc command. - * - * Can only have zero or one step for now. When execution is requested, it - * can return a result immediately, or provide a form to be filled, and - * provide a result once the filled form is received. - */ - -#include <xmpp/adhoc_session.hpp> - -#include <functional> -#include <string> - -class AdhocCommand -{ - friend class AdhocSession; -public: - AdhocCommand(std::vector<AdhocStep>&& callback, const std::string& name, const bool admin_only); - ~AdhocCommand() = default; - AdhocCommand(const AdhocCommand&) = default; - AdhocCommand(AdhocCommand&&) = default; - AdhocCommand& operator=(AdhocCommand&&) = delete; - AdhocCommand& operator=(const AdhocCommand&) = delete; - - const std::string name; - - bool is_admin_only() const; - -private: - /** - * A command may have one or more steps. Each step is a different - * callback, inserting things into a <command/> XmlNode and calling - * methods of an AdhocSession. - */ - std::vector<AdhocStep> callbacks; - const bool admin_only; -}; - -void PingStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); -void HelloStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); -void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); -void Reload(XmppComponent&, AdhocSession& session, XmlNode& command_node); diff --git a/louloulibs/xmpp/adhoc_commands_handler.cpp b/louloulibs/xmpp/adhoc_commands_handler.cpp deleted file mode 100644 index 040d0ff..0000000 --- a/louloulibs/xmpp/adhoc_commands_handler.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include <xmpp/adhoc_commands_handler.hpp> -#include <xmpp/xmpp_component.hpp> - -#include <utils/timed_events.hpp> -#include <logger/logger.hpp> -#include <config/config.hpp> -#include <xmpp/jid.hpp> - -#include <iostream> - -using namespace std::string_literals; - -const std::map<const std::string, const AdhocCommand>& AdhocCommandsHandler::get_commands() const -{ - return this->commands; -} - -void AdhocCommandsHandler::add_command(std::string name, AdhocCommand command) -{ - const auto found = this->commands.find(name); - if (found != this->commands.end()) - throw std::runtime_error("Trying to add an ad-hoc command that already exist: "s + name); - this->commands.emplace(std::make_pair(std::move(name), std::move(command))); -} - -XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, const std::string& to, XmlNode command_node) -{ - std::string action = command_node.get_tag("action"); - if (action.empty()) - action = "execute"; - command_node.del_tag("action"); - - Jid jid(executor_jid); - - const std::string node = command_node.get_tag("node"); - auto command_it = this->commands.find(node); - if (command_it == this->commands.end()) - { - XmlSubNode error(command_node, ADHOC_NS":error"); - error["type"] = "cancel"; - XmlSubNode condition(error, STANZA_NS":item-not-found"); - } - else if (command_it->second.is_admin_only() && - Config::get("admin", "") != jid.local + "@" + jid.domain) - { - XmlSubNode error(command_node, ADHOC_NS":error"); - error["type"] = "cancel"; - XmlSubNode condition(error, STANZA_NS":forbidden"); - } - else - { - std::string sessionid = command_node.get_tag("sessionid"); - if (sessionid.empty()) - { // create a new session, with a new id - sessionid = XmppComponent::next_id(); - command_node["sessionid"] = sessionid; - this->sessions.emplace(std::piecewise_construct, - std::forward_as_tuple(sessionid, executor_jid), - std::forward_as_tuple(command_it->second, executor_jid, to)); - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 3600s, - std::bind(&AdhocCommandsHandler::remove_session, this, sessionid, executor_jid), - "adhocsession"s + sessionid + executor_jid)); - } - auto session_it = this->sessions.find(std::make_pair(sessionid, executor_jid)); - if ((session_it != this->sessions.end()) && - (action == "execute" || action == "next" || action == "complete")) - { - // execute the step - AdhocSession& session = session_it->second; - const AdhocStep& step = session.get_next_step(); - step(this->xmpp_component, session, command_node); - if (session.remaining_steps() == 0 || - session.is_terminated()) - { - this->sessions.erase(session_it); - command_node["status"] = "completed"; - TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); - } - else - { - command_node["status"] = "executing"; - XmlSubNode actions(command_node, "actions"); - XmlSubNode next(actions, "next"); - } - } - else if (action == "cancel") - { - this->sessions.erase(session_it); - command_node["status"] = "canceled"; - TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); - } - else // unsupported action - { - XmlSubNode error(command_node, ADHOC_NS":error"); - error["type"] = "modify"; - XmlSubNode condition(error, STANZA_NS":bad-request"); - } - } - return command_node; -} - -void AdhocCommandsHandler::remove_session(const std::string& session_id, const std::string& initiator_jid) -{ - auto session_it = this->sessions.find(std::make_pair(session_id, initiator_jid)); - if (session_it != this->sessions.end()) - { - this->sessions.erase(session_it); - return ; - } - log_error("Tried to remove ad-hoc session for [", session_id, ", ", initiator_jid, "] but none found"); -} diff --git a/louloulibs/xmpp/adhoc_commands_handler.hpp b/louloulibs/xmpp/adhoc_commands_handler.hpp deleted file mode 100644 index e37d913..0000000 --- a/louloulibs/xmpp/adhoc_commands_handler.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -/** - * Manage a list of available AdhocCommands and the list of ongoing - * AdhocCommandSessions. - */ - -#include <xmpp/adhoc_command.hpp> -#include <xmpp/xmpp_stanza.hpp> - -#include <utility> -#include <string> -#include <map> - -class AdhocCommandsHandler -{ -public: - explicit AdhocCommandsHandler(XmppComponent& xmpp_component): - xmpp_component(xmpp_component), - commands{} - { } - ~AdhocCommandsHandler() = default; - - AdhocCommandsHandler(const AdhocCommandsHandler&) = delete; - AdhocCommandsHandler(AdhocCommandsHandler&&) = delete; - AdhocCommandsHandler& operator=(const AdhocCommandsHandler&) = delete; - AdhocCommandsHandler& operator=(AdhocCommandsHandler&&) = delete; - - /** - * Returns the list of available commands. - */ - const std::map<const std::string, const AdhocCommand>& get_commands() const; - /** - * Add a command into the list, associated with the given name - */ - void add_command(std::string name, AdhocCommand command); - /** - * Find the requested command, create a new session or use an existing - * one, and process the request (provide a new form, an error, or a - * result). - * - * Returns a (moved) XmlNode that will be inserted in the iq response. It - * should be a <command/> node containing one or more useful children. If - * it contains an <error/> node, the iq response will have an error type. - * - * Takes a copy of the <command/> node so we can actually edit it and use - * it as our return value. - */ - XmlNode handle_request(const std::string& executor_jid, const std::string& to, XmlNode command_node); - /** - * Remove the session from the list. This is done to avoid filling the - * memory with waiting session (for example due to a client that starts - * multi-steps command but never finishes them). - */ - void remove_session(const std::string& session_id, const std::string& initiator_jid); -private: - /** - * To access basically anything in the gateway. - */ - XmppComponent& xmpp_component; - /** - * The list of all available commands. - */ - std::map<const std::string, const AdhocCommand> commands; - /** - * The list of all currently on-going commands. - * - * Of the form: {{session_id, owner_jid}, session}. - */ - std::map<std::pair<const std::string, const std::string>, AdhocSession> sessions; -}; diff --git a/louloulibs/xmpp/adhoc_session.cpp b/louloulibs/xmpp/adhoc_session.cpp deleted file mode 100644 index dda4bea..0000000 --- a/louloulibs/xmpp/adhoc_session.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include <xmpp/adhoc_session.hpp> -#include <xmpp/adhoc_command.hpp> - -#include <assert.h> - -AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& owner_jid, - const std::string& to_jid): - command(command), - owner_jid(owner_jid), - to_jid(to_jid), - current_step(0), - terminated(false) -{ -} - -const AdhocStep& AdhocSession::get_next_step() -{ - assert(this->current_step < this->command.callbacks.size()); - return this->command.callbacks[this->current_step++]; -} - -size_t AdhocSession::remaining_steps() const -{ - return this->command.callbacks.size() - this->current_step; -} - -bool AdhocSession::is_terminated() const -{ - return this->terminated; -} - -void AdhocSession::terminate() -{ - this->terminated = true; -} diff --git a/louloulibs/xmpp/adhoc_session.hpp b/louloulibs/xmpp/adhoc_session.hpp deleted file mode 100644 index 0de8d13..0000000 --- a/louloulibs/xmpp/adhoc_session.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -#include <xmpp/xmpp_stanza.hpp> - -#include <functional> -#include <string> -#include <map> - -class XmppComponent; - -class AdhocCommand; -class AdhocSession; - -/** - * A function executed as an ad-hoc command step. It takes a <command/> - * XmlNode and modifies it accordingly (inserting for example an <error/> - * node, or a data form…). - */ -using AdhocStep = std::function<void(XmppComponent&, AdhocSession&, XmlNode&)>; - -class AdhocSession -{ -public: - explicit AdhocSession(const AdhocCommand& command, const std::string& owner_jid, - const std::string& to_jid); - ~AdhocSession() = default; - - AdhocSession(const AdhocSession&) = delete; - AdhocSession(AdhocSession&&) = delete; - AdhocSession& operator=(const AdhocSession&) = delete; - AdhocSession& operator=(AdhocSession&&) = delete; - - /** - * Return the function to be executed, found in our AdhocCommand, for the - * current_step. And increment the current_step. - */ - const AdhocStep& get_next_step(); - /** - * Return the number of remaining steps. - */ - size_t remaining_steps() const; - /** - * This may be modified by an AdhocStep, to indicate that this session - * should no longer exist, because we encountered an error, and we can't - * execute any more step of it. - */ - void terminate(); - bool is_terminated() const; - std::string get_target_jid() const - { - return this->to_jid; - } - std::string get_owner_jid() const - { - return this->owner_jid; - } - -private: - /** - * A reference of the command concerned by this session. Used for example - * to get the next step of that command, things like that. - */ - const AdhocCommand& command; - /** - * The full JID of the XMPP user that created this session by executing - * the first step of a command. Only that JID must be allowed to access - * this session. - */ - const std::string& owner_jid; - /** - * The 'to' attribute in the request stanza. This is the target of the current session. - */ - const std::string& to_jid; - /** - * The current step we are at. It starts at zero. It is used to index the - * associated AdhocCommand::callbacks vector. - */ - size_t current_step; - bool terminated; - -public: - /** - * A map to store various things that we may want to remember between two - * steps of the same session. A step can insert any value associated to - * any key in there. - */ - std::map<std::string, std::string> vars; -}; diff --git a/louloulibs/xmpp/auth.cpp b/louloulibs/xmpp/auth.cpp deleted file mode 100644 index 8a34a4e..0000000 --- a/louloulibs/xmpp/auth.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include <xmpp/auth.hpp> - -#include <utils/sha1.hpp> - -std::string get_handshake_digest(const std::string& stream_id, const std::string& secret) -{ - return sha1(stream_id + secret); -} diff --git a/louloulibs/xmpp/auth.hpp b/louloulibs/xmpp/auth.hpp deleted file mode 100644 index 34a2116..0000000 --- a/louloulibs/xmpp/auth.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include <string> - -std::string get_handshake_digest(const std::string& stream_id, const std::string& secret); - diff --git a/louloulibs/xmpp/body.hpp b/louloulibs/xmpp/body.hpp deleted file mode 100644 index 068d1a4..0000000 --- a/louloulibs/xmpp/body.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - - -namespace Xmpp -{ -// Contains: -// - an XMPP-valid UTF-8 body -// - an XML node representing the XHTML-IM body, or null - using body = std::tuple<const std::string, std::unique_ptr<XmlNode>>; -} - - diff --git a/louloulibs/xmpp/jid.cpp b/louloulibs/xmpp/jid.cpp deleted file mode 100644 index 46e01ea..0000000 --- a/louloulibs/xmpp/jid.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include <xmpp/jid.hpp> -#include <algorithm> -#include <cstring> -#include <map> - -#include <louloulibs.h> -#ifdef LIBIDN_FOUND - #include <stringprep.h> - #include <sys/types.h> - #include <sys/socket.h> - #include <netdb.h> - #include <utils/scopeguard.hpp> - #include <set> -#endif - -#include <logger/logger.hpp> - -Jid::Jid(const std::string& jid) -{ - std::string::size_type slash = jid.find('/'); - if (slash != std::string::npos) - { - this->resource = jid.substr(slash + 1); - } - - std::string::size_type at = jid.find('@'); - if (at != std::string::npos && at < slash) - { - this->local = jid.substr(0, at); - at++; - } - else - at = 0; - - this->domain = jid.substr(at, slash - at); -} - -static constexpr size_t max_jid_part_len = 1023; - -std::string jidprep(const std::string& original) -{ -#ifdef LIBIDN_FOUND - using CacheType = std::map<std::string, std::string>; - static CacheType cache; - std::pair<CacheType::iterator, bool> cached = cache.insert({original, {}}); - if (std::get<1>(cached) == false) - { // Insertion failed: the result is already in the cache, return it - return std::get<0>(cached)->second; - } - - const std::string error_msg("Failed to convert " + original + " into a valid JID:"); - Jid jid(original); - - char local[max_jid_part_len] = {}; - memcpy(local, jid.local.data(), std::min(max_jid_part_len, jid.local.size())); - Stringprep_rc rc = static_cast<Stringprep_rc>(::stringprep(local, max_jid_part_len, - static_cast<Stringprep_profile_flags>(0), stringprep_xmpp_nodeprep)); - if (rc != STRINGPREP_OK) - { - log_error(error_msg + stringprep_strerror(rc)); - return ""; - } - - char domain[max_jid_part_len] = {}; - memcpy(domain, jid.domain.data(), std::min(max_jid_part_len, jid.domain.size())); - - { - // Using getaddrinfo, check if the domain part is a valid IPv4 (then use - // it as is), or IPv6 (surround it with []), or a domain name (run - // nameprep) - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_NUMERICHOST; - hints.ai_family = AF_UNSPEC; - - struct addrinfo* addr_res = nullptr; - const auto ret = ::getaddrinfo(domain, nullptr, &hints, &addr_res); - auto addrinfo_deleter = utils::make_scope_guard([addr_res] { if (addr_res) freeaddrinfo(addr_res); }); - if (ret || !addr_res || (addr_res->ai_family != AF_INET && addr_res->ai_family != AF_INET6)) - { // Not an IP, run nameprep on it - rc = static_cast<Stringprep_rc>(::stringprep(domain, max_jid_part_len, - static_cast<Stringprep_profile_flags>(0), stringprep_nameprep)); - if (rc != STRINGPREP_OK) - { - log_error(error_msg + stringprep_strerror(rc)); - return ""; - } - - // Make sure it contains only allowed characters - using std::begin; - using std::end; - char* domain_end = domain + ::strlen(domain); - std::replace_if(std::begin(domain), domain + ::strlen(domain), - [](const char c) -> bool - { - return !((c >= 'a' && c <= 'z') || c == '-' || - (c >= '0' && c <= '9') || c == '.'); - }, '-'); - // Make sure there are no doubled - or . - std::set<char> special_chars{'-', '.'}; - domain_end = std::unique(begin(domain), domain + ::strlen(domain), [&special_chars](const char& a, const char& b) -> bool - { - return special_chars.count(a) && special_chars.count(b); - }); - // remove leading and trailing -. if any - if (domain_end != domain && special_chars.count(*(domain_end - 1))) - --domain_end; - if (domain_end != domain && special_chars.count(domain[0])) - { - std::memmove(domain, domain + 1, domain_end - domain + 1); - --domain_end; - } - // And if the final result is an empty string, return a dummy hostname - if (domain_end == domain) - ::strcpy(domain, "empty"); - else - *domain_end = '\0'; - } - else if (addr_res->ai_family == AF_INET6) - { // IPv6, surround it with []. The length is always enough: - // the longest possible IPv6 is way shorter than max_jid_part_len - ::memmove(domain + 1, domain, jid.domain.size()); - domain[0] = '['; - domain[jid.domain.size() + 1] = ']'; - } - } - - - // If there is no resource, stop here - if (jid.resource.empty()) - { - std::get<0>(cached)->second = std::string(local) + "@" + domain; - return std::get<0>(cached)->second; - } - - // Otherwise, also process the resource part - char resource[max_jid_part_len] = {}; - memcpy(resource, jid.resource.data(), std::min(max_jid_part_len, jid.resource.size())); - rc = static_cast<Stringprep_rc>(::stringprep(resource, max_jid_part_len, - static_cast<Stringprep_profile_flags>(0), stringprep_xmpp_resourceprep)); - if (rc != STRINGPREP_OK) - { - log_error(error_msg + stringprep_strerror(rc)); - return ""; - } - std::get<0>(cached)->second = std::string(local) + "@" + domain + "/" + resource; - return std::get<0>(cached)->second; - -#else - (void)original; - return ""; -#endif -} diff --git a/louloulibs/xmpp/jid.hpp b/louloulibs/xmpp/jid.hpp deleted file mode 100644 index 85e835c..0000000 --- a/louloulibs/xmpp/jid.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - - -#include <string> - -/** - * Parse a JID into its different subart - */ -class Jid -{ -public: - explicit Jid(const std::string& jid); - - Jid(const Jid&) = delete; - Jid(Jid&&) = delete; - Jid& operator=(const Jid&) = delete; - Jid& operator=(Jid&&) = delete; - - std::string domain; - std::string local; - std::string resource; - - std::string bare() const - { - return this->local + "@" + this->domain; - } - std::string full() const - { - std::string res = this->domain; - if (!this->local.empty()) - res = this->local + "@" + this->domain; - if (!this->resource.empty()) - res += "/" + this->resource; - return res; - } -}; - -/** - * Prepare the given UTF-8 string according to the XMPP node stringprep - * identifier profile. This is used to send properly-formed JID to the XMPP - * server. - * - * If the stringprep library is not found, we return an empty string. When - * this function is used, the result must always be checked for an empty - * value, and if this is the case it must not be used as a JID. - */ -std::string jidprep(const std::string& original); - - diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp deleted file mode 100644 index 5d98e58..0000000 --- a/louloulibs/xmpp/xmpp_component.cpp +++ /dev/null @@ -1,672 +0,0 @@ -#include <utils/timed_events.hpp> -#include <utils/scopeguard.hpp> -#include <utils/tolower.hpp> -#include <logger/logger.hpp> - -#include <xmpp/xmpp_component.hpp> -#include <config/config.hpp> -#include <utils/system.hpp> -#include <utils/time.hpp> -#include <xmpp/auth.hpp> -#include <xmpp/jid.hpp> - -#include <stdexcept> -#include <iostream> -#include <set> - -#include <uuid/uuid.h> - -#include <cstdlib> -#include <set> - -#include <louloulibs.h> -#ifdef SYSTEMD_FOUND -# include <systemd/sd-daemon.h> -#endif - -using namespace std::string_literals; - -static std::set<std::string> kickable_errors{ - "gone", - "internal-server-error", - "item-not-found", - "jid-malformed", - "recipient-unavailable", - "redirect", - "remote-server-not-found", - "remote-server-timeout", - "service-unavailable", - "malformed-error" - }; - -XmppComponent::XmppComponent(std::shared_ptr<Poller>& poller, const std::string& hostname, const std::string& secret): - TCPClientSocketHandler(poller), - ever_auth(false), - first_connection_try(true), - secret(secret), - authenticated(false), - doc_open(false), - served_hostname(hostname), - stanza_handlers{}, - adhoc_commands_handler(*this) -{ - this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this, - std::placeholders::_1)); - this->parser.add_stanza_callback(std::bind(&XmppComponent::on_stanza, this, - std::placeholders::_1)); - this->parser.add_stream_close_callback(std::bind(&XmppComponent::on_remote_stream_close, this, - std::placeholders::_1)); - this->stanza_handlers.emplace("handshake", - std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1)); - this->stanza_handlers.emplace("error", - std::bind(&XmppComponent::handle_error, this,std::placeholders::_1)); -} - -void XmppComponent::start() -{ - this->connect(Config::get("xmpp_server_ip", "127.0.0.1"), Config::get("port", "5347"), false); -} - -bool XmppComponent::is_document_open() const -{ - return this->doc_open; -} - -void XmppComponent::send_stanza(const Stanza& stanza) -{ - std::string str = stanza.to_string(); - log_debug("XMPP SENDING: ", str); - this->send_data(std::move(str)); -} - -void XmppComponent::on_connection_failed(const std::string& reason) -{ - this->first_connection_try = false; - log_error("Failed to connect to the XMPP server: ", reason); -#ifdef SYSTEMD_FOUND - sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data()); -#endif -} - -void XmppComponent::on_connected() -{ - log_info("connected to XMPP server"); - this->first_connection_try = true; - auto data = "<stream:stream to='"s + this->served_hostname + \ - "' xmlns:stream='http://etherx.jabber.org/streams' xmlns='" COMPONENT_NS "'>"; - log_debug("XMPP SENDING: ", data); - this->send_data(std::move(data)); - this->doc_open = true; - // We may have some pending data to send: this happens when we try to send - // some data before we are actually connected. We send that data right now, if any - this->send_pending_data(); -} - -void XmppComponent::on_connection_close(const std::string& error) -{ - if (error.empty()) - log_info("XMPP server closed connection"); - else - log_info("XMPP server closed connection: ", error); -} - -void XmppComponent::parse_in_buffer(const size_t size) -{ - if (!this->in_buf.empty()) - { // This may happen if the parser could not allocate enough space for - // us. We try to feed it the data that was read into our in_buf - // instead. If this fails again we are in trouble. - this->parser.feed(this->in_buf.data(), this->in_buf.size(), false); - this->in_buf.clear(); - } - else - { // Just tell the parser to parse the data that was placed into the - // buffer it provided to us with GetBuffer - this->parser.parse(size, false); - } -} - -void XmppComponent::on_remote_stream_open(const XmlNode& node) -{ - log_debug("XMPP RECEIVING: ", node.to_string()); - this->stream_id = node.get_tag("id"); - if (this->stream_id.empty()) - { - log_error("Error: no attribute 'id' found"); - this->send_stream_error("bad-format", "missing 'id' attribute"); - this->close_document(); - return ; - } - - // Try to authenticate - auto data = "<handshake xmlns='" COMPONENT_NS "'>"s + get_handshake_digest(this->stream_id, this->secret) + "</handshake>"; - log_debug("XMPP SENDING: ", data); - this->send_data(std::move(data)); -} - -void XmppComponent::on_remote_stream_close(const XmlNode& node) -{ - log_debug("XMPP RECEIVING: ", node.to_string()); - this->doc_open = false; -} - -void XmppComponent::reset() -{ - this->parser.reset(); -} - -void XmppComponent::on_stanza(const Stanza& stanza) -{ - log_debug("XMPP RECEIVING: ", stanza.to_string()); - std::function<void(const Stanza&)> handler; - try - { - handler = this->stanza_handlers.at(stanza.get_name()); - } - catch (const std::out_of_range& exception) - { - log_warning("No handler for stanza of type ", stanza.get_name()); - return; - } - handler(stanza); -} - -void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation) -{ - Stanza node("stream:error"); - { - XmlSubNode error(node, name); - error["xmlns"] = STREAM_NS; - if (!explanation.empty()) - error.set_inner(explanation); - } - this->send_stanza(node); -} - -void XmppComponent::send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, - const std::string& id, const std::string& error_type, - const std::string& defined_condition, const std::string& text, - const bool fulljid) -{ - Stanza node(kind); - { - if (!to.empty()) - node["to"] = to; - if (!from.empty()) - { - if (fulljid) - node["from"] = from; - else - node["from"] = from + "@" + this->served_hostname; - } - if (!id.empty()) - node["id"] = id; - node["type"] = "error"; - { - XmlSubNode error(node, "error"); - error["type"] = error_type; - { - XmlSubNode inner_error(error, defined_condition); - inner_error["xmlns"] = STANZA_NS; - } - if (!text.empty()) - { - XmlSubNode text_node(error, "text"); - text_node["xmlns"] = STANZA_NS; - text_node.set_inner(text); - } - } - } - this->send_stanza(node); -} - -void XmppComponent::close_document() -{ - log_debug("XMPP SENDING: </stream:stream>"); - this->send_data("</stream:stream>"); - this->doc_open = false; -} - -void XmppComponent::handle_handshake(const Stanza&) -{ - this->authenticated = true; - this->ever_auth = true; - log_info("Authenticated with the XMPP server"); -#ifdef SYSTEMD_FOUND - sd_notify(0, "READY=1"); - // Install an event that sends a keepalive to systemd. If biboumi crashes - // or hangs for too long, systemd will restart it. - uint64_t usec; - if (sd_watchdog_enabled(0, &usec) > 0) - { - TimedEventsManager::instance().add_event(TimedEvent( - std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::microseconds(usec / 2)), - []() { sd_notify(0, "WATCHDOG=1"); })); - } -#endif - this->after_handshake(); -} - -void XmppComponent::handle_error(const Stanza& stanza) -{ - const XmlNode* text = stanza.get_child("text", STREAMS_NS); - std::string error_message("Unspecified error"); - if (text) - error_message = text->get_inner(); - log_error("Stream error received from the XMPP server: ", error_message); -#ifdef SYSTEMD_FOUND - if (!this->ever_auth) - sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data()); -#endif - -} - -void* XmppComponent::get_receive_buffer(const size_t size) const -{ - return this->parser.get_buffer(size); -} - -void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, - const std::string& type, const bool fulljid, const bool nocopy) -{ - Stanza message("message"); - { - message["to"] = to; - if (fulljid) - message["from"] = from; - else - message["from"] = from + "@" + this->served_hostname; - if (!type.empty()) - message["type"] = type; - XmlSubNode body_node(message, "body"); - body_node.set_inner(std::get<0>(body)); - if (std::get<1>(body)) - { - XmlSubNode html(message, "html"); - html["xmlns"] = XHTMLIM_NS; - // Pass the ownership of the pointer to this xmlnode - html.add_child(std::move(std::get<1>(body))); - } - if (nocopy) - { - XmlSubNode private_node(message, "private"); - private_node["xmlns"] = "urn:xmpp:carbons:2"; - XmlSubNode nocopy(message, "no-copy"); - nocopy["xmlns"] = "urn:xmpp:hints"; - } - } - this->send_stanza(message); -} - -void XmppComponent::send_user_join(const std::string& from, - const std::string& nick, - const std::string& realjid, - const std::string& affiliation, - const std::string& role, - const std::string& to, - const bool self) -{ - Stanza presence("presence"); - { - presence["to"] = to; - presence["from"] = from + "@" + this->served_hostname + "/" + nick; - - XmlSubNode x(presence, "x"); - x["xmlns"] = MUC_USER_NS; - - XmlSubNode item(x, "item"); - if (!affiliation.empty()) - item["affiliation"] = affiliation; - if (!role.empty()) - item["role"] = role; - if (!realjid.empty()) - { - const std::string preped_jid = jidprep(realjid); - if (!preped_jid.empty()) - item["jid"] = preped_jid; - } - - if (self) - { - XmlSubNode status(x, "status"); - status["code"] = "110"; - } - } - this->send_stanza(presence); -} - -void XmppComponent::send_invalid_room_error(const std::string& muc_name, - const std::string& nick, - const std::string& to) -{ - Stanza presence("presence"); - { - if (!muc_name.empty ()) - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; - else - presence["from"] = this->served_hostname; - presence["to"] = to; - presence["type"] = "error"; - XmlSubNode x(presence, "x"); - x["xmlns"] = MUC_NS; - XmlSubNode error(presence, "error"); - error["by"] = muc_name + "@" + this->served_hostname; - error["type"] = "cancel"; - XmlSubNode item_not_found(error, "item-not-found"); - item_not_found["xmlns"] = STANZA_NS; - XmlSubNode text(error, "text"); - text["xmlns"] = STANZA_NS; - text["xml:lang"] = "en"; - text.set_inner(muc_name + - " is not a valid IRC channel name. A correct room jid is of the form: #<chan>%<server>@" + - this->served_hostname); - } - this->send_stanza(presence); -} - -void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to, const std::string& who) -{ - Stanza message("message"); - { - message["to"] = to; - if (who.empty()) - message["from"] = from + "@" + this->served_hostname; - else - message["from"] = from + "@" + this->served_hostname + "/" + who; - message["type"] = "groupchat"; - XmlSubNode subject(message, "subject"); - subject.set_inner(std::get<0>(topic)); - } - this->send_stanza(message); -} - -void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to) -{ - Stanza message("message"); - message["to"] = jid_to; - if (!nick.empty()) - message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; - else // Message from the room itself - message["from"] = muc_name + "@" + this->served_hostname; - message["type"] = "groupchat"; - - { - XmlSubNode body(message, "body"); - body.set_inner(std::get<0>(xmpp_body)); - } - - if (std::get<1>(xmpp_body)) - { - XmlSubNode html(message, "html"); - html["xmlns"] = XHTMLIM_NS; - // Pass the ownership of the pointer to this xmlnode - html.add_child(std::move(std::get<1>(xmpp_body))); - } - this->send_stanza(message); -} - -void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, std::time_t timestamp) -{ - Stanza message("message"); - message["to"] = jid_to; - if (!nick.empty()) - message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; - else - message["from"] = muc_name + "@" + this->served_hostname; - message["type"] = "groupchat"; - - { - XmlSubNode body(message, "body"); - body.set_inner(body_txt); - } - { - XmlSubNode delay(message, "delay"); - delay["xmlns"] = DELAY_NS; - delay["from"] = muc_name + "@" + this->served_hostname; - delay["stamp"] = utils::to_string(timestamp); - } - - this->send_stanza(message); -} - -void XmppComponent::send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self) -{ - Stanza presence("presence"); - { - presence["to"] = jid_to; - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; - presence["type"] = "unavailable"; - const std::string message_str = std::get<0>(message); - XmlSubNode x(presence, "x"); - x["xmlns"] = MUC_USER_NS; - if (self) - { - XmlSubNode status(x, "status"); - status["code"] = "110"; - } - if (!message_str.empty()) - { - XmlSubNode status(presence, "status"); - status.set_inner(message_str); - } - } - this->send_stanza(presence); -} - -void XmppComponent::send_nick_change(const std::string& muc_name, - const std::string& old_nick, - const std::string& new_nick, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to, - const bool self) -{ - Stanza presence("presence"); - { - presence["to"] = jid_to; - presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick; - presence["type"] = "unavailable"; - XmlSubNode x(presence, "x"); - x["xmlns"] = MUC_USER_NS; - XmlSubNode item(x, "item"); - item["nick"] = new_nick; - XmlSubNode status(x, "status"); - status["code"] = "303"; - if (self) - { - XmlSubNode status(x, "status"); - status["code"] = "110"; - } - } - this->send_stanza(presence); - - this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self); -} - -void XmppComponent::kick_user(const std::string& muc_name, const std::string& target, const std::string& txt, - const std::string& author, const std::string& jid_to, const bool self) -{ - Stanza presence("presence"); - { - presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; - presence["to"] = jid_to; - presence["type"] = "unavailable"; - XmlSubNode x(presence, "x"); - x["xmlns"] = MUC_USER_NS; - XmlSubNode item(x, "item"); - item["affiliation"] = "none"; - item["role"] = "none"; - XmlSubNode actor(item, "actor"); - actor["nick"] = author; - actor["jid"] = author; // backward compatibility with old clients - XmlSubNode reason(item, "reason"); - reason.set_inner(txt); - XmlSubNode status(x, "status"); - status["code"] = "307"; - if (self) - { - XmlSubNode status(x, "status"); - status["code"] = "110"; - } - } - this->send_stanza(presence); -} - -void XmppComponent::send_presence_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to, - const std::string& type, - const std::string& condition, - const std::string& error_code, - const std::string& text) -{ - Stanza presence("presence"); - { - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; - presence["to"] = jid_to; - presence["type"] = "error"; - XmlSubNode x(presence, "x"); - x["xmlns"] = MUC_NS; - XmlSubNode error(presence, "error"); - error["by"] = muc_name + "@" + this->served_hostname; - error["type"] = type; - if (!text.empty()) - { - XmlSubNode text_node(error, "text"); - text_node["xmlns"] = STANZA_NS; - text_node.set_inner(text); - } - if (!error_code.empty()) - error["code"] = error_code; - XmlSubNode subnode(error, condition); - subnode["xmlns"] = STANZA_NS; - } - this->send_stanza(presence); -} - -void XmppComponent::send_affiliation_role_change(const std::string& muc_name, - const std::string& target, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to) -{ - Stanza presence("presence"); - { - presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; - presence["to"] = jid_to; - XmlSubNode x(presence, "x"); - x["xmlns"] = MUC_USER_NS; - XmlSubNode item(x, "item"); - item["affiliation"] = affiliation; - item["role"] = role; - } - this->send_stanza(presence); -} - -void XmppComponent::send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from, - const std::string& version) -{ - Stanza iq("iq"); - iq["type"] = "result"; - iq["id"] = id; - iq["to"] = jid_to; - iq["from"] = jid_from; - { - XmlSubNode query(iq, "query"); - query["xmlns"] = VERSION_NS; - if (version.empty()) - { - { - XmlSubNode name(query, "name"); - name.set_inner("biboumi"); - } - { - XmlSubNode version(query, "version"); - version.set_inner(SOFTWARE_VERSION); - } - { - XmlSubNode os(query, "os"); - os.set_inner(utils::get_system_name()); - } - } - else - { - XmlSubNode name(query, "name"); - name.set_inner(version); - } - } - this->send_stanza(iq); -} - -void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid, - const std::string& from_jid, - const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler) -{ - Stanza iq("iq"); - { - iq["type"] = "result"; - iq["id"] = id; - iq["to"] = requester_jid; - iq["from"] = from_jid; - XmlSubNode query(iq, "query"); - query["xmlns"] = DISCO_ITEMS_NS; - query["node"] = ADHOC_NS; - for (const auto &kv: adhoc_handler.get_commands()) - { - if (kv.second.is_admin_only() && !with_admin_only) - continue; - XmlSubNode item(query, "item"); - item["jid"] = from_jid; - item["node"] = kv.first; - item["name"] = kv.second.name; - } - } - this->send_stanza(iq); -} - -void XmppComponent::send_iq_version_request(const std::string& from, - const std::string& jid_to) -{ - Stanza iq("iq"); - { - iq["type"] = "get"; - iq["id"] = "version_"s + XmppComponent::next_id(); - iq["from"] = from + "@" + this->served_hostname; - iq["to"] = jid_to; - XmlSubNode query(iq, "query"); - query["xmlns"] = VERSION_NS; - } - this->send_stanza(iq); -} - -void XmppComponent::send_iq_result_full_jid(const std::string& id, const std::string& to_jid, const std::string& from_full_jid) -{ - Stanza iq("iq"); - iq["from"] = from_full_jid; - iq["to"] = to_jid; - iq["id"] = id; - iq["type"] = "result"; - this->send_stanza(iq); -} - -void XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part) -{ - Stanza iq("iq"); - if (!from_local_part.empty()) - iq["from"] = from_local_part + "@" + this->served_hostname; - else - iq["from"] = this->served_hostname; - iq["to"] = to_jid; - iq["id"] = id; - iq["type"] = "result"; - this->send_stanza(iq); -} - -std::string XmppComponent::next_id() -{ - char uuid_str[37]; - uuid_t uuid; - uuid_generate(uuid); - uuid_unparse(uuid, uuid_str); - return uuid_str; -} diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp deleted file mode 100644 index 16d7480..0000000 --- a/louloulibs/xmpp/xmpp_component.hpp +++ /dev/null @@ -1,245 +0,0 @@ -#pragma once - - -#include <xmpp/adhoc_commands_handler.hpp> -#include <network/tcp_client_socket_handler.hpp> -#include <xmpp/xmpp_parser.hpp> -#include <xmpp/body.hpp> - -#include <unordered_map> -#include <memory> -#include <string> -#include <ctime> -#include <map> - -#define STREAM_NS "http://etherx.jabber.org/streams" -#define COMPONENT_NS "jabber:component:accept" -#define MUC_NS "http://jabber.org/protocol/muc" -#define MUC_USER_NS MUC_NS"#user" -#define MUC_ADMIN_NS MUC_NS"#admin" -#define DISCO_NS "http://jabber.org/protocol/disco" -#define DISCO_ITEMS_NS DISCO_NS"#items" -#define DISCO_INFO_NS DISCO_NS"#info" -#define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im" -#define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas" -#define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" -#define VERSION_NS "jabber:iq:version" -#define ADHOC_NS "http://jabber.org/protocol/commands" -#define PING_NS "urn:xmpp:ping" -#define DELAY_NS "urn:xmpp:delay" -#define MAM_NS "urn:xmpp:mam:1" -#define FORWARD_NS "urn:xmpp:forward:0" -#define CLIENT_NS "jabber:client" -#define DATAFORM_NS "jabber:x:data" -#define RSM_NS "http://jabber.org/protocol/rsm" -#define MUC_TRAFFIC_NS "http://jabber.org/protocol/muc#traffic" - -/** - * An XMPP component, communicating with an XMPP server using the protocole - * described in XEP-0114: Jabber Component Protocol - * - * TODO: implement XEP-0225: Component Connections - */ -class XmppComponent: public TCPClientSocketHandler -{ -public: - explicit XmppComponent(std::shared_ptr<Poller>& poller, const std::string& hostname, const std::string& secret); - virtual ~XmppComponent() = default; - - XmppComponent(const XmppComponent&) = delete; - XmppComponent(XmppComponent&&) = delete; - XmppComponent& operator=(const XmppComponent&) = delete; - XmppComponent& operator=(XmppComponent&&) = delete; - - void on_connection_failed(const std::string& reason) override final; - void on_connected() override final; - void on_connection_close(const std::string& error) override final; - void parse_in_buffer(const size_t size) override final; - - /** - * Returns a unique id, to be used in the 'id' element of our iq stanzas. - */ - static std::string next_id(); - bool is_document_open() const; - /** - * Connect to the XMPP server. - */ - void start(); - /** - * Reset the component so we can use the component on a new XMPP stream - */ - void reset(); - /** - * Serialize the stanza and add it to the out_buf to be sent to the - * server. - */ - void send_stanza(const Stanza& stanza); - /** - * Handle the opening of the remote stream - */ - void on_remote_stream_open(const XmlNode& node); - /** - * Handle the closing of the remote stream - */ - void on_remote_stream_close(const XmlNode& node); - /** - * Handle received stanzas - */ - void on_stanza(const Stanza& stanza); - /** - * Send an error stanza. Message being the name of the element inside the - * stanza, and explanation being a short human-readable sentence - * describing the error. - */ - void send_stream_error(const std::string& message, const std::string& explanation); - /** - * Send error stanza, described in http://xmpp.org/rfcs/rfc6120.html#stanzas-error - */ - void send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, - const std::string& id, const std::string& error_type, - const std::string& defined_condition, const std::string& text, - const bool fulljid=true); - /** - * Send the closing signal for our document (not closing the connection though). - */ - void close_document(); - /** - * Send a message from from@served_hostname, with the given body - * - * If fulljid is false, the provided 'from' doesn't contain the - * server-part of the JID and must be added. - */ - void send_message(const std::string& from, Xmpp::body&& body, const std::string& to, - const std::string& type, const bool fulljid, const bool nocopy=false); - /** - * Send a join from a new participant - */ - void send_user_join(const std::string& from, - const std::string& nick, - const std::string& realjid, - const std::string& affiliation, - const std::string& role, - const std::string& to, - const bool self); - /** - * Send an error to indicate that the user tried to join an invalid room - */ - void send_invalid_room_error(const std::string& muc_jid, - const std::string& nick, - const std::string& to); - /** - * Send the MUC topic to the user - */ - void send_topic(const std::string& from, Xmpp::body&& xmpp_topic, const std::string& to, const std::string& who); - /** - * Send a (non-private) message to the MUC - */ - void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to); - /** - * Send a message, with a <delay/> element, part of a MUC history - */ - void send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body, - const std::string& jid_to, const std::time_t timestamp); - /** - * Send an unavailable presence for this nick - */ - void send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self); - /** - * Indicate that a participant changed his nick - */ - void send_nick_change(const std::string& muc_name, - const std::string& old_nick, - const std::string& new_nick, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to, - const bool self); - /** - * An user is kicked from a room - */ - void kick_user(const std::string& muc_name, const std::string& target, const std::string& reason, - const std::string& author, const std::string& jid_to, const bool self); - /** - * Send a generic presence error - */ - void send_presence_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to, - const std::string& type, - const std::string& condition, - const std::string& error_code, - const std::string& text); - /** - * Send a presence from the MUC indicating a change in the role and/or - * affiliation of a participant - */ - void send_affiliation_role_change(const std::string& muc_name, - const std::string& target, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to); - /** - * Send a result IQ with the given version, or the gateway version if the - * passed string is empty. - */ - void send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from, - const std::string& version=""); - /** - * Send the list of all available ad-hoc commands to that JID. The list is - * different depending on what JID made the request. - */ - void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid, const std::string& from_jid, - const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler); - /** - * Send an iq version request - */ - void send_iq_version_request(const std::string& from, - const std::string& jid_to); - /** - * Send an empty iq of type result - */ - void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from); - void send_iq_result_full_jid(const std::string& id, const std::string& to_jid, - const std::string& from_full_jid); - - void handle_handshake(const Stanza& stanza); - void handle_error(const Stanza& stanza); - - virtual void after_handshake() {} - - const std::string& get_served_hostname() const - { return this->served_hostname; } - - /** - * Whether or not we ever succeeded our authentication to the XMPP server - */ - bool ever_auth; - /** - * Whether or not this is the first consecutive try on connecting to the - * XMPP server. We use this to delay the connection attempt for a few - * seconds, if it is not the first try. - */ - bool first_connection_try; - -private: - /** - * Return a buffer provided by the XML parser, to read data directly into - * it, and avoiding some unnecessary copy. - */ - void* get_receive_buffer(const size_t size) const override final; - XmppParser parser; - std::string stream_id; - std::string secret; - bool authenticated; - /** - * Whether or not OUR XMPP document is open - */ - bool doc_open; -protected: - std::string served_hostname; - - std::unordered_map<std::string, std::function<void(const Stanza&)>> stanza_handlers; - AdhocCommandsHandler adhoc_commands_handler; -}; - - diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/louloulibs/xmpp/xmpp_parser.cpp deleted file mode 100644 index 0488be9..0000000 --- a/louloulibs/xmpp/xmpp_parser.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include <xmpp/xmpp_parser.hpp> -#include <xmpp/xmpp_stanza.hpp> - -#include <logger/logger.hpp> - -/** - * Expat handlers. Called by the Expat library, never by ourself. - * They just forward the call to the XmppParser corresponding methods. - */ - -static void start_element_handler(void* user_data, const XML_Char* name, const XML_Char** atts) -{ - static_cast<XmppParser*>(user_data)->start_element(name, atts); -} - -static void end_element_handler(void* user_data, const XML_Char* name) -{ - static_cast<XmppParser*>(user_data)->end_element(name); -} - -static void character_data_handler(void *user_data, const XML_Char *s, int len) -{ - static_cast<XmppParser*>(user_data)->char_data(s, len); -} - -/** - * XmppParser class - */ - -XmppParser::XmppParser(): - level(0), - current_node(nullptr), - root(nullptr) -{ - this->init_xml_parser(); -} - -void XmppParser::init_xml_parser() -{ - // Create the expat parser - this->parser = XML_ParserCreateNS("UTF-8", ':'); - XML_SetUserData(this->parser, static_cast<void*>(this)); - - // Install Expat handlers - XML_SetElementHandler(this->parser, &start_element_handler, &end_element_handler); - XML_SetCharacterDataHandler(this->parser, &character_data_handler); -} - -XmppParser::~XmppParser() -{ - XML_ParserFree(this->parser); -} - -int XmppParser::feed(const char* data, const int len, const bool is_final) -{ - int res = XML_Parse(this->parser, data, len, is_final); - if (res == XML_STATUS_ERROR && - (XML_GetErrorCode(this->parser) != XML_ERROR_FINISHED)) - log_error("Xml_Parse encountered an error: ", - XML_ErrorString(XML_GetErrorCode(this->parser))); - return res; -} - -int XmppParser::parse(const int len, const bool is_final) -{ - int res = XML_ParseBuffer(this->parser, len, is_final); - if (res == XML_STATUS_ERROR) - log_error("Xml_Parsebuffer encountered an error: ", - XML_ErrorString(XML_GetErrorCode(this->parser))); - return res; -} - -void XmppParser::reset() -{ - XML_ParserFree(this->parser); - this->init_xml_parser(); - this->current_node = nullptr; - this->root.reset(nullptr); - this->level = 0; -} - -void* XmppParser::get_buffer(const size_t size) const -{ - return XML_GetBuffer(this->parser, static_cast<int>(size)); -} - -void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute) -{ - this->level++; - - auto new_node = std::make_unique<XmlNode>(name, this->current_node); - auto new_node_ptr = new_node.get(); - if (this->current_node) - this->current_node->add_child(std::move(new_node)); - else - this->root = std::move(new_node); - this->current_node = new_node_ptr; - for (size_t i = 0; attribute[i]; i += 2) - this->current_node->set_attribute(attribute[i], attribute[i+1]); - if (this->level == 1) - this->stream_open_event(*this->current_node); -} - -void XmppParser::end_element(const XML_Char*) -{ - this->level--; - if (this->level == 0) - { // End of the whole stream - this->stream_close_event(*this->current_node); - this->current_node = nullptr; - this->root.reset(); - } - else - { - auto parent = this->current_node->get_parent(); - if (this->level == 1) - { // End of a stanza - this->stanza_event(*this->current_node); - // Note: deleting all the children of our parent deletes ourself, - // so current_node is an invalid pointer after this line - parent->delete_all_children(); - } - this->current_node = parent; - } -} - -void XmppParser::char_data(const XML_Char* data, const size_t len) -{ - if (this->current_node->has_children()) - this->current_node->get_last_child()->add_to_tail({data, len}); - else - this->current_node->add_to_inner({data, len}); -} - -void XmppParser::stanza_event(const Stanza& stanza) const -{ - for (const auto& callback: this->stanza_callbacks) - { - try { - callback(stanza); - } catch (const std::exception& e) { - log_error("Unhandled exception: ", e.what()); - } - } -} - -void XmppParser::stream_open_event(const XmlNode& node) const -{ - for (const auto& callback: this->stream_open_callbacks) - callback(node); -} - -void XmppParser::stream_close_event(const XmlNode& node) const -{ - for (const auto& callback: this->stream_close_callbacks) - callback(node); -} - -void XmppParser::add_stanza_callback(std::function<void(const Stanza&)>&& callback) -{ - this->stanza_callbacks.emplace_back(std::move(callback)); -} - -void XmppParser::add_stream_open_callback(std::function<void(const XmlNode&)>&& callback) -{ - this->stream_open_callbacks.emplace_back(std::move(callback)); -} - -void XmppParser::add_stream_close_callback(std::function<void(const XmlNode&)>&& callback) -{ - this->stream_close_callbacks.emplace_back(std::move(callback)); -} diff --git a/louloulibs/xmpp/xmpp_parser.hpp b/louloulibs/xmpp/xmpp_parser.hpp deleted file mode 100644 index 9d67228..0000000 --- a/louloulibs/xmpp/xmpp_parser.hpp +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once - - -#include <xmpp/xmpp_stanza.hpp> - -#include <functional> - -#include <expat.h> - -/** - * A SAX XML parser that builds XML nodes and spawns events when a complete - * stanza is received (an element of level 2), or when the document is - * opened/closed (an element of level 1) - * - * After a stanza_event has been spawned, we delete the whole stanza. This - * means that even with a very long document (in XMPP the document is - * potentially infinite), the memory is never exhausted as long as each - * stanza is reasonnably short. - * - * The element names generated by expat contain the namespace of the - * element, a colon (':') and then the actual name of the element. To get - * an element "x" with a namespace of "http://jabber.org/protocol/muc", you - * just look for an XmlNode named "http://jabber.org/protocol/muc:x" - * - * TODO: enforce the size-limit for the stanza (limit the number of childs - * it can contain). For example forbid the parser going further than level - * 20 (arbitrary number here), and each XML node to have more than 15 childs - * (arbitrary number again). - */ -class XmppParser -{ -public: - explicit XmppParser(); - ~XmppParser(); - XmppParser(const XmppParser&) = delete; - XmppParser& operator=(const XmppParser&) = delete; - XmppParser(XmppParser&&) = delete; - XmppParser& operator=(XmppParser&&) = delete; - -public: - /** - * Feed the parser with some XML data - */ - int feed(const char* data, const int len, const bool is_final); - /** - * Parse the data placed in the parser buffer - */ - int parse(const int size, const bool is_final); - /** - * Reset the parser, so it can be used from scratch afterward - */ - void reset(); - /** - * Get a buffer provided by the xml parser. - */ - void* get_buffer(const size_t size) const; - /** - * Add one callback for the various events that this parser can spawn. - */ - void add_stanza_callback(std::function<void(const Stanza&)>&& callback); - void add_stream_open_callback(std::function<void(const XmlNode&)>&& callback); - void add_stream_close_callback(std::function<void(const XmlNode&)>&& callback); - - /** - * Called when a new XML element has been opened. We instanciate a new - * XmlNode and set it as our current node. The parent of this new node is - * the previous "current" node. We have all the element's attributes in - * this event. - * - * We spawn a stream_event with this node if this is a level-1 element. - */ - void start_element(const XML_Char* name, const XML_Char** attribute); - /** - * Called when an XML element has been closed. We close the current_node, - * set our current_node as the parent of the current_node, and if that was - * a level-2 element we spawn a stanza_event with this node. - * - * And we then delete the stanza (and everything under it, its children, - * attribute, etc). - */ - void end_element(const XML_Char* name); - /** - * Some inner or tail data has been parsed - */ - void char_data(const XML_Char* data, const size_t len); - /** - * Calls all the stanza_callbacks one by one. - */ - void stanza_event(const Stanza& stanza) const; - /** - * Calls all the stream_open_callbacks one by one. Note: the passed node is not - * closed yet. - */ - void stream_open_event(const XmlNode& node) const; - /** - * Calls all the stream_close_callbacks one by one. - */ - void stream_close_event(const XmlNode& node) const; - -private: - /** - * Init the XML parser and install the callbacks - */ - void init_xml_parser(); - - /** - * Expat structure. - */ - XML_Parser parser; - /** - * The current depth in the XML document - */ - size_t level; - /** - * The deepest XML node opened but not yet closed (to which we are adding - * new children, inner or tail) - */ - XmlNode* current_node; - /** - * The root node has no parent, so we keep it here: the XmppParser object - * is its owner. - */ - std::unique_ptr<XmlNode> root; - /** - * A list of callbacks to be called on an *_event, receiving the - * concerned Stanza/XmlNode. - */ - std::vector<std::function<void(const Stanza&)>> stanza_callbacks; - std::vector<std::function<void(const XmlNode&)>> stream_open_callbacks; - std::vector<std::function<void(const XmlNode&)>> stream_close_callbacks; -}; - - diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp deleted file mode 100644 index ac6ce9b..0000000 --- a/louloulibs/xmpp/xmpp_stanza.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#include <xmpp/xmpp_stanza.hpp> - -#include <utils/encoding.hpp> -#include <utils/split.hpp> - -#include <stdexcept> -#include <iostream> -#include <sstream> - -#include <string.h> - -std::string xml_escape(const std::string& data) -{ - std::string res; - res.reserve(data.size()); - for (size_t pos = 0; pos != data.size(); ++pos) - { - switch(data[pos]) - { - case '&': - res += "&"; - break; - case '<': - res += "<"; - break; - case '>': - res += ">"; - break; - case '\"': - res += """; - break; - case '\'': - res += "'"; - break; - default: - res += data[pos]; - break; - } - } - return res; -} - -std::string sanitize(const std::string& data, const std::string& encoding) -{ - if (utils::is_valid_utf8(data.data())) - return xml_escape(utils::remove_invalid_xml_chars(data)); - else - return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, encoding.data()))); -} - -XmlNode::XmlNode(const std::string& name, XmlNode* parent): - parent(parent) -{ - // split the namespace and the name - auto n = name.rfind(":"); - if (n == std::string::npos) - this->name = name; - else - { - this->name = name.substr(n+1); - this->attributes["xmlns"] = name.substr(0, n); - } -} - -XmlNode::XmlNode(const std::string& name): - XmlNode(name, nullptr) -{ -} - -void XmlNode::delete_all_children() -{ - this->children.clear(); -} - -void XmlNode::set_attribute(const std::string& name, const std::string& value) -{ - this->attributes[name] = value; -} - -void XmlNode::set_tail(const std::string& data) -{ - this->tail = data; -} - -void XmlNode::add_to_tail(const std::string& data) -{ - this->tail += data; -} - -void XmlNode::set_inner(const std::string& data) -{ - this->inner = data; -} - -void XmlNode::add_to_inner(const std::string& data) -{ - this->inner += data; -} - -std::string XmlNode::get_inner() const -{ - return this->inner; -} - -std::string XmlNode::get_tail() const -{ - return this->tail; -} - -const XmlNode* XmlNode::get_child(const std::string& name, const std::string& xmlns) const -{ - for (const auto& child: this->children) - { - if (child->name == name && child->get_tag("xmlns") == xmlns) - return child.get(); - } - return nullptr; -} - -std::vector<const XmlNode*> XmlNode::get_children(const std::string& name, const std::string& xmlns) const -{ - std::vector<const XmlNode*> res; - for (const auto& child: this->children) - { - if (child->name == name && child->get_tag("xmlns") == xmlns) - res.push_back(child.get()); - } - return res; -} - -XmlNode* XmlNode::add_child(std::unique_ptr<XmlNode> child) -{ - child->parent = this; - auto ret = child.get(); - this->children.push_back(std::move(child)); - return ret; -} - -XmlNode* XmlNode::add_child(XmlNode&& child) -{ - auto new_node = std::make_unique<XmlNode>(std::move(child)); - return this->add_child(std::move(new_node)); -} - -XmlNode* XmlNode::add_child(const XmlNode& child) -{ - auto new_node = std::make_unique<XmlNode>(child); - return this->add_child(std::move(new_node)); -} - -XmlNode* XmlNode::get_last_child() const -{ - return this->children.back().get(); -} - -XmlNode* XmlNode::get_parent() const -{ - return this->parent; -} - -void XmlNode::set_name(const std::string& name) -{ - this->name = name; -} - -void XmlNode::set_name(std::string&& name) -{ - this->name = std::move(name); -} - -const std::string XmlNode::get_name() const -{ - return this->name; -} - -std::string XmlNode::to_string() const -{ - std::ostringstream res; - res << "<" << this->name; - for (const auto& it: this->attributes) - res << " " << it.first << "='" << sanitize(it.second) + "'"; - if (!this->has_children() && this->inner.empty()) - res << "/>"; - else - { - res << ">" + sanitize(this->inner); - for (const auto& child: this->children) - res << child->to_string(); - res << "</" << this->get_name() << ">"; - } - res << sanitize(this->tail); - return res.str(); -} - -bool XmlNode::has_children() const -{ - return !this->children.empty(); -} - -const std::string& XmlNode::get_tag(const std::string& name) const -{ - try - { - const auto& value = this->attributes.at(name); - return value; - } - catch (const std::out_of_range& e) - { - static const std::string def{}; - return def; - } -} - -bool XmlNode::del_tag(const std::string& name) -{ - if (this->attributes.erase(name) != 0) - return true; - return false; -} - -std::string& XmlNode::operator[](const std::string& name) -{ - return this->attributes[name]; -} - -std::ostream& operator<<(std::ostream& os, const XmlNode& node) -{ - return os << node.to_string(); -} diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp deleted file mode 100644 index f4b3948..0000000 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ /dev/null @@ -1,160 +0,0 @@ -#pragma once - - -#include <map> -#include <string> -#include <vector> -#include <memory> - -std::string xml_escape(const std::string& data); -std::string xml_unescape(const std::string& data); -std::string sanitize(const std::string& data, const std::string& encoding = "ISO-8859-1"); - -/** - * Represent an XML node. It has - * - A parent XML node (in the case of the first-level nodes, the parent is - nullptr) - * - zero, one or more children XML nodes - * - A name - * - A map of attributes - * - inner data (text inside the node) - * - tail data (text just after the node) - */ -class XmlNode -{ -public: - explicit XmlNode(const std::string& name, XmlNode* parent); - explicit XmlNode(const std::string& name); - /** - * The copy constructor does not copy the parent attribute. The children - * nodes are all copied recursively. - */ - XmlNode(const XmlNode& node): - name(node.name), - parent(nullptr), - attributes(node.attributes), - children{}, - inner(node.inner), - tail(node.tail) - { - for (const auto& child: node.children) - this->add_child(std::make_unique<XmlNode>(*child)); - } - - XmlNode(XmlNode&& node) = default; - XmlNode& operator=(const XmlNode&) = delete; - XmlNode& operator=(XmlNode&&) = delete; - - ~XmlNode() = default; - - void delete_all_children(); - void set_attribute(const std::string& name, const std::string& value); - /** - * Set the content of the tail, that is the text just after this node - */ - void set_tail(const std::string& data); - /** - * Append the given data to the content of the tail. This exists because - * the expat library may provide the complete text of an element in more - * than one call - */ - void add_to_tail(const std::string& data); - /** - * Set the content of the inner, that is the text inside this node. - */ - void set_inner(const std::string& data); - /** - * Append the given data to the content of the inner. For the reason - * described in add_to_tail comment. - */ - void add_to_inner(const std::string& data); - /** - * Get the content of inner - */ - std::string get_inner() const; - /** - * Get the content of the tail - */ - std::string get_tail() const; - /** - * Get a pointer to the first child element with that name and that xml namespace - */ - const XmlNode* get_child(const std::string& name, const std::string& xmlns) const; - /** - * Get a vector of all the children that have that name and that xml namespace. - */ - std::vector<const XmlNode*> get_children(const std::string& name, const std::string& xmlns) const; - /** - * Add a node child to this node. Assign this node to the child’s parent. - * Returns a pointer to the newly added child. - */ - XmlNode* add_child(std::unique_ptr<XmlNode> child); - XmlNode* add_child(XmlNode&& child); - XmlNode* add_child(const XmlNode& child); - /** - * Returns the last of the children. If the node doesn't have any child, - * the behaviour is undefined. The user should make sure this is the case - * by calling has_children() for example. - */ - XmlNode* get_last_child() const; - XmlNode* get_parent() const; - void set_name(const std::string& name); - void set_name(std::string&& name); - const std::string get_name() const; - /** - * Serialize the stanza into a string - */ - std::string to_string() const; - /** - * Whether or not this node has at least one child (if not, this is a leaf - * node) - */ - bool has_children() const; - /** - * Gets the value for the given attribute, returns an empty string if the - * node as no such attribute. - */ - const std::string& get_tag(const std::string& name) const; - /** - * Remove the attribute of the node. Does nothing if that attribute is not - * present. Returns true if the tag was removed, false if it was absent. - */ - bool del_tag(const std::string& name); - /** - * Use this to set an attribute's value, like node["id"] = "12"; - */ - std::string& operator[](const std::string& name); - -private: - std::string name; - XmlNode* parent; - std::map<std::string, std::string> attributes; - std::vector<std::unique_ptr<XmlNode>> children; - std::string inner; - std::string tail; -}; - -std::ostream& operator<<(std::ostream& os, const XmlNode& node); - -/** - * An XMPP stanza is just an XML node of level 2 in the XMPP document (the - * level 1 ones are the <stream::stream/>, and the ones above 2 are just the - * content of the stanzas) - */ -using Stanza = XmlNode; - -class XmlSubNode: public XmlNode -{ -public: - XmlSubNode(XmlNode& parent_ref, const std::string& name): - XmlNode(name), - parent_to_add(parent_ref) - {} - - ~XmlSubNode() - { - this->parent_to_add.add_child(std::move(*this)); - } -private: - XmlNode& parent_to_add; -};
\ No newline at end of file |