~singpolyma/biboumi

a418b6ed5d70f0e61e71bb1adce2a693ade89e30 — Florent Le Coz 9 years ago 4b76a30
Send and receive messages

Also correctly respond to PING with the id, escape some XML content, but not
always
M src/bridge/bridge.cpp => src/bridge/bridge.cpp +42 -1
@@ 2,6 2,8 @@
#include <xmpp/xmpp_component.hpp>
#include <network/poller.hpp>

#include <iostream>

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);
}


M src/bridge/bridge.hpp => src/bridge/bridge.hpp +10 -1
@@ 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


@@ 63,6 67,11 @@ private:
   */
  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.
   */

M src/irc/iid.cpp => src/irc/iid.cpp +4 -0
@@ 15,3 15,7 @@ Iid::Iid(const std::string& iid)
    }
  this->server = iid.substr(sep);
}

Iid::Iid()
{
}

M src/irc/iid.hpp => src/irc/iid.hpp +1 -0
@@ 20,6 20,7 @@ class Iid
{
public:
  explicit Iid(const std::string& iid);
  explicit Iid();

  std::string chan;
  std::string server;

M src/irc/irc_client.cpp => src/irc/irc_client.cpp +54 -5
@@ 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();
}

M src/irc/irc_client.hpp => src/irc/irc_client.hpp +30 -2
@@ 45,6 45,10 @@ public:
   */
  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
   * for send events to be ready)


@@ 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
   */


@@ 67,6 71,11 @@ public:
   */
  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
   */
  void forward_server_message(const IrcMessage& message);


@@ 81,6 90,10 @@ public:
   */
  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
   */
  void on_topic_received(const IrcMessage& message);


@@ 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:
  /**


@@ 100,14 117,25 @@ private:
   */
  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<std::string, std::unique_ptr<IrcChannel>> 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<std::string> channels_to_join;
  bool welcomed;

  IrcClient(const IrcClient&) = delete;
  IrcClient(IrcClient&&) = delete;

D src/xmpp/stanza.hpp => src/xmpp/stanza.hpp +0 -18
@@ 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



M src/xmpp/xmpp_component.cpp => src/xmpp/xmpp_component.cpp +30 -0
@@ 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);
}

M src/xmpp/xmpp_component.hpp => src/xmpp/xmpp_component.hpp +5 -0
@@ 72,10 72,15 @@ public:
   */
  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:
  /**

M src/xmpp/xmpp_stanza.cpp => src/xmpp/xmpp_stanza.cpp +36 -1
@@ 2,6 2,26 @@

#include <iostream>

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 += "&amp;";       break;
        case '\"': buffer += "&quot;";      break;
        case '\'': buffer += "&apos;";      break;
        case '<':  buffer += "&lt;";        break;
        case '>':  buffer += "&gt;";        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)

M src/xmpp/xmpp_stanza.hpp => src/xmpp/xmpp_stanza.hpp +12 -0
@@ 7,6 7,8 @@

#include <expatpp.h>

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;