~singpolyma/biboumi

0d2dd71de5292895f69d5f08b000e03e928bdd34 — louiz’ 6 years ago 183f53d
Don’t use ! as the separator for nicknames, use % instead

It’s now easier to use. The distinction between a nick and a channel name is
based on the first character (by default it's '#' and '&'). The user doesn’t
have to worry about which separator to use anymore.

fix #3066
M src/bridge/bridge.cpp => src/bridge/bridge.cpp +17 -9
@@ 133,7 133,7 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname)
    }
}

IrcClient* Bridge::find_irc_client(const std::string& hostname)
IrcClient* Bridge::find_irc_client(const std::string& hostname) const
{
  try
    {


@@ 470,7 470,7 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s
                                        const std::string& iq_id, const std::string& to_jid,
                                        const std::string& from_jid)
{
  Iid iid(nick + "!" + irc_hostname);
  Iid iid(nick, irc_hostname, Iid::Type::User);
  this->send_private_message(iid, "\01PING " + iq_id + "\01");

  irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool


@@ 541,7 541,7 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std
                                      const std::string& iq_id, const std::string& to_jid,
                                      const std::string& from_jid)
{
  Iid iid(target + "!" + irc_hostname);
  Iid iid(target, irc_hostname, Iid::Type::User);
  this->send_private_message(iid, "\01VERSION\01");
  // TODO, add a timer to remove that waiting iq if the server does not
  // respond with a matching command before n seconds


@@ 590,7 590,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st
      const auto it = this->preferred_user_from.find(iid.get_local());
      if (it != this->preferred_user_from.end())
        {
          const auto chan_name = Iid(Jid(it->second).local).get_local();
          const auto chan_name = Iid(Jid(it->second).local, {}).get_local();
          for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, iid.get_server()}])
            this->xmpp.send_message(it->second, this->make_xmpp_body(body, encoding),
                                    this->user_jid + "/" + resource, "chat", true);


@@ 653,7 653,7 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho
  else
    body = msg;

  const auto encoding = in_encoding_for(*this, {from});
  const auto encoding = in_encoding_for(*this, {from, this});
  for (const auto& resource: this->resources_in_server[from])
    {
        this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat");


@@ 696,7 696,7 @@ void Bridge::send_topic(const std::string& hostname, const std::string& chan_nam
{
  std::string encoded_chan_name(chan_name);
  xep0106::encode(encoded_chan_name);
  const auto encoding = in_encoding_for(*this, {encoded_chan_name + '%' + hostname});
  const auto encoding = in_encoding_for(*this, {encoded_chan_name, hostname, Iid::Type::Channel});
  this->xmpp.send_topic(encoded_chan_name + utils::empty_if_fixed_server(
      "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who);



@@ 741,7 741,7 @@ void Bridge::send_iq_version_request(const std::string& nick, const std::string&
{
  const auto resources = this->resources_in_server[hostname];
  if (resources.begin() != resources.end())
    this->xmpp.send_iq_version_request(utils::tolower(nick) + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin());
    this->xmpp.send_iq_version_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin());
}

void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname,


@@ 753,7 753,7 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& 
  // Forward to the first resource (arbitrary, based on the “order” of the std::set) only
  const auto resources = this->resources_in_server[hostname];
  if (resources.begin() != resources.end())
    this->xmpp.send_ping_request(utils::tolower(nick) + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id));
    this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id));
}

void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid)


@@ 776,7 776,7 @@ void Bridge::remove_all_preferred_from_jid_of_room(const std::string& channel_na
{
  for (auto it = this->preferred_user_from.begin(); it != this->preferred_user_from.end();)
    {
      Iid iid(Jid(it->second).local);
      Iid iid(Jid(it->second).local, {});
      if (iid.get_local() == channel_name)
        it = this->preferred_user_from.erase(it);
      else


@@ 806,6 806,14 @@ std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_cli
  return this->irc_clients;
}

std::set<char> Bridge::get_chantypes(const std::string& hostname) const
{
  IrcClient* irc = this->find_irc_client(hostname);
  if (!irc)
    return {'#', '&'};
  return irc->get_chantypes();
}

void Bridge::add_resource_to_chan(const Bridge::ChannelKey& channel, const std::string& resource)
{
  auto it = this->resources_in_chan.find(channel);

M src/bridge/bridge.hpp => src/bridge/bridge.hpp +2 -1
@@ 201,6 201,7 @@ public:
   */
  void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message);
  std::unordered_map<std::string, std::shared_ptr<IrcClient>>& get_irc_clients();
  std::set<char> get_chantypes(const std::string& hostname) const;

private:
  /**


@@ 217,7 218,7 @@ private:
  /**
   * Idem, but returns nullptr if the server does not exist.
   */
  IrcClient* find_irc_client(const std::string& hostname);
  IrcClient* find_irc_client(const std::string& hostname) const;
  /**
   * The bare 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 +54 -55
@@ 1,62 1,70 @@
#include <utils/tolower.hpp>
#include <config/config.hpp>

#include <bridge/bridge.hpp>
#include <irc/iid.hpp>

#include <utils/encoding.hpp>

Iid::Iid(const std::string& iid):
  is_channel(false),
  is_user(false)
Iid::Iid(const std::string local, const std::string server, Iid::Type type):
        type(type),
        local(local),
        server(server)
{
  const std::string fixed_irc_server = Config::get("fixed_irc_server", "");
  if (fixed_irc_server.empty())
    this->init(iid);
  else
    this->init_with_fixed_server(iid, fixed_irc_server);
}

Iid::Iid(const std::string& iid, const std::set<char>& chantypes)
{
  this->init(iid);
  this->set_type(std::set<char>(chantypes));
}

void Iid::init(const std::string& iid)
Iid::Iid(const std::string& iid, const std::initializer_list<char>& chantypes):
    Iid(iid, std::set<char>(chantypes))
{
  const std::string::size_type sep = iid.find_first_of("%!");
  if (sep != std::string::npos)
    {
      if (iid[sep] == '%')
        this->is_channel = true;
      else
        this->is_user = true;
      this->set_local(iid.substr(0, sep));
      this->set_server(iid.substr(sep + 1));
    }
  else
    this->set_server(iid);
}

void Iid::init_with_fixed_server(const std::string& iid, const std::string& hostname)
Iid::Iid(const std::string& iid, const Bridge *bridge)
{
  this->init(iid);
  const auto chantypes = bridge->get_chantypes(this->server);
  this->set_type(chantypes);
}

void Iid::set_type(const std::set<char>& chantypes)
{
  this->set_server(hostname);
  if (this->local.empty())
    return;

  const std::string::size_type sep = iid.find("!");
  if (chantypes.count(this->local[0]) == 1)
    this->type = Iid::Type::Channel;
  else
    this->type = Iid::Type::User;
}

  // Without any separator, we consider that it's a channel
  if (sep == std::string::npos)
void Iid::init(const std::string& iid)
{
  const std::string fixed_irc_server = Config::get("fixed_irc_server", "");

  if (fixed_irc_server.empty())
  {
    const std::string::size_type sep = iid.find('%');
    if (sep != std::string::npos)
    {
      this->is_channel = true;
      this->set_local(iid);
      this->set_local(iid.substr(0, sep));
      this->set_server(iid.substr(sep + 1));
      this->type = Iid::Type::Channel;
    }
  else // A separator can be present to differenciate a channel from a user,
       // but the part behind it (the hostname) is ignored
    else
    {
      this->set_local(iid.substr(0, sep));
        this->is_user = true;
      this->set_server(iid);
      this->type = Iid::Type::Server;
    }
}

Iid::Iid():
  is_channel(false),
  is_user(false)
{
  }
  else
  {
    this->set_server(fixed_irc_server);
    this->set_local(iid);
  }
}

void Iid::set_local(const std::string& loc)


@@ 88,27 96,18 @@ const std::string& Iid::get_server() const
  return this->server;
}

std::string Iid::get_sep() const
{
  if (this->is_channel)
    return "%";
  else if (this->is_user)
    return "!";
  return "";
}

namespace std {
  const std::string to_string(const Iid& iid)
  {
    if (Config::get("fixed_irc_server", "").empty())
      return iid.get_encoded_local() + iid.get_sep() + iid.get_server();
    {
      if (iid.type == Iid::Type::Server)
        return iid.get_server();
      else
        return iid.get_encoded_local() + iid.separator + iid.get_server();
    }
    else
      {
        if (iid.get_sep() == "!")
          return iid.get_encoded_local() + iid.get_sep();
        else
          return iid.get_encoded_local();
      }
      return iid.get_encoded_local();
  }
}


M src/irc/iid.hpp => src/irc/iid.hpp +36 -22
@@ 2,48 2,64 @@


#include <string>
#include <set>

class Bridge;

/**
 * A name representing an IRC channel on an IRC server, or an IRC user on an
 * IRC server, or just an IRC server.
 *
 * The separator for an user is '!', for a channel it's '%'. If no separator
 * is present, it's just an irc server.
 * The separator is '%' between the local part (nickname or channel) and the
 * server part. If no separator is present, it's just an irc server.
 * If it is present, the first character of the local part determines if it’s
 * a channel or a user: ff the local part is empty or if its first character
 * is part of the chantypes characters, then it’s a channel, otherwise it’s
 * a user.
 *
 * It’s possible to have an empty-string server, but it makes no sense in
 * the biboumi context.
 * biboumi’s context.
 *
 * Assuming the chantypes are '#' and '&':
 *
 * #test%irc.example.org has :
 * - local: "#test" (the # is part of the name, it could very well be absent, or & (for example) instead)
 * - server: "irc.example.org"
 * - is_channel: true
 * - is_user: false
 * - type: channel
 *
 * %irc.example.org:
 * - local: ""
 * - server: "irc.example.org"
 * - is_channel: true
 * - is_user: false
 * Note: this is the special empty-string channel, used internal in biboumi
 * - type: channel
 * Note: this is the special empty-string channel, used internally in biboumi
 * but has no meaning on IRC.
 *
 * foo!irc.example.org
 * foo%irc.example.org
 * - local: "foo"
 * - server: "irc.example.org"
 * - is_channel: false
 * - is_user: true
 * Note: the empty-string user (!irc.example.org) has no special meaning in biboumi
 * - type: user
 * Note: the empty-string user (!irc.example.org) makes no sense for biboumi
 *
 * irc.example.org:
 * - local: ""
 * - server: "irc.example.org"
 * - is_channel: false
 * - is_user: false
 * - type: server
 */
class Iid
{
public:
  Iid(const std::string& iid);
  Iid();
  enum class Type
  {
      Channel,
      User,
      Server,
  };
  static constexpr auto separator = "%";
  Iid(const std::string& iid, const std::set<char>& chantypes);
  Iid(const std::string& iid, const std::initializer_list<char>& chantypes);
  Iid(const std::string& iid, const Bridge* bridge);
  Iid(const std::string local, const std::string server, Type type);
  Iid() = default;
  Iid(const Iid&) = default;

  Iid(Iid&&) = delete;


@@ 52,21 68,19 @@ public:

  void set_local(const std::string& loc);
  void set_server(const std::string& serv);

  const std::string& get_local() const;
  const std::string get_encoded_local() const;
  const std::string& get_server() const;

  bool is_channel;
  bool is_user;

  std::string get_sep() const;

  std::tuple<std::string, std::string> to_tuple() const;

  Type type { Type::Server };

private:

  void init(const std::string& iid);
  void init_with_fixed_server(const std::string& iid, const std::string& hostname);
  void set_type(const std::set<char>& chantypes);

  std::string local;
  std::string server;

M src/irc/irc_client.cpp => src/irc/irc_client.cpp +13 -13
@@ 213,7 213,7 @@ void IrcClient::on_connection_failed(const std::string& reason)
      // Send an error message for all room that the user wanted to join
      for (const auto& tuple: this->channels_to_join)
        {
          Iid iid(std::get<0>(tuple) + "%" + this->hostname);
          Iid iid(std::get<0>(tuple) + "%" + this->hostname, this->chantypes);
          this->bridge.send_presence_error(iid, this->current_nick,
                                            "cancel", "item-not-found",
                                            "", reason);


@@ 551,7 551,7 @@ void IrcClient::on_notice(const IrcMessage& message)
      if (this->nicks_to_treat_as_private.find(nick) !=
          this->nicks_to_treat_as_private.end())
        { // We previously sent a message to that nick)
          this->bridge.send_message({nick + "!" + this->hostname}, nick, body,
          this->bridge.send_message({nick, this->hostname, Iid::Type::User}, nick, body,
                                     false);
        }
      else


@@ 663,12 663,12 @@ void IrcClient::on_channel_message(const IrcMessage& message)
  bool muc = true;
  if (!this->get_channel(iid.get_local())->joined)
    {
      iid.is_user = true;
      iid.type = Iid::Type::User;
      iid.set_local(nick);
      muc = false;
    }
  else
    iid.is_channel = true;
    iid.type = Iid::Type::Channel;
  if (!body.empty() && body[0] == '\01')
    {
      if (body.substr(1, 6) == "ACTION")


@@ 780,7 780,7 @@ void IrcClient::on_nickname_conflict(const IrcMessage& message)
    Iid iid;
    iid.set_local(it->first);
    iid.set_server(this->hostname);
    iid.is_channel = true;
    iid.type = Iid::Type::Channel;
    this->bridge.send_nickname_conflict_error(iid, nickname);
  }
}


@@ 797,7 797,7 @@ void IrcClient::on_nickname_change_too_fast(const IrcMessage& message)
    Iid iid;
    iid.set_local(it->first);
    iid.set_server(this->hostname);
    iid.is_channel = true;
    iid.type = Iid::Type::Channel;
    this->bridge.send_presence_error(iid, nickname,
                                      "cancel", "not-acceptable",
                                      "", txt);


@@ 858,7 858,7 @@ void IrcClient::on_part(const IrcMessage& message)
      Iid iid;
      iid.set_local(chan_name);
      iid.set_server(this->hostname);
      iid.is_channel = true;
      iid.type = Iid::Type::Channel;
      bool self = channel->get_self()->nick == nick;
      if (self)
      {


@@ 880,7 880,7 @@ void IrcClient::on_error(const IrcMessage& message)
    Iid iid;
    iid.set_local(it->first);
    iid.set_server(this->hostname);
    iid.is_channel = true;
    iid.type = Iid::Type::Channel;
    IrcChannel* channel = it->second.get();
    if (!channel->joined)
      continue;


@@ 908,7 908,7 @@ void IrcClient::on_quit(const IrcMessage& message)
          Iid iid;
          iid.set_local(chan_name);
          iid.set_server(this->hostname);
          iid.is_channel = true;
          iid.type = Iid::Type::Channel;
          this->bridge.send_muc_leave(std::move(iid), std::move(nick), txt, false);
        }
    }


@@ 928,7 928,7 @@ void IrcClient::on_nick(const IrcMessage& message)
          Iid iid;
          iid.set_local(chan_name);
          iid.set_server(this->hostname);
          iid.is_channel = true;
          iid.type = Iid::Type::Channel;
          const bool self = channel->get_self()->nick == old_nick;
          const char user_mode = user->get_most_significant_mode(this->sorted_user_modes);
          this->bridge.send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self);


@@ 956,7 956,7 @@ void IrcClient::on_kick(const IrcMessage& message)
  Iid iid;
  iid.set_local(chan_name);
  iid.set_server(this->hostname);
  iid.is_channel = true;
  iid.type = Iid::Type::Channel;
  this->bridge.kick_muc_user(std::move(iid), target, reason, author.nick);
}



@@ 976,7 976,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message)
  Iid iid;
  iid.set_local(message.arguments[0]);
  iid.set_server(this->hostname);
  iid.is_channel = true;
  iid.type = Iid::Type::Channel;
  IrcUser user(message.prefix);
  std::string mode_arguments;
  for (size_t i = 1; i < message.arguments.size(); ++i)


@@ 1105,7 1105,7 @@ void IrcClient::leave_dummy_channel(const std::string& exit_message)
  this->dummy_channel.joined = false;
  this->dummy_channel.joining = false;
  this->dummy_channel.remove_all_users();
  this->bridge.send_muc_leave(Iid("%"s + this->hostname), std::string(this->current_nick), exit_message, true);
  this->bridge.send_muc_leave(Iid("%"s + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true);
}

#ifdef BOTAN_FOUND

M src/irc/irc_client.hpp => src/irc/irc_client.hpp +2 -1
@@ 280,8 280,9 @@ public:

  const Resolver& get_resolver() const { return this->dns_resolver; }

  const std::vector<char>& get_sorted_user_modes() const { return sorted_user_modes; }
  const std::vector<char>& get_sorted_user_modes() const { return this->sorted_user_modes; }

  std::set<char> get_chantypes() const { return this->chantypes; }
private:
  /**
   * The hostname of the server we are connected to.

M src/xmpp/biboumi_adhoc_commands.cpp => src/xmpp/biboumi_adhoc_commands.cpp +2 -2
@@ 381,7 381,7 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co
{
  const Jid owner(session.get_owner_jid());
  const Jid target(session.get_target_jid());
  const Iid iid(target.local);
  const Iid iid(target.local, {});
  auto options = Database::get_irc_channel_options_with_server_default(owner.local + "@" + owner.domain,
                                                                       iid.get_server(), iid.get_local());



@@ 434,7 434,7 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co
    {
      const Jid owner(session.get_owner_jid());
      const Jid target(session.get_target_jid());
      const Iid iid(target.local);
      const Iid iid(target.local, {});
      auto options = Database::get_irc_channel_options(owner.local + "@" + owner.domain,
                                                       iid.get_server(), iid.get_local());
      for (const XmlNode* field: x->get_children("field", "jabber:x:data"))

M src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +31 -30
@@ 126,7 126,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
  Bridge* bridge = this->get_user_bridge(from_str);
  Jid to(to_str);
  Jid from(from_str);
  Iid iid(to.local);
  Iid iid(to.local, bridge);

  // An error stanza is sent whenever we exit this function without
  // disabling this scopeguard.  If error_type and error_name are not


@@ 142,7 142,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
        });

  try {
  if (iid.is_channel && !iid.get_server().empty())
  if (iid.type == Iid::Type::Channel && !iid.get_server().empty())
    { // presence toward a MUC that corresponds to an irc channel, or a
      // dummy channel if iid.chan is empty
      if (type.empty())


@@ 191,7 191,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
    type = "normal";
  Bridge* bridge = this->get_user_bridge(from);
  Jid to(to_str);
  Iid iid(to.local);
  Iid iid(to.local, bridge);

  std::string error_type("cancel");
  std::string error_name("internal-server-error");


@@ 202,7 202,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
  const XmlNode* body = stanza.get_child("body", COMPONENT_NS);

  try {                         // catch IRCNotConnected exceptions
  if (type == "groupchat" && iid.is_channel)
  if (type == "groupchat" && iid.type == Iid::Type::Channel)
    {
      if (body && !body->get_inner().empty())
        {


@@ 234,27 234,27 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
      if (body && !body->get_inner().empty())
        {
          // a message for nick!server
          if (iid.is_user && !iid.get_local().empty())
          if (iid.type == Iid::Type::User && !iid.get_local().empty())
            {
              bridge->send_private_message(iid, body->get_inner());
              bridge->remove_preferred_from_jid(iid.get_local());
            }
          else if (!iid.is_user && !to.resource.empty())
          else if (iid.type != Iid::Type::User && !to.resource.empty())
            { // a message for chan%server@biboumi/Nick or
              // server@biboumi/Nick
              // Convert that into a message to nick!server
              Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server());
              Iid user_iid(utils::tolower(to.resource), iid.get_server(), Iid::Type::User);
              bridge->send_private_message(user_iid, body->get_inner());
              bridge->set_preferred_from_jid(user_iid.get_local(), to_str);
            }
          else if (!iid.is_user && !iid.is_channel)
          else if (iid.type == Iid::Type::Server)
            { // Message sent to the server JID
              // Convert the message body into a raw IRC message
              bridge->send_raw_message(iid.get_server(), body->get_inner());
            }
        }
    }
  else if (iid.is_user)
  else if (iid.type == Iid::Type::User)
    this->send_invalid_user_error(to.local, from);
  } catch (const IRCNotConnected& ex)
    {


@@ 321,7 321,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
              std::string affiliation = child->get_tag("affiliation");
              if (!nick.empty())
                {
                  Iid iid(to.local);
                  Iid iid(to.local, {});
                  if (role == "none")
                    {               // This is a kick
                      std::string reason;


@@ 345,15 345,17 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)

          // Depending on the 'to' jid in the request, we use one adhoc
          // command handler or an other
          Iid iid(to.local);
          Iid iid(to.local, {});
          AdhocCommandsHandler* adhoc_handler;
          if (!to.local.empty() && !iid.is_user && !iid.is_channel)
            adhoc_handler = &this->irc_server_adhoc_commands_handler;
          else if (!to.local.empty() && iid.is_channel)
            adhoc_handler = &this->irc_channel_adhoc_commands_handler;
          else
          if (to.local.empty())
            adhoc_handler = &this->adhoc_commands_handler;

          else
          {
            if (iid.type == Iid::Type::Server)
              adhoc_handler = &this->irc_server_adhoc_commands_handler;
            else
              adhoc_handler = &this->irc_channel_adhoc_commands_handler;
          }
          // Execute the command, if any, and get a result XmlNode that we
          // insert in our response
          XmlNode inner_node = adhoc_handler->handle_request(from, to_str, *query);


@@ 384,13 386,12 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
        }
      else if ((query = stanza.get_child("query", VERSION_NS)))
        {
          Iid iid(to.local);
          if (iid.is_user ||
              (iid.is_channel && !to.resource.empty()))
          Iid iid(to.local, bridge);
          if (iid.type != Iid::Type::Server && !to.resource.empty())
            {
              // Get the IRC user version
              std::string target;
              if (iid.is_user)
              if (iid.type == Iid::Type::User)
                target = iid.get_local();
              else
                target = to.resource;


@@ 406,7 407,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
        }
      else if ((query = stanza.get_child("query", DISCO_ITEMS_NS)))
        {
          Iid iid(to.local);
          Iid iid(to.local, bridge);
          const std::string node = query->get_tag("node");
          if (node == ADHOC_NS)
            {


@@ 419,7 420,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
                                                 this->adhoc_commands_handler);
                  stanza_error.disable();
                }
              else if (!iid.is_user && !iid.is_channel)
              else if (iid.type == Iid::Type::Server)
                {               // Get the server's adhoc commands
                  this->send_adhoc_commands_list(id, from, to_str,
                                                 (Config::get("admin", "") ==


@@ 427,7 428,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
                                                 this->irc_server_adhoc_commands_handler);
                  stanza_error.disable();
                }
              else if (!iid.is_user && iid.is_channel)
              else if (iid.type == Iid::Type::Channel)
                {               // Get the channel's adhoc commands
                  this->send_adhoc_commands_list(id, from, to_str,
                                                 (Config::get("admin", "") ==


@@ 436,7 437,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
                  stanza_error.disable();
                }
            }
          else if (node.empty() && !iid.is_user && !iid.is_channel)
          else if (node.empty() && iid.type == Iid::Type::Server)
            { // Disco on an IRC server: get the list of channels
              bridge->send_irc_channel_list_request(iid, id, from);
              stanza_error.disable();


@@ 444,13 445,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
        }
      else if ((query = stanza.get_child("ping", PING_NS)))
        {
          Iid iid(to.local);
          if (iid.is_user)
          Iid iid(to.local, bridge);
          if (iid.type == Iid::Type::User)
            { // Ping any user (no check on the nick done ourself)
              bridge->send_irc_user_ping_request(iid.get_server(),
                                                 iid.get_local(), id, from, to_str);
            }
          else if (iid.is_channel && !to.resource.empty())
          else if (iid.type == Iid::Type::Channel && !to.resource.empty())
            { // Ping a room participant (we check if the nick is in the room)
              bridge->send_irc_participant_ping_request(iid,
                                                        to.resource, id, from, to_str);


@@ 481,7 482,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
            version = version_node->get_inner();
          if (os_node)
            os = os_node->get_inner();
          const Iid iid(to.local);
          const Iid iid(to.local, bridge);
          bridge->send_xmpp_version_to_irc(iid, name, version, os);
        }
      else


@@ 604,7 605,7 @@ void BiboumiComponent::send_ping_request(const std::string& from,
                    "the response mismatches the 'from' of the request");
        }
      else
        bridge->send_irc_ping_result(from, id);
        bridge->send_irc_ping_result({from, bridge}, id);
    };
  this->waiting_iq[id] = result_cb;
}

M tests/end_to_end/__main__.py => tests/end_to_end/__main__.py +22 -22
@@ 648,8 648,8 @@ if __name__ == '__main__':
                     # That second user sends a private message to the first one
                     partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='chat'><body>RELLO</body></message>"),
                     # Message is received with a server-wide JID, by the two resources behind nick_one
                     partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='RELLO']"),
                     partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']"),
                     partial(expect_stanza, "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='RELLO']"),
                     partial(expect_stanza, "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']"),

                     # One resource leaves the server entirely.
                     partial(send_stanza, "<presence type='unavailable' from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),


@@ 662,8 662,8 @@ if __name__ == '__main__':
                     partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='chat'><body>first</body></message>"),
                     partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='chat'><body>second</body></message>"),
                     # The first user receives the two messages, on the connected resource, once each
                     partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='first']"),
                     partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='second']"),
                     partial(expect_stanza, "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='first']"),
                     partial(expect_stanza, "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='second']"),


                 ]),


@@ 708,10 708,10 @@ if __name__ == '__main__':
                     # Send a private message, to a in-room JID
                     partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_two}' type='chat'><body>coucou in private</body></message>"),
                     # Message is received with a server-wide JID
                     partial(expect_stanza, "/message[@from='{lower_nick_one}!{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='coucou in private']"),
                     partial(expect_stanza, "/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='coucou in private']"),

                     # Respond to the message, to the server-wide JID
                     partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}!{irc_server_one}' type='chat'><body>yes</body></message>"),
                     partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>yes</body></message>"),
                     # The response is received from the in-room JID
                     partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='yes']"),



@@ 729,10 729,10 @@ if __name__ == '__main__':
                     # Send a private message, to a in-room JID
                     partial(send_stanza, "<message from='{jid_one}/{resource_one}' to='%{irc_server_one}/{nick_two}' type='chat'><body>re in private</body></message>"),
                     # Message is received with a server-wide JID
                     partial(expect_stanza, "/message[@from='{lower_nick_one}!{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='re in private']"),
                     partial(expect_stanza, "/message[@from='{lower_nick_one}%{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='re in private']"),

                     # Respond to the message, to the server-wide JID
                     partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}!{irc_server_one}' type='chat'><body>re</body></message>"),
                     partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>re</body></message>"),
                     # The response is received from the in-room JID
                     partial(expect_stanza, "/message[@from='%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='re']"),



@@ 743,9 743,9 @@ if __name__ == '__main__':
                     "/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']"),

                     # The private messages from this nick should now come (again) from the server-wide JID
                     partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}!{irc_server_one}' type='chat'><body>hihihoho</body></message>"),
                     partial(send_stanza, "<message from='{jid_two}/{resource_one}' to='{lower_nick_one}%{irc_server_one}' type='chat'><body>hihihoho</body></message>"),
                     partial(expect_stanza,
                     "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}']"),
                     "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}']"),
                 ]
                 ),
                Scenario("encoded_channel_join",


@@ 781,10 781,10 @@ if __name__ == '__main__':
                             "<iq type='get' from='{jid_one}/{resource_one}' id='first_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"),
                     # We receive our own ping request,
                     partial(expect_stanza,
                             "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"),
                             "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"),
                     # Respond to the request
                     partial(send_stanza,
                             "<iq type='result' to='{lower_nick_one}!{irc_server_one}' id='gnip_tsrif' from='{jid_one}/{resource_one}'/>"),
                             "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='gnip_tsrif' from='{jid_one}/{resource_one}'/>"),
                     partial(expect_stanza,
                             "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"),



@@ 802,21 802,21 @@ if __name__ == '__main__':
                             "<iq type='get' from='{jid_one}/{resource_one}' id='second_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"),
                     # We receive our own ping request. Note that we don't know the to value, it could be one of our two resources.
                     partial(expect_stanza,
                             "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to][@id='gnip_dnoces']",
                             "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to][@id='gnip_dnoces']",
                             after = partial(save_value, "to", partial(extract_attribute, "/iq", "to"))),
                     # Respond to the request, using the extracted 'to' value as our 'from'
                     partial(send_stanza,
                             "<iq type='result' to='{lower_nick_one}!{irc_server_one}' id='gnip_dnoces' from='{to}'/>"),
                             "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='gnip_dnoces' from='{to}'/>"),
                     partial(expect_stanza,
                             "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_ping']"),
                     ## And re-do exactly the same thing, just change the resource initiating the self ping
                     partial(send_stanza,
                             "<iq type='get' from='{jid_one}/{resource_two}' id='third_ping' to='#foo%{irc_server_one}/{nick_one}'><ping xmlns='urn:xmpp:ping' /></iq>"),
                     partial(expect_stanza,
                             "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to][@id='gnip_driht']",
                             "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to][@id='gnip_driht']",
                             after = partial(save_value, "to", partial(extract_attribute, "/iq", "to"))),
                     partial(send_stanza,
                             "<iq type='result' to='{lower_nick_one}!{irc_server_one}' id='gnip_driht' from='{to}'/>"),
                             "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='gnip_driht' from='{to}'/>"),
                     partial(expect_stanza,
                             "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='third_ping']"),



@@ 944,11 944,11 @@ if __name__ == '__main__':
                             "<iq type='get' from='{jid_one}/{resource_one}' id='first_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"),
                     # We receive our own request,
                     partial(expect_stanza,
                             "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}']",
                             "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}']",
                             after = partial(save_value, "id", partial(extract_attribute, "/iq", 'id'))),
                     # Respond to the request
                     partial(send_stanza,
                             "<iq type='result' to='{lower_nick_one}!{irc_server_one}' id='{id}' from='{jid_one}/{resource_one}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"),
                             "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{jid_one}/{resource_one}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"),
                     partial(expect_stanza,
                             "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_version']/version:query/version:name[text()='e2e test (through the biboumi gateway) 1.0 Fedora']"),



@@ 966,12 966,12 @@ if __name__ == '__main__':
                             "<iq type='get' from='{jid_one}/{resource_two}' id='second_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"),
                     # We receive our own request. Note that we don't know the to value, it could be one of our two resources.
                     partial(expect_stanza,
                             "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to]",
                             "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to]",
                             after = (partial(save_value, "to", partial(extract_attribute, "/iq", "to")),
                                      partial(save_value, "id", partial(extract_attribute, "/iq", "id")))),
                     # Respond to the request, using the extracted 'to' value as our 'from'
                     partial(send_stanza,
                             "<iq type='result' to='{lower_nick_one}!{irc_server_one}' id='{id}' from='{to}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"),
                             "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{to}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"),
                     partial(expect_stanza,
                             "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='second_version']"),



@@ 980,12 980,12 @@ if __name__ == '__main__':
                             "<iq type='get' from='{jid_one}/{resource_one}' id='second_version' to='#foo%{irc_server_one}/{nick_one}'><query xmlns='jabber:iq:version' /></iq>"),
                     # We receive our own request. Note that we don't know the to value, it could be one of our two resources.
                     partial(expect_stanza,
                             "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to]",
                             "/iq[@from='{lower_nick_one}%{irc_server_one}'][@type='get'][@to]",
                             after = (partial(save_value, "to", partial(extract_attribute, "/iq", "to")),
                                      partial(save_value, "id", partial(extract_attribute, "/iq", "id")))),
                     # Respond to the request, using the extracted 'to' value as our 'from'
                     partial(send_stanza,
                             "<iq type='result' to='{lower_nick_one}!{irc_server_one}' id='{id}' from='{to}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"),
                             "<iq type='result' to='{lower_nick_one}%{irc_server_one}' id='{id}' from='{to}'><query xmlns='jabber:iq:version'><name>e2e test</name><version>1.0</version><os>Fedora</os></query></iq>"),
                     partial(expect_stanza,
                             "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_version']"),
                 ]),

M tests/iid.cpp => tests/iid.cpp +28 -36
@@ 47,66 47,60 @@ namespace Catch

TEST_CASE("Iid creation")
{
    Iid iid1("foo!irc.example.org");
    CHECK(std::to_string(iid1) == "foo!irc.example.org");
    const std::set<char> chantypes {'#', '&'};
    Iid iid1("foo%irc.example.org", chantypes);
    CHECK(std::to_string(iid1) == "foo%irc.example.org");
    CHECK(iid1.get_local() == "foo");
    CHECK(iid1.get_server() == "irc.example.org");
    CHECK(!iid1.is_channel);
    CHECK(iid1.is_user);
    CHECK(iid1.type == Iid::Type::User);

    Iid iid2("#test%irc.example.org");
    Iid iid2("#test%irc.example.org", chantypes);
    CHECK(std::to_string(iid2) == "#test%irc.example.org");
    CHECK(iid2.get_local() == "#test");
    CHECK(iid2.get_server() == "irc.example.org");
    CHECK(iid2.is_channel);
    CHECK(!iid2.is_user);
    CHECK(iid2.type == Iid::Type::Channel);

    Iid iid3("%irc.example.org");
    Iid iid3("%irc.example.org", chantypes);
    CHECK(std::to_string(iid3) == "%irc.example.org");
    CHECK(iid3.get_local() == "");
    CHECK(iid3.get_local().empty());
    CHECK(iid3.get_server() == "irc.example.org");
    CHECK(iid3.is_channel);
    CHECK(!iid3.is_user);
    CHECK(iid3.type == Iid::Type::Channel);

    Iid iid4("irc.example.org");
    Iid iid4("irc.example.org", chantypes);
    CHECK(std::to_string(iid4) == "irc.example.org");
    CHECK(iid4.get_local() == "");
    CHECK(iid4.get_server() == "irc.example.org");
    CHECK(!iid4.is_channel);
    CHECK(!iid4.is_user);
    CHECK(iid4.type == Iid::Type::Server);

    Iid iid5("nick!");
    CHECK(std::to_string(iid5) == "nick!");
    Iid iid5("nick%", chantypes);
    CHECK(std::to_string(iid5) == "nick%");
    CHECK(iid5.get_local() == "nick");
    CHECK(iid5.get_server() == "");
    CHECK(!iid5.is_channel);
    CHECK(iid5.is_user);
    CHECK(iid5.type == Iid::Type::User);

    Iid iid6("##channel%");
    Iid iid6("##channel%", chantypes);
    CHECK(std::to_string(iid6) == "##channel%");
    CHECK(iid6.get_local() == "##channel");
    CHECK(iid6.get_server() == "");
    CHECK(iid6.is_channel);
    CHECK(!iid6.is_user);
    CHECK(iid6.type == Iid::Type::Channel);
}

TEST_CASE("Iid creation in fixed_server mode")
{
    Config::set("fixed_irc_server", "fixed.example.com", false);

    Iid iid1("foo!irc.example.org");
    CHECK(std::to_string(iid1) == "foo!");
    CHECK(iid1.get_local() == "foo");
    const std::set<char> chantypes {'#', '&'};
    Iid iid1("foo%irc.example.org", chantypes);
    CHECK(std::to_string(iid1) == "foo%irc.example.org");
    CHECK(iid1.get_local() == "foo%irc.example.org");
    CHECK(iid1.get_server() == "fixed.example.com");
    CHECK(!iid1.is_channel);
    CHECK(iid1.is_user);
    CHECK(iid1.type == Iid::Type::User);

    Iid iid2("#test%irc.example.org");
    Iid iid2("#test%irc.example.org", chantypes);
    CHECK(std::to_string(iid2) == "#test%irc.example.org");
    CHECK(iid2.get_local() == "#test%irc.example.org");
    CHECK(iid2.get_server() == "fixed.example.com");
    CHECK(iid2.is_channel);
    CHECK(!iid2.is_user);
    CHECK(iid2.type == Iid::Type::Channel);

    // Note that it is impossible to adress the IRC server directly, or to
    // use the virtual channel, in that mode


@@ 114,17 108,15 @@ TEST_CASE("Iid creation in fixed_server mode")
    // Iid iid3("%irc.example.org");
    // Iid iid4("irc.example.org");

    Iid iid5("nick!");
    CHECK(std::to_string(iid5) == "nick!");
    Iid iid5("nick", chantypes);
    CHECK(std::to_string(iid5) == "nick");
    CHECK(iid5.get_local() == "nick");
    CHECK(iid5.get_server() == "fixed.example.com");
    CHECK(!iid5.is_channel);
    CHECK(iid5.is_user);
    CHECK(iid5.type == Iid::Type::User);

    Iid iid6("##channel%");
    Iid iid6("##channel%", chantypes);
    CHECK(std::to_string(iid6) == "##channel%");
    CHECK(iid6.get_local() == "##channel%");
    CHECK(iid6.get_server() == "fixed.example.com");
    CHECK(iid6.is_channel);
    CHECK(!iid6.is_user);
    CHECK(iid6.type == Iid::Type::Channel);
}