From a418b6ed5d70f0e61e71bb1adce2a693ade89e30 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 7 Nov 2013 01:53:09 +0100 Subject: Send and receive messages Also correctly respond to PING with the id, escape some XML content, but not always --- src/bridge/bridge.cpp | 43 ++++++++++++++++++++++++++++++++- src/bridge/bridge.hpp | 11 ++++++++- src/irc/iid.cpp | 4 +++ src/irc/iid.hpp | 1 + src/irc/irc_client.cpp | 59 +++++++++++++++++++++++++++++++++++++++++---- src/irc/irc_client.hpp | 32 ++++++++++++++++++++++-- src/xmpp/stanza.hpp | 18 -------------- src/xmpp/xmpp_component.cpp | 30 +++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 5 ++++ src/xmpp/xmpp_stanza.cpp | 37 +++++++++++++++++++++++++++- src/xmpp/xmpp_stanza.hpp | 12 +++++++++ 11 files changed, 224 insertions(+), 28 deletions(-) delete mode 100644 src/xmpp/stanza.hpp (limited to 'src') diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 638777d..5047a78 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -2,6 +2,8 @@ #include #include +#include + Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller): user_jid(user_jid), xmpp(xmpp), @@ -29,15 +31,54 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname, const std::string } } +IrcClient* Bridge::get_irc_client(const std::string& hostname) +{ + try + { + return this->irc_clients.at(hostname).get(); + } + catch (const std::out_of_range& exception) + { + return nullptr; + } +} + + void Bridge::join_irc_channel(const Iid& iid, const std::string& username) { IrcClient* irc = this->get_irc_client(iid.server, username); irc->send_join_command(iid.chan); } +void Bridge::send_channel_message(const Iid& iid, const std::string& body) +{ + if (iid.chan.empty() || iid.server.empty()) + { + std::cout << "Cannot send message to channel: [" << iid.chan << "] on server [" << iid.server << "]" << std::endl; + return; + } + IrcClient* irc = this->get_irc_client(iid.server); + if (!irc) + { + std::cout << "Cannot send message: no client exist for server " << iid.server << std::endl; + return; + } + irc->send_channel_message(iid.chan, body); + this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), body, this->user_jid); +} + +void Bridge::send_muc_message(const Iid& iid, const std::string& nick, const std::string& body) +{ + this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, body, this->user_jid); +} + void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg) { - const std::string body = std::string("[") + author + std::string("] ") + msg; + std::string body; + if (!author.empty()) + body = std::string("[") + author + std::string("] ") + msg; + else + body = msg; this->xmpp->send_message(from, body, this->user_jid); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index f9ddcca..38cf565 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -30,6 +30,7 @@ public: **/ void join_irc_channel(const Iid& iid, const std::string& username); + void send_channel_message(const Iid& iid, const std::string& body); /*** ** @@ -54,7 +55,10 @@ public: * Send the topic of the MUC to the user */ void send_topic(const std::string& hostname, const std::string& chan_name, const std::string topic); - + /** + * Send a MUC message from some participant + */ + void send_muc_message(const Iid& iid, const std::string& nick, const std::string& body); private: /** * Returns the client for the given hostname, create one (and use the @@ -62,6 +66,11 @@ private: * client immediately. */ IrcClient* get_irc_client(const std::string& hostname, const std::string& username); + /** + * This version does not create the IrcClient if it does not exist, and + * returns nullptr in that case + */ + IrcClient* get_irc_client(const std::string& hostname); /** * The JID of the user associated with this bridge. Messages from/to this * JID are only managed by this bridge. diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index ffc8d88..4694c0c 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -15,3 +15,7 @@ Iid::Iid(const std::string& iid) } this->server = iid.substr(sep); } + +Iid::Iid() +{ +} diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index aacc9e6..a62ac71 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -20,6 +20,7 @@ class Iid { public: explicit Iid(const std::string& iid); + explicit Iid(); std::string chan; std::string server; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 7875b1c..cf57bd7 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -12,7 +12,9 @@ IrcClient::IrcClient(const std::string& hostname, const std::string& username, Bridge* bridge): hostname(hostname), username(username), - bridge(bridge) + current_nick(username), + bridge(bridge), + welcomed(false) { std::cout << "IrcClient()" << std::endl; } @@ -51,6 +53,11 @@ IrcChannel* IrcClient::get_channel(const std::string& name) } } +std::string IrcClient::get_own_nick() const +{ + return this->current_nick; +} + void IrcClient::parse_in_buffer() { while (true) @@ -63,19 +70,23 @@ void IrcClient::parse_in_buffer() std::cout << message << std::endl; // TODO map function and command name properly if (message.command == "PING") - this->send_pong_command(); + this->send_pong_command(message); else if (message.command == "NOTICE" || message.command == "375" || message.command == "372") this->forward_server_message(message); else if (message.command == "JOIN") this->on_self_channel_join(message); + else if (message.command == "PRIVMSG") + this->on_channel_message(message); else if (message.command == "353") this->set_and_forward_user_list(message); else if (message.command == "332") this->on_topic_received(message); else if (message.command == "366") this->on_channel_completely_joined(message); + else if (message.command == "001") + this->on_welcome_message(message); } } @@ -102,7 +113,7 @@ void IrcClient::send_message(IrcMessage&& message) void IrcClient::send_user_command(const std::string& username, const std::string& realname) { - this->send_message(IrcMessage("USER", {username, "NONE", "NONE", realname})); + this->send_message(IrcMessage("USER", {username, "ignored", "ignored", realname})); } void IrcClient::send_nick_command(const std::string& nick) @@ -112,14 +123,32 @@ void IrcClient::send_nick_command(const std::string& nick) void IrcClient::send_join_command(const std::string& chan_name) { + if (this->welcomed == false) + { + this->channels_to_join.push_back(chan_name); + return ; + } IrcChannel* channel = this->get_channel(chan_name); if (channel->joined == false) this->send_message(IrcMessage("JOIN", {chan_name})); } -void IrcClient::send_pong_command() +bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body) { - this->send_message(IrcMessage("PONG", {})); + IrcChannel* channel = this->get_channel(chan_name); + if (channel->joined == false) + { + std::cout << "Cannot send message to channel " << chan_name << ", it is not joined" << std::endl; + return false; + } + this->send_message(IrcMessage("PRIVMSG", {chan_name, body})); + return true; +} + +void IrcClient::send_pong_command(const IrcMessage& message) +{ + const std::string id = message.arguments[0]; + this->send_message(IrcMessage("PONG", {id})); } void IrcClient::forward_server_message(const IrcMessage& message) @@ -154,6 +183,17 @@ void IrcClient::on_self_channel_join(const IrcMessage& message) channel->set_self(message.prefix); } +void IrcClient::on_channel_message(const IrcMessage& message) +{ + const IrcUser user(message.prefix); + const std::string nick = user.nick; + Iid iid; + iid.chan = message.arguments[0]; + iid.server = this->hostname; + const std::string body = message.arguments[1]; + this->bridge->send_muc_message(iid, nick, body); +} + void IrcClient::on_topic_received(const IrcMessage& message) { const std::string chan_name = message.arguments[1]; @@ -168,3 +208,12 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) this->bridge->send_self_join(this->hostname, chan_name, channel->get_self()->nick); this->bridge->send_topic(this->hostname, chan_name, channel->topic); } + +void IrcClient::on_welcome_message(const IrcMessage& message) +{ + this->current_nick = message.arguments[0]; + this->welcomed = true; + for (const std::string& chan_name: this->channels_to_join) + this->send_join_command(chan_name); + this->channels_to_join.clear(); +} diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index db1b83b..50f3781 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -44,6 +44,10 @@ public: * Return the channel with this name, create it if it does not yet exist */ IrcChannel* get_channel(const std::string& name); + /** + * Return our own nick + */ + std::string get_own_nick() const; /** * Serialize the given message into a line, and send that into the socket * (actually, into our out_buf and signal the poller that we want to wach @@ -53,7 +57,7 @@ public: /** * Send the PONG irc command */ - void send_pong_command(); + void send_pong_command(const IrcMessage& message); /** * Send the USER irc command */ @@ -66,6 +70,11 @@ public: * Send the JOIN irc command */ void send_join_command(const std::string& chan_name); + /** + * Send a PRIVMSG command for a channel + * Return true if the message was actually sent + */ + bool send_channel_message(const std::string& chan_name, const std::string& body); /** * Forward the server message received from IRC to the XMPP component */ @@ -80,6 +89,10 @@ public: * of user comes after so we do not send the self-presence over XMPP yet. */ void on_self_channel_join(const IrcMessage& message); + /** + * When a channel message is received + */ + void on_channel_message(const IrcMessage& message); /** * Save the topic in the IrcChannel */ @@ -89,6 +102,10 @@ public: * received etc), send the self presence and topic to the XMPP user. */ void on_channel_completely_joined(const IrcMessage& message); + /** + * When a message 001 is received, join the rooms we wanted to join, and set our actual nickname + */ + void on_welcome_message(const IrcMessage& message); private: /** @@ -99,15 +116,26 @@ private: * The user name used in the USER irc command */ const std::string username; + /** + * Our current nickname on the server + */ + std::string current_nick; /** * Raw pointer because the bridge owns us. */ Bridge* bridge; - /** * The list of joined channels, indexed by name */ std::unordered_map> channels; + /** + * A list of chan we want to join, but we need a response 001 from + * the server before sending the actual JOIN commands. So we just keep the + * channel names in a list, and send the JOIN commands for each of them + * whenever the WELCOME message is received. + */ + std::vector channels_to_join; + bool welcomed; IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; diff --git a/src/xmpp/stanza.hpp b/src/xmpp/stanza.hpp deleted file mode 100644 index 697bda4..0000000 --- a/src/xmpp/stanza.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef Stanza -# define Stanza - -class Stanza -{ -public: - explicit Stanza(); - ~Stanza(); -private: - Stanza(const Stanza&) = delete; - Stanza(Stanza&&) = delete; - Stanza& operator=(const Stanza&) = delete; - Stanza& operator=(Stanza&&) = delete; -}; - -#endif // Stanza - - diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 3a288c7..cd9cd6f 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -25,6 +25,8 @@ XmppComponent::XmppComponent(const std::string& hostname, const std::string& sec std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1)); this->stanza_handlers.emplace("presence", std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1)); + this->stanza_handlers.emplace("message", + std::bind(&XmppComponent::handle_message, this,std::placeholders::_1)); } XmppComponent::~XmppComponent() @@ -151,6 +153,20 @@ void XmppComponent::handle_presence(const Stanza& stanza) bridge->join_irc_channel(iid, to.resource); } +void XmppComponent::handle_message(const Stanza& stanza) +{ + Bridge* bridge = this->get_user_bridge(stanza["from"]); + Jid to(stanza["to"]); + Iid iid(to.local); + XmlNode* body = stanza.get_child("body"); + if (stanza["type"] == "groupchat") + { + if (to.resource.empty()) + if (body && !body->get_inner().empty()) + bridge->send_channel_message(iid, body->get_inner()); + } +} + Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) { try @@ -239,3 +255,17 @@ void XmppComponent::send_topic(const std::string& from, const std::string& topic message.close(); this->send_stanza(message); } + +void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, const std::string body_str, const std::string& jid_to) +{ + Stanza message("message"); + message["to"] = jid_to; + message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + message["type"] = "groupchat"; + XmlNode body("body"); + body.set_inner(body_str); + body.close(); + message.add_child(std::move(body)); + message.close(); + this->send_stanza(message); +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 725b495..73eadd2 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -71,11 +71,16 @@ public: * Send the MUC topic to the user */ void send_topic(const std::string& from, const std::string& topic, const std::string& to); + /** + * Send a (non-private) message to the MUC + */ + void send_muc_message(const std::string& muc_name, const std::string& nick, const std::string body_str, const std::string& jid_to); /** * Handle the various stanza types */ void handle_handshake(const Stanza& stanza); void handle_presence(const Stanza& stanza); + void handle_message(const Stanza& stanza); private: /** diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 2c98acc..4c0088e 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -2,6 +2,26 @@ #include +std::string xml_escape(const std::string& data) +{ + std::string res; + buffer.reserve(data.size()); + for(size_t pos = 0; pos != data.size(); ++pos) + { + switch(data[pos]) + { + case '&': buffer += "&"; break; + case '\"': buffer += """; break; + case '\'': buffer += "'"; break; + case '<': buffer += "<"; break; + case '>': buffer += ">"; break; + default: buffer += data[pos]; break; + } + } + return buffer; +} + + XmlNode::XmlNode(const std::string& name, XmlNode* parent): name(name), parent(parent), @@ -40,7 +60,22 @@ void XmlNode::set_tail(const std::string& data) void XmlNode::set_inner(const std::string& data) { - this->inner = data; + this->inner = xml_escape(data); +} + +std::string XmlNode::get_inner() const +{ + return this->inner; +} + +XmlNode* XmlNode::get_child(const std::string& name) const +{ + for (auto& child: this->children) + { + if (child->name == name) + return child; + } + return nullptr; } void XmlNode::add_child(XmlNode* child) diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index 277b0db..62f152d 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -7,6 +7,8 @@ #include +std::string xml_escape(const std::string& data); + /** * Raised on operator[] when the attribute does not exist */ @@ -51,8 +53,18 @@ public: void set_tail(const std::string& data); /** * Set the content of the inner, that is the text inside this node + * TODO: escape it here. */ void set_inner(const std::string& data); + /** + * Get the content of inner + * TODO: unescape it here. + */ + std::string get_inner() const; + /** + * Get a pointer to the first child element with that name + */ + XmlNode* get_child(const std::string& name) const; void add_child(XmlNode* child); void add_child(XmlNode&& child); XmlNode* get_last_child() const; -- cgit v1.2.3