#pragma once #include <xmpp/adhoc_commands_handler.hpp> #include <network/tcp_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 TCPSocketHandler { 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 an error to indicate that the user tried to send a message to an * invalid user. */ void send_invalid_user_error(const std::string& user_name, 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 gateway disco informations. */ void send_self_disco_info(const std::string& id, 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; };