~singpolyma/biboumi

5406de35a39c935a19460da06bf3dcd3948a00d5 — louiz’ 6 years ago a4ab1db
On a client error, do not QUIT, just make the resource leave all channels

This should fix #3205
M src/bridge/bridge.cpp => src/bridge/bridge.cpp +15 -1
@@ 60,6 60,20 @@ void Bridge::shutdown(const std::string& exit_message)
  }
}

void Bridge::remove_resource(const std::string& resource,
                             const std::string& part_message)
{
  const auto resources_in_chan_copy = this->resources_in_chan;
  for (const auto& chan_pair: resources_in_chan_copy)
  {
    const ChannelKey& channel_key = chan_pair.first;
    const std::set<Resource>& resources = chan_pair.second;
    if (resources.count(resource))
      this->leave_irc_channel({std::get<0>(channel_key), std::get<1>(channel_key), {}},
                              part_message, resource);
  }
}

void Bridge::clean()
{
  auto it = this->irc_clients.begin();


@@ 330,7 344,7 @@ void Bridge::send_raw_message(const std::string& hostname, const std::string& bo
  irc->send_raw(body);
}

void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message, const std::string& resource)
void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource)
{
  IrcClient* irc = this->get_irc_client(iid.get_server());
  const auto key = iid.to_tuple();

M src/bridge/bridge.hpp => src/bridge/bridge.hpp +5 -1
@@ 45,6 45,10 @@ public:
   */
  void shutdown(const std::string& exit_message);
  /**
   * PART the given resource from all the channels
   */
  void remove_resource(const std::string& resource, const std::string& part_message);
  /**
   * Remove all inactive IrcClients
   */
  void clean();


@@ 70,7 74,7 @@ public:
  void send_channel_message(const Iid& iid, const std::string& body);
  void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG");
  void send_raw_message(const std::string& hostname, const std::string& body);
  void leave_irc_channel(Iid&& iid, std::string&& status_message, const std::string& resource);
  void leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource);
  void send_irc_nick_change(const Iid& iid, const std::string& new_nick);
  void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason,
                     const std::string& iq_id, const std::string& to_jid);

M src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +9 -8
@@ 180,23 180,24 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)

void BiboumiComponent::handle_message(const Stanza& stanza)
{
  std::string from = stanza.get_tag("from");
  std::string from_str = stanza.get_tag("from");
  std::string id = stanza.get_tag("id");
  std::string to_str = stanza.get_tag("to");
  std::string type = stanza.get_tag("type");

  if (from.empty())
  if (from_str.empty())
    return;
  if (type.empty())
    type = "normal";
  Bridge* bridge = this->get_user_bridge(from);
  Bridge* bridge = this->get_user_bridge(from_str);
  Jid from(from_str);
  Jid to(to_str);
  Iid iid(to.local, bridge);

  std::string error_type("cancel");
  std::string error_name("internal-server-error");
  utils::ScopeGuard stanza_error([&](){
      this->send_stanza_error("message", from, to_str, id,
      this->send_stanza_error("message", from_str, to_str, id,
                              error_type, error_name, "");
    });
  const XmlNode* body = stanza.get_child("body", COMPONENT_NS);


@@ 216,7 217,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
    {
      const XmlNode* error = stanza.get_child("error", COMPONENT_NS);
      // Only a set of errors are considered “fatal”. If we encounter one of
      // them, we purge (we disconnect the user from all the IRC servers).
      // them, we purge (we disconnect that resource from all the IRC servers)
      // We consider this to be true, unless the error condition is
      // specified and is not in the kickable_errors set
      bool kickable_error = true;


@@ 227,7 228,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza)
            kickable_error = false;
        }
      if (kickable_error)
        bridge->shutdown("Error from remote client");
        bridge->remove_resource(from.resource, "Error from remote client");
    }
  else if (type == "chat")
    {


@@ 268,10 269,10 @@ void BiboumiComponent::handle_message(const Stanza& stanza)

    }
  else if (iid.type == Iid::Type::User)
    this->send_invalid_user_error(to.local, from);
    this->send_invalid_user_error(to.local, from_str);
  } catch (const IRCNotConnected& ex)
    {
      this->send_stanza_error("message", from, to_str, id,
      this->send_stanza_error("message", from_str, to_str, id,
                              "cancel", "remote-server-not-found",
                              "Not connected to IRC server "s + ex.hostname,
                              true);

M tests/end_to_end/__main__.py => tests/end_to_end/__main__.py +33 -0
@@ 1006,6 1006,39 @@ if __name__ == '__main__':
                            "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}'><x xmlns='http://jabber.org/protocol/muc#user'><invite to='{nick_one}'/></x></message>"),
                    partial(expect_stanza,
                            "/message/body[text()='{nick_one} is already on channel #foo']")
                ]),
                Scenario("client_error",
                [
                    handshake_sequence(),
                    # First resource
                    partial(send_stanza,
                            "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
                    connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
                    partial(expect_stanza,
                            "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
                    partial(expect_stanza,
                            ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
                             "/presence/muc_user:x/muc_user:status[@code='110']")
                            ),
                    partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"),

                    # Second resource, same channel
                    partial(send_stanza,
                            "<presence from='{jid_one}/{resource_two}' to='#foo%{irc_server_one}/{nick_one}' />"),
                    partial(expect_stanza,
                            ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
                             "/presence/muc_user:x/muc_user:status[@code='110']")
                            ),
                    partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"),

                    # Now the first resource has an error
                    partial(send_stanza,
                            "<message from='{jid_one}/{resource_one}' to='#foo%%{irc_server_one}/{nick_one}' type='error'><error type='cancel'><recipient-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error></message>"),
                    # Receive a leave only to the leaving resource
                    partial(expect_stanza,
                            ("/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:status[@code='110']",
                             "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']")
                            ),
                ])
    )