From f9eb534dff28709bfbd98cb5d5ddc0daaae9ecd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 4 Aug 2016 11:52:25 +0200 Subject: Start version 4.0 --- CHANGELOG.rst | 3 +++ CMakeLists.txt | 4 ++-- packaging/biboumi.spec.cmake | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 75a96cc..9ecd898 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,6 @@ +Version 4.0 +=========== + Version 3.0 - 2016-08-03 ======================== diff --git a/CMakeLists.txt b/CMakeLists.txt index d6d5ce8..bb614fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.0) project(biboumi) -set(${PROJECT_NAME}_VERSION_MAJOR 3) +set(${PROJECT_NAME}_VERSION_MAJOR 4) set(${PROJECT_NAME}_VERSION_MINOR 0) -set(${PROJECT_NAME}_VERSION_SUFFIX "") +set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index c94da8b..ec1ac5e 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -59,6 +59,9 @@ make check %{?_smp_mflags} %changelog +* ${RPM_DATE} Le Coz Florent - ${RPM_VERSION}-1 +- Build latest git revision + * Thu Aug 4 2016 Le Coz Florent - 3.0-1 - Update to 3.0 sources -- cgit v1.2.3 From 183f53d0efac4183ae657776357caf8d619a4aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 5 Aug 2016 14:38:51 +0200 Subject: Update the links to the forge in README.rst --- README.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index a7f9348..054ff75 100644 --- a/README.rst +++ b/README.rst @@ -37,14 +37,12 @@ Florent Le Coz (louiz’) Contact/Support --------------- * XMPP ChatRoom: biboumi@muc.poez.io -* Report a bug: https://dev.louiz.org/projects/biboumi/issues/new +* Report a bug: https://lab.louiz.org/louiz/biboumi/issues/new To contribute, the preferred way is to commit your changes on some -publicly-available git repository (your own, or github -(https://github.com/louiz/biboumi), or a fork on https://lab.louiz.org) and -to notify the developers with a ticket on the bug tracker -(https://dev.louiz.org/projects/biboumi/issues/new), a pull request on -github or a merge request on gitlab. +publicly-available git repository (a fork on gitlab or github or on your own +repository) and to notify the developers with a ticket on the bug tracker, +or a merge request on gitlab or a pull request on github. Optionally you can come discuss your changes on the XMPP chat room, beforehand. -- cgit v1.2.3 From 0d2dd71de5292895f69d5f08b000e03e928bdd34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 8 Aug 2016 20:49:00 +0200 Subject: =?UTF-8?q?Don=E2=80=99t=20use=20!=20as=20the=20separator=20for=20?= =?UTF-8?q?nicknames,=20use=20%=20instead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/bridge/bridge.cpp | 26 ++++++--- src/bridge/bridge.hpp | 3 +- src/irc/iid.cpp | 109 ++++++++++++++++++------------------ src/irc/iid.hpp | 58 +++++++++++-------- src/irc/irc_client.cpp | 26 ++++----- src/irc/irc_client.hpp | 3 +- src/xmpp/biboumi_adhoc_commands.cpp | 4 +- src/xmpp/biboumi_component.cpp | 61 ++++++++++---------- tests/end_to_end/__main__.py | 44 +++++++-------- tests/iid.cpp | 64 +++++++++------------ 10 files changed, 207 insertions(+), 191 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 17d3ec6..ac61dbc 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -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>& Bridge::get_irc_cli return this->irc_clients; } +std::set 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); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 69b7bd5..d7b2a5c 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -201,6 +201,7 @@ public: */ void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message); std::unordered_map>& get_irc_clients(); + std::set 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. diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 0e2841e..c951a49 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -1,62 +1,70 @@ #include #include - +#include #include #include -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& chantypes) +{ + this->init(iid); + this->set_type(std::set(chantypes)); +} -void Iid::init(const std::string& iid) +Iid::Iid(const std::string& iid, const std::initializer_list& chantypes): + Iid(iid, std::set(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& 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(); } } diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index 3b11470..7361c51 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -2,48 +2,64 @@ #include +#include + +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& chantypes); + Iid(const std::string& iid, const std::initializer_list& 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 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& chantypes); std::string local; std::string server; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index dd83307..d00b6ee 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -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 diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index fc3918e..fa35e65 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -280,8 +280,9 @@ public: const Resolver& get_resolver() const { return this->dns_resolver; } - const std::vector& get_sorted_user_modes() const { return sorted_user_modes; } + const std::vector& get_sorted_user_modes() const { return this->sorted_user_modes; } + std::set get_chantypes() const { return this->chantypes; } private: /** * The hostname of the server we are connected to. diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index eec930d..b14081f 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -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")) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index a6aac21..f621a6d 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -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; } diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 4348197..d438265 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -648,8 +648,8 @@ if __name__ == '__main__': # That second user sends a private message to the first one partial(send_stanza, "RELLO"), # 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, ""), @@ -662,8 +662,8 @@ if __name__ == '__main__': partial(send_stanza, "first"), partial(send_stanza, "second"), # 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, "coucou in private"), # 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, "yes"), + partial(send_stanza, "yes"), # 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, "re in private"), # 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, "re"), + partial(send_stanza, "re"), # 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, "hihihoho"), + partial(send_stanza, "hihihoho"), 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__': ""), # 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, - ""), + ""), 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__': ""), # 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, - ""), + ""), 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, ""), 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, - ""), + ""), 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__': ""), # 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, - "e2e test1.0Fedora"), + "e2e test1.0Fedora"), 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__': ""), # 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, - "e2e test1.0Fedora"), + "e2e test1.0Fedora"), 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__': ""), # 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, - "e2e test1.0Fedora"), + "e2e test1.0Fedora"), partial(expect_stanza, "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_version']"), ]), diff --git a/tests/iid.cpp b/tests/iid.cpp index 74d010d..b42b9e5 100644 --- a/tests/iid.cpp +++ b/tests/iid.cpp @@ -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 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 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); } -- cgit v1.2.3 From 06d5a5a36173cb48cfdbac6f7be0929a618efe41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 11 Aug 2016 17:17:34 +0200 Subject: Document the new behaviour to address nicknames, and add to changelog --- CHANGELOG.rst | 7 +++++++ doc/biboumi.1.rst | 61 +++++++++++++++++++++++-------------------------------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9ecd898..5d2ae8a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,13 @@ Version 4.0 =========== + - The separator between the IRC nickname and the IRC server is now '%' + instead of '!'. This makes things simpler (only one separator to + remember). The distinction between a JID refering to a channel and a JID + refering to a nickname is based on the first character (# or & by + default, but this can be customized by the server with the ISUPPORT + extension) + Version 3.0 - 2016-08-03 ======================== diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index dd365f0..b370140 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -86,16 +86,13 @@ irc.example.org), then biboumi will enforce the connexion to that IRC server only. This means that a JID like "#chan@biboumi.example.com" must be used instead of "#chan%irc.example.org@biboumi.example.com". In that mode, the virtual channel (see `Connect to an IRC server`_) is not -available and you still need to use the ! separator to send message to an -IRC user (for example "foo!@biboumi.example.com" to send a message to -foo), although the in-room JID still work as expected -("#channel@biboumi.example.com/Nick"). On the other hand, the '%' lose -any meaning. It can appear in the JID but will not be interpreted as a -separator (thus the JID "#channel%hello@biboumi.example.com" points to the -channel named "#channel%hello" on the configured IRC server) This option -can for example be used by an administrator that just wants to let their -users join their own IRC server using an XMPP client, while forbidding -access to any other IRC server. +available. The '%' character loses any meaning in the JIDs. It can appear +in the JID but will not be interpreted as a separator (thus the JID +"#channel%hello@biboumi.example.com" points to the channel named +"#channel%hello" on the configured IRC server) This option can for example +be used by an administrator that just wants to let their users join their own +IRC server using an XMPP client, while forbidding access to any other IRC +server. realname_customization ---------------------- @@ -201,26 +198,21 @@ the domain served by biboumi (the part after the ``@``, biboumi.example.com in the examples), and the local part (the part before the ``@``) depends on the concerned entity. -IRC channels have a local part formed like this: -``channel_name`` % ``irc_server``. +IRC channels and IRC users have a local part formed like this: +``name`` % ``irc_server``. -If the IRC channel you want to adress starts with the ``'#'`` character (or an -other character, announced by the IRC server, like ``'&'``, ``'+'`` or ``'!'``), -then you must include it in the JID. Some other gateway implementations, as -well as some IRC clients, do not require them to be started by one of these -characters, adding an implicit ``'#'`` in that case. Biboumi does not do that -because this gets confusing when trying to understand the difference between -the channels *#foo*, and *##foo*. Note that biboumi does not use the -presence of these special characters to identify an IRC channel, only the -presence of the separator `%` is used for that. +``name`` can be a channel name or an user nickname. The distinction between +the two is based on the first character: by default, if the name starts with +``'#'`` or ``'&'`` (but this can be overridden by the server, using the +ISUPPORT extension) then it’s a channel name, otherwise this is a nickname. -The channel name can also be empty (for example ``%irc.example.com``), in that -case this represents the virtual channel provided by biboumi. See *Connect -to an IRC server* for more details. +As a special case, the channel name can also be empty (for example +``%irc.example.com``), in that case this represents the virtual channel +provided by biboumi. See *Connect to an IRC server* for more details. There is two ways to address an IRC user, using a local part like this: -``nickname`` ! ``irc_server`` -or by using the in-room address of the participant, like this: +``nickname`` % ``irc_server`` or by using the in-room address of the +participant, like this: ``channel_name`` % ``irc_server`` @ ``biboumi.example.com`` / ``Nickname`` The second JID is available only to be compatible with XMPP clients when the @@ -232,11 +224,11 @@ IRC nicknames are case-insensitive, this means that the nicknames toto, Toto, tOtO and TOTO all represent the same IRC user. This means you can talk to the user toto, and this will work. -Also note that some IRC nicknames may contain characters that are not -allowed in the local part of a JID (for example '@'). If you need to send a -message to a nick containing such a character, you have to use a jid like +Also note that some IRC nicknames or channels may contain characters that are +not allowed in the local part of a JID (for example '@'). If you need to send a +message to a nick containing such a character, you can use a jid like ``%irc.example.com@biboumi.example.com/AnnoyingNickn@me``, because the JID -``AnnoyingNickn@me!irc.example.com@biboumi.example.com`` would not work. +``AnnoyingNickn@me%irc.example.com@biboumi.example.com`` would not work. Examples: @@ -244,7 +236,7 @@ Examples: irc.example.com IRC server, and this is served by the biboumi instance on biboumi.example.com -* ``toto!irc.example.com@biboumi.example.com`` is the IRC user named toto, or +* ``toto%irc.example.com@biboumi.example.com`` is the IRC user named toto, or TotO, etc. * ``irc.example.com@biboumi.example.com`` is the IRC server irc.example.com. @@ -255,9 +247,6 @@ Examples: Note: Some JIDs are valid but make no sense in the context of biboumi: -* ``!irc.example.com@biboumi.example.com`` is the empty-string nick on the - irc.example.com server. It makes no sense to try to send messages to it. - * ``#test%@biboumi.example.com``, or any other JID that does not contain an IRC server is invalid. Any message to that kind of JID will trigger an error, or will be ignored. @@ -286,7 +275,7 @@ channel ``#foo`` on the server ``irc.example.com``, but you need to authenticate to a bot of the server before you’re allowed to join it, you can first join the room ``%irc.example.com@biboumi.example.com`` (this will effectively connect you to the IRC server without joining any room), then send your -authentication message to the user ``bot!irc.example.com@biboumi.example.com`` +authentication message to the user ``bot%irc.example.com@biboumi.example.com`` and finally join the room ``#foo%irc.example.com@biboumi.example.com``. Channel messages @@ -333,7 +322,7 @@ Private messages are handled differently on IRC and on XMPP. On IRC, you talk directly to one server-user: toto on the channel #foo is the same user as toto on the channel #bar (as long as these two channels are on the same IRC server). By default you will receive private messages from the “global” -user (aka nickname!irc.example.com@biboumi.example.com), unless you +user (aka nickname%irc.example.com@biboumi.example.com), unless you previously sent a message to an in-room participant (something like \#test%irc.example.com@biboumi.example.com/nickname), in which case future messages from that same user will be received from that same “in-room” JID. -- cgit v1.2.3 From 7ffe278e0556d222291840dc91c785ccf824fee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 11 Aug 2016 17:18:51 +0200 Subject: Document the JID escaping with xep 106 --- doc/biboumi.1.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index b370140..6ac4b71 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -229,6 +229,12 @@ not allowed in the local part of a JID (for example '@'). If you need to send a message to a nick containing such a character, you can use a jid like ``%irc.example.com@biboumi.example.com/AnnoyingNickn@me``, because the JID ``AnnoyingNickn@me%irc.example.com@biboumi.example.com`` would not work. +And if you need to address a channel that contains such invalid characters, you +have to use `jid-escaping `, +and replace each of these characters with their escaped version, for example to +join the channel ``#b@byfoot``, you need to use the following JID: +``#b\40byfoot%irc.example.com@biboumi.example.com``. + Examples: -- cgit v1.2.3 From 8ec823be4fc587abb7282a06a12f9df9c37810d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 12 Aug 2016 16:39:19 +0200 Subject: Save received and sent messages into the database --- database/database.xml | 15 ++++++++++++--- src/bridge/bridge.cpp | 10 ++++++++++ src/database/database.cpp | 31 +++++++++++++++++++++++++++++++ src/database/database.hpp | 8 ++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/database/database.xml b/database/database.xml index f102db0..fc67caf 100644 --- a/database/database.xml +++ b/database/database.xml @@ -39,8 +39,17 @@ - - - + + + + + + + + + + + diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ac61dbc..1812611 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -224,6 +224,11 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01"); else irc->send_channel_message(iid.get_local(), line); + + const auto xmpp_body = this->make_xmpp_body(line); + Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + std::get<0>(xmpp_body), irc->get_own_nick()); + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line), this->user_jid + "/" + resource); @@ -578,10 +583,15 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st const auto encoding = in_encoding_for(*this, iid); if (muc) { + const auto xmpp_body = this->make_xmpp_body(body, encoding); + Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + std::get<0>(xmpp_body), nick); + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) { this->xmpp.send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource); + } } else diff --git a/src/database/database.cpp b/src/database/database.cpp index 61e1b47..5513946 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -3,7 +3,9 @@ #include #include +#include #include +#include using namespace std::string_literals; @@ -79,9 +81,38 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(cons return coptions; } +void Database::store_muc_message(const std::string& owner, const Iid& iid, + Database::time_point date, + const std::string& body, + const std::string& nick) +{ + db::MucLogLine line(*Database::db); + + line.uuid = Database::gen_uuid(); + line.owner = owner; + line.ircChanName = iid.get_local(); + line.ircServerName = iid.get_server(); + line.date = date.time_since_epoch().count(); + line.body = body; + line.nick = nick; + + line.update(); +} + void Database::close() { Database::db.reset(nullptr); } + +std::string Database::gen_uuid() +{ + char uuid_str[37]; + uuid_t uuid; + uuid_generate(uuid); + uuid_unparse(uuid, uuid_str); + return uuid_str; +} + + #endif diff --git a/src/database/database.hpp b/src/database/database.hpp index 7173bcd..b11332e 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -9,10 +9,14 @@ #include #include +#include + +class Iid; class Database { public: + using time_point = std::chrono::system_clock::time_point; Database() = default; ~Database() = default; @@ -41,11 +45,15 @@ public: const std::string& server, const std::string& channel); + static void store_muc_message(const std::string& owner, const Iid& iid, + time_point date, const std::string& body, const std::string& nick); + static void close(); static void open(const std::string& filename, const std::string& db_type="sqlite3"); private: + static std::string gen_uuid(); static std::unique_ptr db; }; #endif /* USE_DATABASE */ -- cgit v1.2.3 From 787563722c36bc63223655d337d98caac82dc44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 13 Aug 2016 01:46:11 +0200 Subject: Only save the messages into the db if we are actually using a db --- src/bridge/bridge.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 1812611..23fc71d 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -225,10 +225,11 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) else irc->send_channel_message(iid.get_local(), line); +#ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(line); Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), std::get<0>(xmpp_body), irc->get_own_nick()); - +#endif for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line), this->user_jid + "/" + resource); @@ -583,10 +584,11 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st const auto encoding = in_encoding_for(*this, iid); if (muc) { +#ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(body, encoding); Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), std::get<0>(xmpp_body), nick); - +#endif for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) { this->xmpp.send_muc_message(std::to_string(iid), nick, -- cgit v1.2.3 From 487b80ec9ec965520cc441c8ba2fcb7e14ce4fc7 Mon Sep 17 00:00:00 2001 From: louiz Date: Sat, 13 Aug 2016 14:06:18 +0200 Subject: Fix a link in the documenation --- doc/biboumi.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 6ac4b71..d4c21d9 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -230,7 +230,7 @@ message to a nick containing such a character, you can use a jid like ``%irc.example.com@biboumi.example.com/AnnoyingNickn@me``, because the JID ``AnnoyingNickn@me%irc.example.com@biboumi.example.com`` would not work. And if you need to address a channel that contains such invalid characters, you -have to use `jid-escaping `, +have to use `jid-escaping `_, and replace each of these characters with their escaped version, for example to join the channel ``#b@byfoot``, you need to use the following JID: ``#b\40byfoot%irc.example.com@biboumi.example.com``. -- cgit v1.2.3 From 6bbab9d943a56e3cd20a2ac96cce1e9056d148cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 16 Aug 2016 10:44:13 +0200 Subject: Add ping to the disco info of the gateay --- src/xmpp/biboumi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f621a6d..a0222f5 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -558,7 +558,7 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st identity["type"] = "irc"; identity["name"] = "Biboumi XMPP-IRC gateway"; query.add_child(std::move(identity)); - for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS}) + for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS}) { XmlNode feature("feature"); feature["var"] = ns; -- cgit v1.2.3 From 663d4ad54a014b2ced62610098a6f5676f813d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 16 Aug 2016 15:59:00 +0200 Subject: Forward mediated invitations (XMPP to IRC only) --- doc/biboumi.1.rst | 15 +++++++++++++++ src/bridge/bridge.cpp | 16 ++++++++++++++++ src/bridge/bridge.hpp | 2 ++ src/irc/irc_client.cpp | 15 +++++++++++++-- src/irc/irc_client.hpp | 5 +++++ src/xmpp/biboumi_component.cpp | 13 +++++++++++++ 6 files changed, 64 insertions(+), 2 deletions(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index d4c21d9..783788f 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -339,6 +339,21 @@ Notices Notices are received exactly like private messages. It is not possible to send a notice. +Invitations +----------- + +Biboumi forwards the mediated invitations to the target nick. If the user +wishes to invite the user “FooBar” into a room, they can invite one of the +following “JIDs” (one of them is not a JID, actually): + +- foobar%anything@anything +- anything@anything/FooBar +- FooBar + +Note that the “anything” part are simply ignored because they have no +meaning for the IRC server: we already know which IRC server is targeted +using the JID of the target channel. + Kicks and bans -------------- diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 23fc71d..8323c77 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -543,6 +543,22 @@ void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string& "", true); } +void Bridge::send_irc_invitation(const Iid& iid, const std::string to) +{ + IrcClient* irc = this->get_irc_client(iid.get_server()); + Jid to_jid(to); + std::string target_nick; + // Many ways to address a nick: + // A jid (ANY jid…) with a resource + if (!to_jid.resource.empty()) + target_nick = to_jid.resource; + else if (!to_jid.local.empty()) // A jid with a iid with a local part + target_nick = Iid(to_jid.local, {}).get_local(); + else + target_nick = to; // Not a jid, just the nick + irc->send_invitation(iid.get_local(), target_nick); +} + void Bridge::send_irc_version_request(const std::string& irc_hostname, const std::string& target, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index d7b2a5c..a1ff3ad 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -105,6 +105,8 @@ public: void on_gateway_ping(const std::string& irc_hostname, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); + void send_irc_invitation(const Iid& iid, const std::string to); + /*** ** ** From IRC to XMPP. diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d00b6ee..6d4df4a 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -64,6 +64,8 @@ static const std::unordered_mapsend_message(IrcMessage("LIST", {})); } +void IrcClient::send_invitation(const std::string& chan_name, const std::string& nick) +{ + this->send_message(IrcMessage("INVITE", {nick, chan_name})); +} + void IrcClient::send_topic_command(const std::string& chan_name, const std::string& topic) { this->send_message(IrcMessage("TOPIC", {chan_name, topic})); @@ -803,7 +809,6 @@ void IrcClient::on_nickname_change_too_fast(const IrcMessage& message) "", txt); } } - void IrcClient::on_generic_error(const IrcMessage& message) { const std::string error_msg = message.arguments.size() >= 3 ? @@ -811,6 +816,12 @@ void IrcClient::on_generic_error(const IrcMessage& message) this->send_gateway_message(message.arguments[1] + ": " + error_msg, message.prefix); } +void IrcClient::on_useronchannel(const IrcMessage& message) +{ + this->send_gateway_message(message.arguments[1] + " " + message.arguments[3] + " " + + message.arguments[2]); +} + void IrcClient::on_welcome_message(const IrcMessage& message) { this->current_nick = message.arguments[0]; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index fa35e65..6fa40ce 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -129,6 +129,7 @@ public: * Send the LIST irc command */ void send_list_command(); + void send_invitation(const std::string& chan_name, const std::string& nick); void send_topic_command(const std::string& chan_name, const std::string& topic); /** * Send the QUIT irc command @@ -234,6 +235,10 @@ public: * Idem, but for when the user changes their nickname too quickly */ void on_nickname_change_too_fast(const IrcMessage& message); + /** + * An error when we try to invite a user already in the channel + */ + void on_useronchannel(const IrcMessage& message); /** * Handles most errors from the server by just forwarding the message to the user. */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index a0222f5..e7549df 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -254,6 +254,19 @@ void BiboumiComponent::handle_message(const Stanza& stanza) } } } + else if (type == "normal" && iid.type == Iid::Type::Channel) + { + if (const XmlNode* x = stanza.get_child("x", MUC_USER_NS)) + if (const XmlNode* invite = x->get_child("invite", MUC_USER_NS)) + { + const auto invite_to = invite->get_tag("to"); + if (!invite_to.empty()) + { + bridge->send_irc_invitation(iid, invite_to); + } + } + + } else if (iid.type == Iid::Type::User) this->send_invalid_user_error(to.local, from); } catch (const IRCNotConnected& ex) -- cgit v1.2.3 From a4ab1db55f8ca8e130528eed01eeee04ffc8369d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 16 Aug 2016 16:18:08 +0200 Subject: e2e test self invite --- tests/end_to_end/__main__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d438265..4a3abfb 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -989,6 +989,24 @@ if __name__ == '__main__': partial(expect_stanza, "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_version']"), ]), + Scenario("self_invite", + [ + handshake_sequence(), + partial(send_stanza, + ""), + 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())]"), + partial(send_stanza, + ""), + partial(expect_stanza, + "/message/body[text()='{nick_one} is already on channel #foo']") + ]) ) failures = 0 -- cgit v1.2.3 From 5406de35a39c935a19460da06bf3dcd3948a00d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 17 Aug 2016 20:44:00 +0200 Subject: On a client error, do not QUIT, just make the resource leave all channels This should fix #3205 --- src/bridge/bridge.cpp | 16 +++++++++++++++- src/bridge/bridge.hpp | 6 +++++- src/xmpp/biboumi_component.cpp | 17 +++++++++-------- tests/end_to_end/__main__.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 8323c77..f6fefd9 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -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& 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(); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index a1ff3ad..bcad030 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -44,6 +44,10 @@ public: * QUIT all connected IRC servers. */ 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 */ @@ -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); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index e7549df..cb9aac8 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -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); diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 4a3abfb..21718f6 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1006,6 +1006,39 @@ if __name__ == '__main__': ""), partial(expect_stanza, "/message/body[text()='{nick_one} is already on channel #foo']") + ]), + Scenario("client_error", + [ + handshake_sequence(), + # First resource + partial(send_stanza, + ""), + 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, + ""), + 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, + ""), + # 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.']") + ), ]) ) -- cgit v1.2.3 From 4c8fb9a0e314db88dec1f105144aadafc5796ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 17 Aug 2016 03:42:40 +0200 Subject: Forward IRC invites to XMPP --- src/bridge/bridge.cpp | 6 ++++++ src/bridge/bridge.hpp | 2 ++ src/irc/irc_client.cpp | 12 ++++++++++++ src/irc/irc_client.hpp | 1 + src/xmpp/biboumi_component.cpp | 16 ++++++++++++++++ src/xmpp/biboumi_component.hpp | 1 + 6 files changed, 38 insertions(+) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index f6fefd9..f69da77 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -798,6 +798,12 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& this->xmpp.send_ping_request(utils::tolower(nick) + "%" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id)); } +void Bridge::send_xmpp_invitation(const Iid& iid, const std::string& author) +{ + for (const auto& resource: this->resources_in_server[iid.get_server()]) + this->xmpp.send_invitation(std::to_string(iid), this->user_jid + "/" + resource, author); +} + void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid) { auto it = this->preferred_user_from.find(nick); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index bcad030..6fdbcc9 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -175,6 +175,8 @@ public: */ void send_xmpp_ping_request(const std::string& nick, const std::string& hostname, const std::string& id); + void send_xmpp_invitation(const Iid& iid, const std::string& author); + /** * Misc */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 6d4df4a..e4b6377 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -75,6 +75,7 @@ static const std::unordered_mapbridge.kick_muc_user(std::move(iid), target, reason, author.nick); } +void IrcClient::on_invite(const IrcMessage& message) +{ + const std::string& author = message.arguments[0]; + Iid iid; + iid.set_local(message.arguments[1]); + iid.set_server(this->hostname); + iid.type = Iid::Type::Channel; + + this->bridge.send_xmpp_invitation(iid, author); +} + void IrcClient::on_mode(const IrcMessage& message) { const std::string target = message.arguments[0]; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 6fa40ce..9f53c3c 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -249,6 +249,7 @@ public: void on_welcome_message(const IrcMessage& message); void on_part(const IrcMessage& message); void on_error(const IrcMessage& message); + void on_invite(const IrcMessage& message); void on_nick(const IrcMessage& message); void on_kick(const IrcMessage& message); void on_mode(const IrcMessage& message); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index cb9aac8..5c52494 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -645,3 +645,19 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id, iq.add_child(std::move(query)); this->send_stanza(iq); } + +void BiboumiComponent::send_invitation(const std::string& room_target, + const std::string& jid_to, + const std::string& author_nick) +{ + Stanza message("message"); + message["from"] = room_target + "@" + this->served_hostname; + message["to"] = jid_to; + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + XmlNode invite("invite"); + invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick; + x.add_child(std::move(invite)); + message.add_child(std::move(x)); + this->send_stanza(message); +} diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 24d768a..0dbf8f1 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -74,6 +74,7 @@ public: void send_iq_room_list_result(const std::string& id, const std::string& to_jid, const std::string& from, const std::vector& rooms_list); + void send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick); /** * Handle the various stanza types */ -- cgit v1.2.3 From 7d05f9b6aedd9153bd7787a5a83b9840b1991a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 18 Aug 2016 21:13:42 +0200 Subject: Fix the author of the IRC invite --- src/irc/irc_client.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index e4b6377..4cd437d 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -974,13 +974,13 @@ void IrcClient::on_kick(const IrcMessage& message) void IrcClient::on_invite(const IrcMessage& message) { - const std::string& author = message.arguments[0]; + IrcUser author(message.prefix); Iid iid; iid.set_local(message.arguments[1]); iid.set_server(this->hostname); iid.type = Iid::Type::Channel; - this->bridge.send_xmpp_invitation(iid, author); + this->bridge.send_xmpp_invitation(iid, author.nick); } void IrcClient::on_mode(const IrcMessage& message) -- cgit v1.2.3 From d6a22b27f37ea711bcc04ccd699c703696e6b377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 20 Aug 2016 02:36:01 +0200 Subject: Better dependencies for make rpm and dist --- CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bb614fd..81c7da1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -246,7 +246,7 @@ install(FILES conf/biboumi.cfg DESTINATION /etc/bib ## Dist target ## Generate a release tarball from the git sources # -add_custom_target(dist +add_custom_command(OUTPUT ${ARCHIVE_NAME}.tar.xz COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar HEAD > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar # Append this specific file that is not part of the git repo @@ -258,13 +258,15 @@ add_custom_target(dist COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan "${ARCHIVE_NAME}.tar.xz created." WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) -add_dependencies(dist catch) +add_custom_target(dist + DEPENDS ${ARCHIVE_NAME}.tar.xz + DEPENDS catch) add_custom_target(rpm + DEPENDS dist COMMAND mkdir -p rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} COMMAND rpmbuild --define "_topdir `pwd`/rpmbuild/" --define "_sourcedir `pwd`" -ba biboumi.spec ) -add_dependencies(rpm dist) if(BOTAN_FOUND) set(STR_WITH_BOTAN "Botan: yes") -- cgit v1.2.3 From 1d37a9092b47718e539d3d3144c8d2ca642ce50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 20 Aug 2016 02:36:25 +0200 Subject: Go back to non-verbose make check --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 81c7da1..d5b1f52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -215,7 +215,7 @@ if(NOT EXISTS ${CMAKE_SOURCE_DIR}/tests/catch.hpp) ) add_dependencies(test_suite catch) endif() -add_custom_target(check COMMAND "test_suite" "-s" +add_custom_target(check COMMAND "test_suite" DEPENDS test_suite biboumi) add_custom_target(e2e COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" DEPENDS biboumi) -- cgit v1.2.3 From c97e3498216e24ceb4633a5fdce0847ea0609103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 20 Aug 2016 02:36:41 +0200 Subject: =?UTF-8?q?Remove=20dns=20unit=20tests,=20because=20they=20are=20n?= =?UTF-8?q?ot=20*unit*=20test=20(depends=20on=20network=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/dns.cpp | 91 ----------------------------------------------------------- 1 file changed, 91 deletions(-) delete mode 100644 tests/dns.cpp diff --git a/tests/dns.cpp b/tests/dns.cpp deleted file mode 100644 index c3eda7b..0000000 --- a/tests/dns.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include "catch.hpp" - -#include -#include -#include - -#include - -TEST_CASE("DNS resolver") -{ - Resolver resolver; - Resolver resolver2; - Resolver resolver3; - - /** - * If we are using cares, we need to run a poller loop until each - * resolution is finished. Without cares we get the result before - * resolve() returns because it’s blocking. - */ -#ifdef CARES_FOUND - auto p = std::make_shared(); - - const auto loop = [&p]() - { - do - { - DNSHandler::instance.watch_dns_sockets(p); - } - while (p->poll(utils::no_timeout) != -1); - }; -#else - // We don’t need to do anything if we are not using cares. - const auto loop = [](){}; -#endif - - std::string hostname; - std::string port = "6667"; - - bool success = true; - - const auto error_cb = [&success](const std::string& hostname) - { - return [&success, hostname](const char *msg) - { - INFO("Failed to resolve " << hostname << ":" << msg); - success = false; - }; - }; - const auto success_cb = [&success](const std::string& hostname) - { - return [&success, hostname](const struct addrinfo *addr) - { - INFO("Successfully resolved " << hostname << ": " << addr_to_string(addr)); - success = true; - }; - }; - - hostname = "example.com"; - resolver.resolve(hostname, port, - success_cb(hostname), error_cb(hostname)); - hostname = "poez.io"; - resolver2.resolve(hostname, port, - success_cb(hostname), error_cb(hostname)); - hostname = "louiz.org"; - resolver3.resolve(hostname, port, - success_cb(hostname), error_cb(hostname)); - loop(); - CHECK(success); - - hostname = "this.should.fail.because.it.is..misformatted"; - resolver.resolve(hostname, port, - success_cb(hostname), error_cb(hostname)); - loop(); - CHECK(!success); - - hostname = "this.should.fail.because.it.is.does.not.exist.invalid"; - resolver.resolve(hostname, port, - success_cb(hostname), error_cb(hostname)); - loop(); - CHECK(!success); - - hostname = "localhost"; - resolver.resolve(hostname, port, - success_cb(hostname), error_cb(hostname)); - loop(); - CHECK(success); - -#ifdef CARES_FOUND - DNSHandler::instance.destroy(); -#endif -} -- cgit v1.2.3 From 992fa938951558f4515145c9b82af0123c979a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 21 Aug 2016 01:04:32 +0200 Subject: Add get_first_non_empty and use it into Database to simplify a little bit --- CMakeLists.txt | 2 +- src/database/database.cpp | 11 ++++++----- src/utils/get_first_non_empty.cpp | 11 +++++++++++ src/utils/get_first_non_empty.hpp | 20 ++++++++++++++++++++ tests/utils.cpp | 11 +++++++++++ 5 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 src/utils/get_first_non_empty.cpp create mode 100644 src/utils/get_first_non_empty.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d5b1f52..ea9ffc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,7 @@ if(LITESQL_FOUND) add_library(database STATIC src/database/database.cpp ${LITESQL_GENERATED_SOURCES}) - target_link_libraries(database ${LITESQL_LIBRARIES}) + target_link_libraries(database ${LITESQL_LIBRARIES} src_utils) if(BOTAN_FOUND) target_link_libraries(database ${BOTAN_LIBRARIES}) endif() diff --git a/src/database/database.cpp b/src/database/database.cpp index 5513946..3891f41 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -4,8 +4,8 @@ #include #include #include -#include #include +#include using namespace std::string_literals; @@ -73,10 +73,11 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(cons { auto coptions = Database::get_irc_channel_options(owner, server, channel); auto soptions = Database::get_irc_server_options(owner, server); - if (coptions.encodingIn.value().empty()) - coptions.encodingIn = soptions.encodingIn; - if (coptions.encodingOut.value().empty()) - coptions.encodingOut = soptions.encodingOut; + + coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(), + soptions.encodingIn.value()); + coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(), + soptions.encodingOut.value()); return coptions; } diff --git a/src/utils/get_first_non_empty.cpp b/src/utils/get_first_non_empty.cpp new file mode 100644 index 0000000..5b3bedb --- /dev/null +++ b/src/utils/get_first_non_empty.cpp @@ -0,0 +1,11 @@ +#include + +bool is_empty(const std::string& val) +{ + return val.empty(); +} + +bool is_empty(const int& val) +{ + return val == 0; +} diff --git a/src/utils/get_first_non_empty.hpp b/src/utils/get_first_non_empty.hpp new file mode 100644 index 0000000..a38f5fb --- /dev/null +++ b/src/utils/get_first_non_empty.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +bool is_empty(const std::string& val); +bool is_empty(const int& val); + +template +T get_first_non_empty(T&& last) +{ + return last; +} + +template +T get_first_non_empty(T&& first, Args&&... args) +{ + if (!is_empty(first)) + return first; + return get_first_non_empty(std::forward(args)...); +} diff --git a/tests/utils.cpp b/tests/utils.cpp index 01d070e..e7ba415 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -6,6 +6,9 @@ #include #include #include +#include + +using namespace std::string_literals; TEST_CASE("String split") { @@ -100,3 +103,11 @@ TEST_CASE("string cut") CHECK(res[0] == "rhello, "); CHECK(res[1] == "♥"); } + +TEST_CASE("first non-empty string") +{ + CHECK(get_first_non_empty(""s, ""s, "hello"s, "world"s) == "hello"s); + CHECK(get_first_non_empty(""s, ""s, ""s, ""s) == ""s); + CHECK(get_first_non_empty("first"s) == "first"s); + CHECK(get_first_non_empty(0, 1, 2, 3) == 1); +} -- cgit v1.2.3 From 6776b827d243ec0e018eac8233c5df402030640e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 00:38:44 +0200 Subject: Add a global configure ad-hoc command, with max history length --- database/database.xml | 10 +++++ src/database/database.cpp | 36 ++++++++++++++++++ src/database/database.hpp | 5 ++- src/xmpp/biboumi_adhoc_commands.cpp | 74 ++++++++++++++++++++++++++++++++++--- src/xmpp/biboumi_adhoc_commands.hpp | 5 ++- src/xmpp/biboumi_component.cpp | 8 ++-- 6 files changed, 126 insertions(+), 12 deletions(-) diff --git a/database/database.xml b/database/database.xml index fc67caf..277d03f 100644 --- a/database/database.xml +++ b/database/database.xml @@ -2,6 +2,12 @@ + + + + + + @@ -18,6 +24,8 @@ + + @@ -32,6 +40,8 @@ + + diff --git a/src/database/database.cpp b/src/database/database.cpp index 3891f41..acf57d1 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -31,6 +31,19 @@ void Database::set_verbose(const bool val) Database::db->verbose = val; } +db::GlobalOptions Database::get_global_options(const std::string& owner) +{ + try { + auto options = litesql::select(*Database::db, + db::GlobalOptions::Owner == owner).one(); + return options; + } catch (const litesql::NotFound& e) { + db::GlobalOptions options(*Database::db); + options.owner = owner; + return options; + } +} + db::IrcServerOptions Database::get_irc_server_options(const std::string& owner, const std::string& server) { @@ -79,6 +92,29 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(cons coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(), soptions.encodingOut.value()); + coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(), + soptions.maxHistoryLength.value()); + + return coptions; +} + +db::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner, + const std::string& server, + const std::string& channel) +{ + auto coptions = Database::get_irc_channel_options(owner, server, channel); + auto soptions = Database::get_irc_server_options(owner, server); + auto goptions = Database::get_global_options(owner); + + coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(), + soptions.encodingIn.value()); + coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(), + soptions.encodingOut.value()); + + coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(), + soptions.maxHistoryLength.value(), + goptions.maxHistoryLength.value()); + return coptions; } diff --git a/src/database/database.hpp b/src/database/database.hpp index b11332e..d1be2fd 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -36,6 +36,7 @@ public: * Return the object from the db. Create it beforehand (with all default * values) if it is not already present. */ + static db::GlobalOptions get_global_options(const std::string& owner); static db::IrcServerOptions get_irc_server_options(const std::string& owner, const std::string& server); static db::IrcChannelOptions get_irc_channel_options(const std::string& owner, @@ -44,7 +45,9 @@ public: static db::IrcChannelOptions get_irc_channel_options_with_server_default(const std::string& owner, const std::string& server, const std::string& channel); - + static db::IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner, + const std::string& server, + const std::string& channel); static void store_muc_message(const std::string& owner, const Iid& iid, time_point date, const std::string& body, const std::string& nick); diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index b14081f..2050edf 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -11,10 +11,6 @@ #include #endif -#include - -#include - using namespace std::string_literals; void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& command_node) @@ -114,6 +110,72 @@ void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, X } #ifdef USE_DATABASE + +void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node) +{ + const Jid owner(session.get_owner_jid()); + const Jid target(session.get_target_jid()); + + auto options = Database::get_global_options(owner.bare()); + + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Configure some global default settings."); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Edit the form, to configure your global settings for the component."); + x.add_child(std::move(instructions)); + + XmlNode required("required"); + + XmlNode max_histo_length("field"); + max_histo_length["var"] = "max_history_length"; + max_histo_length["type"] = "text-single"; + max_histo_length["label"] = "Max history length"; + max_histo_length["desc"] = "The maximum number of lines in the history that the server sends when joining a channel"; + + XmlNode max_histo_length_value("value"); + max_histo_length_value.set_inner(std::to_string(options.maxHistoryLength.value())); + max_histo_length.add_child(std::move(max_histo_length_value)); + x.add_child(std::move(max_histo_length)); + + command_node.add_child(std::move(x)); +} + +void ConfigureGlobalStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) +{ + const XmlNode* x = command_node.get_child("x", "jabber:x:data"); + if (x) + { + const Jid owner(session.get_owner_jid()); + auto options = Database::get_global_options(owner.bare()); + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) + { + const XmlNode* value = field->get_child("value", "jabber:x:data"); + + if (field->get_tag("var") == "max_history_length" && + value && !value->get_inner().empty()) + options.maxHistoryLength = value->get_inner(); + } + + options.update(); + + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("Configuration successfully applied."); + command_node.add_child(std::move(note)); + return; + } + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + error.add_child(std::move(condition)); + command_node.add_child(std::move(error)); + session.terminate(); +} + void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node) { const Jid owner(session.get_owner_jid()); @@ -315,7 +377,7 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com } else if (field->get_tag("var") == "verify_cert" && value - && !value->get_inner().empty()) + && !value->get_inner().empty()) { auto val = to_bool(value->get_inner()); options.verifyCert = val; @@ -442,7 +504,7 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co const XmlNode* value = field->get_child("value", "jabber:x:data"); if (field->get_tag("var") == "encoding_out" && - value && !value->get_inner().empty()) + value && !value->get_inner().empty()) options.encodingOut = value->get_inner(); else if (field->get_tag("var") == "encoding_in" && diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index 2763a9f..7be5509 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -10,6 +10,9 @@ class XmppComponent; void DisconnectUserStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void ConfigureGlobalStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); + void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); @@ -19,5 +22,3 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep3(XmppComponent&, AdhocSession& session, XmlNode& command_node); - - diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 5c52494..9acccdb 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -63,11 +63,13 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st #ifdef USE_DATABASE AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false); + AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false); if (!Config::get("fixed_irc_server", "").empty()) - { this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure", - configure_server_command)); - } + configure_server_command)); + else + this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure", + configure_global_command)); #endif this->irc_server_adhoc_commands_handler.get_commands() = { -- cgit v1.2.3 From 593b3268273cac2fa58257ee8e51ce1a6de30872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 00:39:11 +0200 Subject: When saving the logs, the date must be in seconds, not nanoseconds --- src/database/database.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/database.cpp b/src/database/database.cpp index acf57d1..fce0f45 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -129,7 +129,7 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, line.owner = owner; line.ircChanName = iid.get_local(); line.ircServerName = iid.get_server(); - line.date = date.time_since_epoch().count(); + line.date = date.time_since_epoch().count() / 1'000'000'000; line.body = body; line.nick = nick; -- cgit v1.2.3 From d1626c929f1d313c2f0f85b7d8b756a8d488d1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 00:44:17 +0200 Subject: When joining a channel, send the most recent history found in the database --- louloulibs/xmpp/xmpp_component.cpp | 28 ++++++++++++++++++++++++++++ louloulibs/xmpp/xmpp_component.hpp | 5 +++++ src/bridge/bridge.cpp | 20 ++++++++++++++++++++ src/bridge/bridge.hpp | 5 +++++ src/database/database.cpp | 9 +++++++++ src/database/database.hpp | 2 ++ src/irc/irc_client.cpp | 4 +++- 7 files changed, 72 insertions(+), 1 deletion(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index e87cdf7..22121f6 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -16,6 +16,9 @@ #include +#include +#include + #include #ifdef SYSTEMD_FOUND # include @@ -426,6 +429,31 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str this->send_stanza(message); } +void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, std::time_t timestamp) +{ + Stanza message("message"); + message["to"] = jid_to; + if (!nick.empty()) + message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + else + message["from"] = muc_name + "@" + this->served_hostname; + message["type"] = "groupchat"; + + XmlNode body("body"); + body.set_inner(body_txt); + message.add_child(std::move(body)); + + XmlNode delay("delay"); + delay["xmlns"] = "urn:xmpp:delay"; + delay["from"] = muc_name + "@" + this->served_hostname; + std::stringstream date_ss; + date_ss << std::put_time(std::gmtime(×tamp), "%FT%Tz") << std::endl; + delay["stamp"] = date_ss.str(); + + message.add_child(std::move(delay)); + this->send_stanza(message); +} + void XmppComponent::send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self) { Stanza presence("presence"); diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 5fc6d2e..3a3b10b 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -134,6 +134,11 @@ public: * 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 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 */ diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index f69da77..11393d4 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -744,6 +744,26 @@ void Bridge::send_topic(const std::string& hostname, const std::string& chan_nam } +void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name) +{ + for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) + this->send_room_history(hostname, chan_name, resource); +} + +void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource) +{ +#ifdef USE_DATABASE + const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name); + const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.maxHistoryLength.value()); + for (const auto& line: lines) + { + const auto seconds = line.date.value().timeStamp(); + this->xmpp.send_history_message(chan_name + "%" + hostname, line.nick.value(), line.body.value(), + this->user_jid + "/" + resource, seconds); + } +#endif +} + std::string Bridge::get_own_nick(const Iid& iid) { IrcClient* irc = this->find_irc_client(iid.get_server()); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 6fdbcc9..c26995f 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -137,6 +137,11 @@ public: */ void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who); void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who, const std::string& resource); + /** + * Send the MUC history to the user + */ + void send_room_history(const std::string& hostname, const std::string& chan_name); + void send_room_history(const std::string& hostname, const std::string& chan_name, const std::string& resource); /** * Send a MUC message from some participant */ diff --git a/src/database/database.cpp b/src/database/database.cpp index fce0f45..48cdea8 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -136,6 +136,15 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, line.update(); } +std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, const int limit) +{ + auto res = litesql::select(*Database::db, + db::MucLogLine::Owner == owner && + db::MucLogLine::IrcChanName == chan_name && + db::MucLogLine::IrcServerName == server).orderBy(db::MucLogLine::Date, false).limit(limit).all(); + return {res.rbegin(), res.rend()}; +} + void Database::close() { Database::db.reset(nullptr); diff --git a/src/database/database.hpp b/src/database/database.hpp index d1be2fd..14012ff 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -48,6 +48,8 @@ public: static db::IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner, const std::string& server, const std::string& channel); + static std::vector get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, + const int limit); static void store_muc_message(const std::string& owner, const Iid& iid, time_point date, const std::string& body, const std::string& nick); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 4cd437d..59da97e 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -755,7 +755,9 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) const std::string chan_name = utils::tolower(message.arguments[1]); IrcChannel* channel = this->get_channel(chan_name); channel->joined = true; - this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(), channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true); + this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(), + channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true); + this->bridge.send_room_history(this->hostname, chan_name); this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author); } -- cgit v1.2.3 From 19240d79688d58f23e67732521b80c6a94fbd4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 00:44:52 +0200 Subject: Default max_history_length is 20, not 0 --- database/database.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/database.xml b/database/database.xml index 277d03f..7a9d656 100644 --- a/database/database.xml +++ b/database/database.xml @@ -5,7 +5,7 @@ - + @@ -24,7 +24,7 @@ - + @@ -40,7 +40,7 @@ - + -- cgit v1.2.3 From 38a4bcb2d2fd9bb535801a0209d614206204a460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 00:47:55 +0200 Subject: Only save messages coming from real MUC participants in the database Not messages coming from the room, for example --- src/bridge/bridge.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 11393d4..ad1a45e 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -616,8 +616,9 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st { #ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(body, encoding); - Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), - std::get<0>(xmpp_body), nick); + if (!nick.empty()) + Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + std::get<0>(xmpp_body), nick); #endif for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) { -- cgit v1.2.3 From e13d3fdf4d4754c85e7e05e98592afb71d22be3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 21:29:22 +0200 Subject: Move get_first_non_empty to louloulibs/utils --- CMakeLists.txt | 2 +- louloulibs/utils/get_first_non_empty.cpp | 11 +++++++++++ louloulibs/utils/get_first_non_empty.hpp | 20 ++++++++++++++++++++ src/utils/get_first_non_empty.cpp | 11 ----------- src/utils/get_first_non_empty.hpp | 20 -------------------- 5 files changed, 32 insertions(+), 32 deletions(-) create mode 100644 louloulibs/utils/get_first_non_empty.cpp create mode 100644 louloulibs/utils/get_first_non_empty.hpp delete mode 100644 src/utils/get_first_non_empty.cpp delete mode 100644 src/utils/get_first_non_empty.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ea9ffc3..9db8a30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,7 @@ if(LITESQL_FOUND) add_library(database STATIC src/database/database.cpp ${LITESQL_GENERATED_SOURCES}) - target_link_libraries(database ${LITESQL_LIBRARIES} src_utils) + target_link_libraries(database ${LITESQL_LIBRARIES} utils) if(BOTAN_FOUND) target_link_libraries(database ${BOTAN_LIBRARIES}) endif() diff --git a/louloulibs/utils/get_first_non_empty.cpp b/louloulibs/utils/get_first_non_empty.cpp new file mode 100644 index 0000000..5b3bedb --- /dev/null +++ b/louloulibs/utils/get_first_non_empty.cpp @@ -0,0 +1,11 @@ +#include + +bool is_empty(const std::string& val) +{ + return val.empty(); +} + +bool is_empty(const int& val) +{ + return val == 0; +} diff --git a/louloulibs/utils/get_first_non_empty.hpp b/louloulibs/utils/get_first_non_empty.hpp new file mode 100644 index 0000000..a38f5fb --- /dev/null +++ b/louloulibs/utils/get_first_non_empty.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +bool is_empty(const std::string& val); +bool is_empty(const int& val); + +template +T get_first_non_empty(T&& last) +{ + return last; +} + +template +T get_first_non_empty(T&& first, Args&&... args) +{ + if (!is_empty(first)) + return first; + return get_first_non_empty(std::forward(args)...); +} diff --git a/src/utils/get_first_non_empty.cpp b/src/utils/get_first_non_empty.cpp deleted file mode 100644 index 5b3bedb..0000000 --- a/src/utils/get_first_non_empty.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include - -bool is_empty(const std::string& val) -{ - return val.empty(); -} - -bool is_empty(const int& val) -{ - return val == 0; -} diff --git a/src/utils/get_first_non_empty.hpp b/src/utils/get_first_non_empty.hpp deleted file mode 100644 index a38f5fb..0000000 --- a/src/utils/get_first_non_empty.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -bool is_empty(const std::string& val); -bool is_empty(const int& val); - -template -T get_first_non_empty(T&& last) -{ - return last; -} - -template -T get_first_non_empty(T&& first, Args&&... args) -{ - if (!is_empty(first)) - return first; - return get_first_non_empty(std::forward(args)...); -} -- cgit v1.2.3 From f7fa34436a4d0f92d8d454ceb48d3df111c822b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 21:30:59 +0200 Subject: Delete the database before each e2e test, to start fresh --- tests/end_to_end/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 21718f6..dbbbe88 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -254,6 +254,8 @@ class BiboumiTest: with open("test.conf", "w") as fd: fd.write(confs[scenario.conf]) + os.remove("e2e_test.sqlite") + # Start the XMPP component and biboumi biboumi = BiboumiRunner(scenario.name, with_valgrind) xmpp = XMPPComponent(self.scenario, biboumi) -- cgit v1.2.3 From cfebca4d7c46959fc490cb9c72363d4ac3ee0c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 00:11:51 +0200 Subject: Only save the logs if recordHistory global config option is true --- database/database.xml | 2 +- doc/biboumi.1.rst | 7 +++++++ src/bridge/bridge.cpp | 19 +++++++++++++++---- src/bridge/bridge.hpp | 8 ++++++++ src/xmpp/biboumi_adhoc_commands.cpp | 32 ++++++++++++++++++++++++++++---- 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/database/database.xml b/database/database.xml index 7a9d656..490bc5a 100644 --- a/database/database.xml +++ b/database/database.xml @@ -6,6 +6,7 @@ + @@ -25,7 +26,6 @@ - diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 783788f..2cf771f 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -294,6 +294,13 @@ messages to other users. This means that the order of the messages displayed in your XMPP client may not be the same than the order on other IRC users’. +History +------- + +Public channel messages are saved into the database, unless the +`record_history` option is set to false for that user +`Ad-hoc commands`). + List channels ------------- diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ad1a45e..9b91474 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -32,6 +32,10 @@ Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ xmpp(xmpp), poller(poller) { +#ifdef USE_DATABASE + const auto options = Database::get_global_options(this->user_jid); + this->set_record_history(options.recordHistory.value()); +#endif } /** @@ -241,8 +245,9 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) #ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(line); - Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), - std::get<0>(xmpp_body), irc->get_own_nick()); + if (this->record_history) + Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), + std::get<0>(xmpp_body), irc->get_own_nick()); #endif for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), @@ -616,7 +621,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st { #ifdef USE_DATABASE const auto xmpp_body = this->make_xmpp_body(body, encoding); - if (!nick.empty()) + if (!nick.empty() && this->record_history) Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(), std::get<0>(xmpp_body), nick); #endif @@ -971,7 +976,6 @@ void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::strin { if (user->nick != self->nick) { - log_debug(user->nick); this->send_user_join(iid.get_server(), iid.get_encoded_local(), user.get(), user->get_most_significant_mode(irc->get_sorted_user_modes()), false, resource); @@ -982,3 +986,10 @@ void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::strin true, resource); this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource); } + +#ifdef USE_DATABASE +void Bridge::set_record_history(const bool val) +{ + this->record_history = val; +} +#endif \ No newline at end of file diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index c26995f..0e1fbf4 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -13,6 +13,8 @@ #include #include +#include + class BiboumiComponent; class Poller; @@ -215,6 +217,9 @@ public: void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message); std::unordered_map>& get_irc_clients(); std::set get_chantypes(const std::string& hostname) const; +#ifdef USE_DATABASE + void set_record_history(const bool val); +#endif private: /** @@ -295,6 +300,9 @@ private: * TODO: send message history */ void generate_channel_join_for_resource(const Iid& iid, const std::string& resource); +#ifdef USE_DATABASE + bool record_history { true }; +#endif }; struct IRCNotConnected: public std::exception diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 2050edf..64c16a9 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -135,16 +135,32 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman max_histo_length["label"] = "Max history length"; max_histo_length["desc"] = "The maximum number of lines in the history that the server sends when joining a channel"; - XmlNode max_histo_length_value("value"); - max_histo_length_value.set_inner(std::to_string(options.maxHistoryLength.value())); - max_histo_length.add_child(std::move(max_histo_length_value)); + XmlNode value("value"); + value.set_inner(std::to_string(options.maxHistoryLength.value())); + max_histo_length.add_child(std::move(value)); x.add_child(std::move(max_histo_length)); + XmlNode record_history("field"); + record_history["var"] = "record_history"; + record_history["type"] = "boolean"; + record_history["label"] = "Record history"; + record_history["desc"] = "Whether to save the messages into the database, or not"; + + value.set_name("value"); + if (options.recordHistory.value()) + value.set_inner("true"); + else + value.set_inner("false"); + record_history.add_child(std::move(value)); + x.add_child(std::move(record_history)); + command_node.add_child(std::move(x)); } -void ConfigureGlobalStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) +void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) { + BiboumiComponent& biboumi_component = static_cast(xmpp_component); + const XmlNode* x = command_node.get_child("x", "jabber:x:data"); if (x) { @@ -157,6 +173,14 @@ void ConfigureGlobalStep2(XmppComponent&, AdhocSession& session, XmlNode& comman if (field->get_tag("var") == "max_history_length" && value && !value->get_inner().empty()) options.maxHistoryLength = value->get_inner(); + else if (field->get_tag("var") == "record_history" && + value && !value->get_inner().empty()) + { + options.recordHistory = to_bool(value->get_inner()); + Bridge* bridge = biboumi_component.find_user_bridge(owner.bare()); + if (bridge) + bridge->set_record_history(options.recordHistory.value()); + } } options.update(); -- cgit v1.2.3 From 91285d144d9ff8692a812840985cf2501e6b71c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 15:12:03 +0200 Subject: Index the owner for the global conf table --- database/database.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/database/database.xml b/database/database.xml index 490bc5a..af15ad5 100644 --- a/database/database.xml +++ b/database/database.xml @@ -7,6 +7,9 @@ + + + -- cgit v1.2.3 From 88499d037962b4a0bd85d812cb77ef0452c985f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 00:28:12 +0200 Subject: If the maxHistoryLength is negative, make it 0 Instead of doing a broken SQL request that returns weird lines --- src/database/database.cpp | 4 +++- src/database/database.hpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/database/database.cpp b/src/database/database.cpp index 48cdea8..009ff0e 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -136,8 +136,10 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, line.update(); } -std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, const int limit) +std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, int limit) { + if (limit < 0) + limit = 0; auto res = litesql::select(*Database::db, db::MucLogLine::Owner == owner && db::MucLogLine::IrcChanName == chan_name && diff --git a/src/database/database.hpp b/src/database/database.hpp index 14012ff..e7fdd5f 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -49,7 +49,7 @@ public: const std::string& server, const std::string& channel); static std::vector get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, - const int limit); + int limit); static void store_muc_message(const std::string& owner, const Iid& iid, time_point date, const std::string& body, const std::string& nick); -- cgit v1.2.3 From bc31e969e6be0b3e62fb81126c6354b043019406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 00:30:17 +0200 Subject: Document the sending of the muc history --- doc/biboumi.1.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 2cf771f..8463ed1 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -299,7 +299,8 @@ History Public channel messages are saved into the database, unless the `record_history` option is set to false for that user -`Ad-hoc commands`). +`Ad-hoc commands`). When a channel is joined, biboumi sends the +`max_history_length` messages found in the database, as the MUC history. List channels ------------- -- cgit v1.2.3 From 1ffc3677536fd2978c5f4a77fc6148e80ff1256b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 02:30:40 +0200 Subject: Send the muc history when a second (or more) resource joins a room --- src/bridge/bridge.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 9b91474..e24383e 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -984,6 +984,7 @@ void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::strin this->send_user_join(iid.get_server(), iid.get_encoded_local(), self, self->get_most_significant_mode(irc->get_sorted_user_modes()), true, resource); + this->send_room_history(iid.get_server(), iid.get_local(), resource); this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource); } -- cgit v1.2.3 From 72af3e33ce2090b8aba8199698547c669682bcb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 02:36:00 +0200 Subject: Update changelog with the history thingy [skip ci] --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5d2ae8a..d7a4511 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,10 @@ Version 4.0 refering to a nickname is based on the first character (# or & by default, but this can be customized by the server with the ISUPPORT extension) + - Save all channel messages into the database, with an ad-hoc option to + disable this feature. + - When joining a room, biboumi sends an history of the most recents messages + found in the database. Version 3.0 - 2016-08-03 ======================== -- cgit v1.2.3 From 5e59cc517ff2c653d796bae01550e5d74b384fe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 20:25:41 +0200 Subject: Add missing ctime include --- louloulibs/xmpp/xmpp_component.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 3a3b10b..ba097e5 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #define STREAM_NS "http://etherx.jabber.org/streams" -- cgit v1.2.3 From 00e88f6b2a8aa97d94ccb3ea24dec3f407b08597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Aug 2016 20:28:37 +0200 Subject: Add missing algorithm include --- src/xmpp/biboumi_adhoc_commands.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 64c16a9..af7e473 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include -- cgit v1.2.3 From 20b187daa02a2fc4eda708ac3be406b88060c06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 20:57:57 +0200 Subject: Do not fail e2e if the database cannot be removed because it's not there --- tests/end_to_end/__main__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index dbbbe88..c534608 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -254,7 +254,10 @@ class BiboumiTest: with open("test.conf", "w") as fd: fd.write(confs[scenario.conf]) - os.remove("e2e_test.sqlite") + try: + os.remove("e2e_test.sqlite") + except FileNotFoundError: + pass # Start the XMPP component and biboumi biboumi = BiboumiRunner(scenario.name, with_valgrind) -- cgit v1.2.3 From a195939cdaca343013d32ae902839ffdb28c33cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 20:47:05 +0200 Subject: =?UTF-8?q?Don't=20use=20put=5Ftime()=20because=20it=E2=80=99s=20n?= =?UTF-8?q?ot=20in=20gcc=204.9,=20in=20shitty-debian=20=E2=80=9Cstable?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use strftime instead --- louloulibs/xmpp/xmpp_component.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 22121f6..46c070a 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #ifdef SYSTEMD_FOUND @@ -446,9 +445,10 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std: XmlNode delay("delay"); delay["xmlns"] = "urn:xmpp:delay"; delay["from"] = muc_name + "@" + this->served_hostname; - std::stringstream date_ss; - date_ss << std::put_time(std::gmtime(×tamp), "%FT%Tz") << std::endl; - delay["stamp"] = date_ss.str(); + constexpr std::size_t stamp_size = 20; + char date_buf[stamp_size]; + std::strftime(date_buf, stamp_size, "%FT%Tz", std::gmtime(×tamp)); + delay["stamp"] = date_buf; message.add_child(std::move(delay)); this->send_stanza(message); -- cgit v1.2.3 From b59fc2a834dccb35f12dcfec624b6181e4e2fbe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 23 Aug 2016 20:52:37 +0200 Subject: Use Z instead of z in the timestamp format --- louloulibs/xmpp/xmpp_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 46c070a..07d2408 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -447,7 +447,7 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std: delay["from"] = muc_name + "@" + this->served_hostname; constexpr std::size_t stamp_size = 20; char date_buf[stamp_size]; - std::strftime(date_buf, stamp_size, "%FT%Tz", std::gmtime(×tamp)); + std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp)); delay["stamp"] = date_buf; message.add_child(std::move(delay)); -- cgit v1.2.3 From 7536a1b3f38fbf093c1629b0db209754ada0c906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 25 Aug 2016 19:43:51 +0200 Subject: Respond to MAM requests on a channel JID At the moment, result-set-management is not implemented, the whole history (well, at most 1024 messages) is returned. --- docker/biboumi-test/debian/Dockerfile | 2 +- docker/biboumi-test/fedora/Dockerfile | 2 +- louloulibs/utils/time.cpp | 12 ++++++ louloulibs/utils/time.hpp | 9 ++++ louloulibs/xmpp/jid.hpp | 7 +++- louloulibs/xmpp/xmpp_component.cpp | 21 ++-------- louloulibs/xmpp/xmpp_component.hpp | 4 ++ src/database/database.cpp | 10 ++--- src/xmpp/biboumi_component.cpp | 79 ++++++++++++++++++++++++++++++++++- src/xmpp/biboumi_component.hpp | 10 +++++ tests/end_to_end/__main__.py | 50 ++++++++++++++++++++-- 11 files changed, 176 insertions(+), 30 deletions(-) create mode 100644 louloulibs/utils/time.cpp create mode 100644 louloulibs/utils/time.hpp diff --git a/docker/biboumi-test/debian/Dockerfile b/docker/biboumi-test/debian/Dockerfile index f2a26ea..8a4f2c6 100644 --- a/docker/biboumi-test/debian/Dockerfile +++ b/docker/biboumi-test/debian/Dockerfile @@ -51,7 +51,7 @@ RUN useradd tester -m RUN apt install -y automake autoconf flex bison libltdl-dev openssl RUN apt install -y libtool RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis -RUN cd /charybdis && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install +RUN cd /charybdis && git checkout 4f2b9a4 && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install RUN chown -R tester:tester /home/tester/ircd RUN rm -rf /charybdis diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile index 3c48645..5370627 100644 --- a/docker/biboumi-test/fedora/Dockerfile +++ b/docker/biboumi-test/fedora/Dockerfile @@ -53,7 +53,7 @@ RUN useradd tester RUN dnf install -y automake autoconf flex flex-devel bison libtool-ltdl-devel openssl-devel RUN dnf install -y libtool RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis -RUN cd /charybdis && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install +RUN cd /charybdis && git checkout 4f2b9a4 && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin --with-included-boost && make -j8 && make install RUN chown -R tester:tester /home/tester/ircd RUN rm -rf /charybdis diff --git a/louloulibs/utils/time.cpp b/louloulibs/utils/time.cpp new file mode 100644 index 0000000..e23def4 --- /dev/null +++ b/louloulibs/utils/time.cpp @@ -0,0 +1,12 @@ +#include + +namespace utils +{ +std::string to_string(const std::time_t& timestamp) +{ + constexpr std::size_t stamp_size = 20; + char date_buf[stamp_size]; + std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp)); + return {std::begin(date_buf), std::end(date_buf)}; +} +} \ No newline at end of file diff --git a/louloulibs/utils/time.hpp b/louloulibs/utils/time.hpp new file mode 100644 index 0000000..dff1250 --- /dev/null +++ b/louloulibs/utils/time.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +namespace utils +{ +std::string to_string(const std::time_t& timestamp); +} \ No newline at end of file diff --git a/louloulibs/xmpp/jid.hpp b/louloulibs/xmpp/jid.hpp index 08327ef..85e835c 100644 --- a/louloulibs/xmpp/jid.hpp +++ b/louloulibs/xmpp/jid.hpp @@ -26,7 +26,12 @@ public: } std::string full() const { - return this->local + "@" + this->domain + "/" + this->resource; + std::string res = this->domain; + if (!this->local.empty()) + res = this->local + "@" + this->domain; + if (!this->resource.empty()) + res += "/" + this->resource; + return res; } }; diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 07d2408..5db857c 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -7,22 +7,10 @@ #include #include #include - -#include -#include -#include - -#include +#include #include -#include - -#include -#ifdef SYSTEMD_FOUND -# include -#endif - using namespace std::string_literals; static std::set kickable_errors{ @@ -443,12 +431,9 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std: message.add_child(std::move(body)); XmlNode delay("delay"); - delay["xmlns"] = "urn:xmpp:delay"; + delay["xmlns"] = DELAY_NS; delay["from"] = muc_name + "@" + this->served_hostname; - constexpr std::size_t stamp_size = 20; - char date_buf[stamp_size]; - std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp)); - delay["stamp"] = date_buf; + delay["stamp"] = utils::to_string(timestamp); message.add_child(std::move(delay)); this->send_stanza(message); diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index ba097e5..8359d05 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -26,6 +26,10 @@ #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" /** * An XMPP component, communicating with an XMPP server using the protocole diff --git a/src/database/database.cpp b/src/database/database.cpp index 009ff0e..be0da8e 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -138,13 +138,13 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, int limit) { - if (limit < 0) - limit = 0; - auto res = litesql::select(*Database::db, + if (limit == -1) + limit = 1024; + const auto& res = litesql::select(*Database::db, db::MucLogLine::Owner == owner && db::MucLogLine::IrcChanName == chan_name && - db::MucLogLine::IrcServerName == server).orderBy(db::MucLogLine::Date, false).limit(limit).all(); - return {res.rbegin(), res.rend()}; + db::MucLogLine::IrcServerName == server).orderBy(db::MucLogLine::Id, false).limit(limit).all(); + return {res.crbegin(), res.crend()}; } void Database::close() diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 9acccdb..a49f52e 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -8,8 +8,9 @@ #include #include #include -#include #include +#include +#include #include #include @@ -25,6 +26,8 @@ # include #endif +#include + using namespace std::string_literals; static std::set kickable_errors{ @@ -383,6 +386,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) this->send_stanza(response); stanza_error.disable(); } +#ifdef USE_DATABASE + else if ((query = stanza.get_child("query", MAM_NS))) + { + if (this->handle_mam_request(stanza)) + stanza_error.disable(); + } +#endif } else if (type == "get") { @@ -525,6 +535,73 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) error_name = "feature-not-implemented"; } +#ifdef USE_DATABASE +bool BiboumiComponent::handle_mam_request(const Stanza& stanza) +{ + std::string id = stanza.get_tag("id"); + Jid from(stanza.get_tag("from")); + Jid to(stanza.get_tag("to")); + + const XmlNode* query = stanza.get_child("query", MAM_NS); + std::string query_id; + if (query) + query_id = query->get_tag("queryid"); + + Iid iid(to.local, {'#', '&'}); + if (iid.type == Iid::Type::Channel && to.resource.empty()) + { + const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1); + for (const db::MucLogLine& line: lines) + { + const auto queryid = query->get_tag("queryid"); + if (!line.nick.value().empty()) + this->send_archived_message(line, to.full(), from.full(), queryid); + } + this->send_iq_result_full_jid(id, from.full(), to.full()); + return true; + } + return false; +} + +void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to, + const std::string& queryid) +{ + Stanza message("message"); + message["from"] = from; + message["to"] = to; + + XmlNode result("result"); + result["xmlns"] = MAM_NS; + result["queryid"] = queryid; + result["id"] = log_line.uuid.value(); + + XmlNode forwarded("forwarded"); + forwarded["xmlns"] = FORWARD_NS; + + XmlNode delay("delay"); + delay["xmlns"] = DELAY_NS; + delay["stamp"] = utils::to_string(log_line.date.value().timeStamp()); + + forwarded.add_child(std::move(delay)); + + XmlNode submessage("message"); + submessage["xmlns"] = CLIENT_NS; + submessage["from"] = from + "/" + log_line.nick.value(); + submessage["type"] = "groupchat"; + + XmlNode body("body"); + body.set_inner(log_line.body.value()); + submessage.add_child(std::move(body)); + + forwarded.add_child(std::move(submessage)); + result.add_child(std::move(forwarded)); + message.add_child(std::move(result)); + + this->send_stanza(message); +} + +#endif + Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid) { auto bare_jid = Jid{user_jid}.bare(); diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 0dbf8f1..1844451 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -9,6 +9,10 @@ #include #include +namespace db +{ +class MucLogLine; +} struct ListElement; /** @@ -82,6 +86,12 @@ public: void handle_message(const Stanza& stanza); void handle_iq(const Stanza& stanza); +#ifdef USE_DATABASE + bool handle_mam_request(const Stanza& stanza); + void send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to, + const std::string& queryid); +#endif + private: /** * Return the bridge associated with the bare JID. Create a new one diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index c534608..57e91c9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -74,7 +74,7 @@ class XMPPComponent(slixmpp.BaseXMPP): self.scenario.steps = [] self.failed = True - def on_end_session(self, event): + def on_end_session(self, _): self.loop.stop() def handle_incoming_stanza(self, stanza): @@ -113,7 +113,11 @@ def match(stanza, xpath): 'disco_items': 'http://jabber.org/protocol/disco#items', 'commands': 'http://jabber.org/protocol/commands', 'dataform': 'jabber:x:data', - 'version': 'jabber:iq:version'}) + 'version': 'jabber:iq:version', + 'mam': 'urn:xmpp:mam:1', + 'delay': 'urn:xmpp:delay', + 'forward': 'urn:xmpp:forward:0', + 'client': 'jabber:client'}) return matched @@ -1044,7 +1048,47 @@ if __name__ == '__main__': ("/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.']") ), - ]) + ]), + Scenario("simple_mam", + [ + handshake_sequence(), + # First user joins + partial(send_stanza, + ""), + 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'][@jid='~nick@localhost'][@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())]"), + + # Send two channel messages + partial(send_stanza, "coucou"), + # Receive the message, forwarded to the two users + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + partial(send_stanza, "coucou 2"), + # Receive the message, forwarded to the two users + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"), + + # Retrieve the complete archive + partial(send_stanza, ""), + + partial(expect_stanza, + ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']") + ), + partial(expect_stanza, + ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']") + ), + + partial(expect_stanza, + "/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']") + + ]), ) failures = 0 -- cgit v1.2.3 From aae2d5127b6f4585d05c77d9d0618c1ada755726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 29 Aug 2016 09:15:40 +0200 Subject: Add zlib to the debian dockerfile, to build charybdis properly --- docker/biboumi-test/debian/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/biboumi-test/debian/Dockerfile b/docker/biboumi-test/debian/Dockerfile index 8a4f2c6..e294d2e 100644 --- a/docker/biboumi-test/debian/Dockerfile +++ b/docker/biboumi-test/debian/Dockerfile @@ -48,7 +48,7 @@ RUN cd slixmpp && python3 setup.py build && python3 setup.py install RUN useradd tester -m # Install charybdis, for e2e tests -RUN apt install -y automake autoconf flex bison libltdl-dev openssl +RUN apt install -y automake autoconf flex bison libltdl-dev openssl zlib1g-dev RUN apt install -y libtool RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis RUN cd /charybdis && git checkout 4f2b9a4 && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install -- cgit v1.2.3 From 9727f4cf00e85e90f89c9b5443ec035c44d4c3f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 29 Aug 2016 19:20:27 +0200 Subject: Add the missing includes back --- louloulibs/xmpp/xmpp_component.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 5db857c..f437a15 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -9,8 +9,21 @@ #include #include +#include +#include +#include + +#include #include +#include +#include + +#include +#ifdef SYSTEMD_FOUND +# include +#endif + using namespace std::string_literals; static std::set kickable_errors{ -- cgit v1.2.3 From 2a4905df0153c47656555a21f3d57bbad6f3ffe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 31 Aug 2016 01:17:45 +0200 Subject: Fix to_string(time_t) and write a unit test for it --- louloulibs/utils/time.cpp | 9 +++++---- tests/utils.cpp | 10 ++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/louloulibs/utils/time.cpp b/louloulibs/utils/time.cpp index e23def4..b85d764 100644 --- a/louloulibs/utils/time.cpp +++ b/louloulibs/utils/time.cpp @@ -4,9 +4,10 @@ namespace utils { std::string to_string(const std::time_t& timestamp) { - constexpr std::size_t stamp_size = 20; - char date_buf[stamp_size]; - std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp)); - return {std::begin(date_buf), std::end(date_buf)}; + constexpr std::size_t stamp_size = 21; + char date_buf[stamp_size]; + if (std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp)) != stamp_size - 1) + return ""; + return {std::begin(date_buf), std::end(date_buf) - 1}; } } \ No newline at end of file diff --git a/tests/utils.cpp b/tests/utils.cpp index e7ba415..abe7ef7 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace std::string_literals; @@ -111,3 +112,12 @@ TEST_CASE("first non-empty string") CHECK(get_first_non_empty("first"s) == "first"s); CHECK(get_first_non_empty(0, 1, 2, 3) == 1); } + +TEST_CASE("time_to_string") +{ + const std::time_t stamp = 1472480968; + const std::string result = "2016-08-29T14:29:28Z"; + CHECK(utils::to_string(stamp) == result); + CHECK(utils::to_string(stamp).size() == result.size()); + CHECK(utils::to_string(0) == "1970-01-01T00:00:00Z"); +} -- cgit v1.2.3 From 1140db3b88bb70cbcab044efa729707bd5a442f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 31 Aug 2016 01:58:13 +0200 Subject: Add parse_datetime --- louloulibs/utils/time.cpp | 11 +++++++++++ louloulibs/utils/time.hpp | 1 + tests/utils.cpp | 8 ++++++++ 3 files changed, 20 insertions(+) diff --git a/louloulibs/utils/time.cpp b/louloulibs/utils/time.cpp index b85d764..305b2ad 100644 --- a/louloulibs/utils/time.cpp +++ b/louloulibs/utils/time.cpp @@ -1,4 +1,5 @@ #include +#include namespace utils { @@ -10,4 +11,14 @@ std::string to_string(const std::time_t& timestamp) return ""; return {std::begin(date_buf), std::end(date_buf) - 1}; } + +std::time_t parse_datetime(const std::string& stamp) +{ + struct tm tm; + if (!::strptime(stamp.data(), "%FT%T%z", &tm)) + return -1; + auto res = ::timegm(&tm); + return res; +} + } \ No newline at end of file diff --git a/louloulibs/utils/time.hpp b/louloulibs/utils/time.hpp index dff1250..c71cd9c 100644 --- a/louloulibs/utils/time.hpp +++ b/louloulibs/utils/time.hpp @@ -6,4 +6,5 @@ namespace utils { std::string to_string(const std::time_t& timestamp); +std::time_t parse_datetime(const std::string& stamp); } \ No newline at end of file diff --git a/tests/utils.cpp b/tests/utils.cpp index abe7ef7..084a048 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -121,3 +121,11 @@ TEST_CASE("time_to_string") CHECK(utils::to_string(stamp).size() == result.size()); CHECK(utils::to_string(0) == "1970-01-01T00:00:00Z"); } + +TEST_CASE("parse_datetime") +{ + const auto time = utils::parse_datetime("1970-01-01T00:00:00Z"); + CHECK(time == 0); + CHECK(utils::parse_datetime("2016-08-29T14:29:28Z") == 1472480968); + CHECK(utils::parse_datetime("blah") == -1); +} \ No newline at end of file -- cgit v1.2.3 From 3047bd41b212390da8e3a4dbcf351e79879042dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 4 Sep 2016 21:04:21 +0200 Subject: MAM results can be filtered by start and end dates --- README.rst | 1 + louloulibs/xmpp/xmpp_component.hpp | 1 + src/database/database.cpp | 32 ++++++++++++++++++++++++-------- src/database/database.hpp | 2 +- src/xmpp/biboumi_component.cpp | 37 ++++++++++++++++++++++++++++++------- tests/end_to_end/__main__.py | 26 +++++++++++++++++++++++++- 6 files changed, 82 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index 054ff75..ee41c81 100644 --- a/README.rst +++ b/README.rst @@ -29,6 +29,7 @@ Usage ----- Read `the documentation`_. + Authors ------- Florent Le Coz (louiz’) diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 8359d05..b556ce2 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -30,6 +30,7 @@ #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" /** * An XMPP component, communicating with an XMPP server using the protocole diff --git a/src/database/database.cpp b/src/database/database.cpp index be0da8e..e995d95 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -6,6 +6,7 @@ #include #include #include +#include using namespace std::string_literals; @@ -136,14 +137,30 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid, line.update(); } -std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, int limit) +std::vector Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, + int limit, const std::string& start, const std::string& end) { - if (limit == -1) - limit = 1024; - const auto& res = litesql::select(*Database::db, - db::MucLogLine::Owner == owner && - db::MucLogLine::IrcChanName == chan_name && - db::MucLogLine::IrcServerName == server).orderBy(db::MucLogLine::Id, false).limit(limit).all(); + auto request = litesql::select(*Database::db, + db::MucLogLine::Owner == owner && + db::MucLogLine::IrcChanName == chan_name && + db::MucLogLine::IrcServerName == server); + request.orderBy(db::MucLogLine::Id, false); + + if (limit >= 0) + request.limit(limit); + if (!start.empty()) + { + const auto start_time = utils::parse_datetime(start); + if (start_time != -1) + request.where(db::MucLogLine::Date >= start_time); + } + if (!end.empty()) + { + const auto end_time = utils::parse_datetime(end); + if (end_time != -1) + request.where(db::MucLogLine::Date <= end_time); + } + const auto& res = request.all(); return {res.crbegin(), res.crend()}; } @@ -152,7 +169,6 @@ void Database::close() Database::db.reset(nullptr); } - std::string Database::gen_uuid() { char uuid_str[37]; diff --git a/src/database/database.hpp b/src/database/database.hpp index e7fdd5f..6823574 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -49,7 +49,7 @@ public: const std::string& server, const std::string& channel); static std::vector get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server, - int limit); + int limit=-1, const std::string& before="", const std::string& after=""); static void store_muc_message(const std::string& owner, const Iid& iid, time_point date, const std::string& body, const std::string& nick); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index a49f52e..a2fda60 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -550,13 +550,36 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) Iid iid(to.local, {'#', '&'}); if (iid.type == Iid::Type::Channel && to.resource.empty()) { - const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1); - for (const db::MucLogLine& line: lines) - { - const auto queryid = query->get_tag("queryid"); - if (!line.nick.value().empty()) - this->send_archived_message(line, to.full(), from.full(), queryid); - } + std::string start; + std::string end; + const XmlNode* x = query->get_child("x", DATAFORM_NS); + if (x) + { + const XmlNode* value; + const auto fields = x->get_children("field", DATAFORM_NS); + for (const auto& field: fields) + { + if (field->get_tag("var") == "start") + { + value = field->get_child("value", DATAFORM_NS); + if (value) + start = value->get_inner(); + } + else if (field->get_tag("var") == "end") + { + value = field->get_child("value", DATAFORM_NS); + if (value) + end = value->get_inner(); + } + } + } + const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1, start, end); + for (const db::MucLogLine& line: lines) + { + const auto queryid = query->get_tag("queryid"); + if (!line.nick.value().empty()) + this->send_archived_message(line, to.full(), from.full(), queryid); + } this->send_iq_result_full_jid(id, from.full(), to.full()); return true; } diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 57e91c9..e1779de 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1086,8 +1086,32 @@ if __name__ == '__main__': ), partial(expect_stanza, - "/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']") + "/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), + # Retrieve an empty archive by specifying an early “end” date + partial(send_stanza, """ + + + urn:xmpp:mam:1 + 2000-06-07T00:00:00Z + + """), + + partial(expect_stanza, + "/iq[@type='result'][@id='id2'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), + + # Retrieve an empty archive by specifying a late “start” date + # (note that this test will break in ~1000 years) + partial(send_stanza, """ + + + urn:xmpp:mam:1 + 3016-06-07T00:00:00Z + + """), + + partial(expect_stanza, + "/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), ]), ) -- cgit v1.2.3 From 2c6132f3ee8d29cd733019b34b0e6b8740a1e3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 4 Sep 2016 23:11:42 +0200 Subject: Add 2 missing namespaces in our disco#info result --- src/xmpp/biboumi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index a2fda60..246ea95 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -674,7 +674,7 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st identity["type"] = "irc"; identity["name"] = "Biboumi XMPP-IRC gateway"; query.add_child(std::move(identity)); - for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS}) + for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS}) { XmlNode feature("feature"); feature["var"] = ns; -- cgit v1.2.3 From 44bc04952c18df6971d06b3c1d9f020c97156fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 8 Sep 2016 02:39:59 +0200 Subject: Improve the documentation for the archives --- doc/biboumi.1.rst | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 8463ed1..cf54ac0 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -297,10 +297,25 @@ IRC users’. History ------- -Public channel messages are saved into the database, unless the -`record_history` option is set to false for that user -`Ad-hoc commands`). When a channel is joined, biboumi sends the -`max_history_length` messages found in the database, as the MUC history. +Public channel messages are saved into archives, inside the database, unless +the `record_history` option is set to false for that user `Ad-hoc commands`. +Private messages (messages that are sent directly to a nickname, not a +channel) are never stored in the database. When a channel is joined, biboumi +sends the `max_history_length` messages found in the database as the MUC +history. + +A channel history can be retrieved by using `Message archive management (MAM) +`_ on the channel JID. The results +can be filtered by start and end dates. + +For a given channel, each user has her or his own archive. The content of +the archives are never shared, and thus a user can not use someone else’s +archive to get the messages that they didn’t receive when they were offline. +Although this feature would be very convenient, this would introduce a very +important privacy issue: for example if a biboumi gateway is used by two +users, by querying the archive one user would be able to know whether or not +the other user was in a room at a given time. + List channels ------------- -- cgit v1.2.3 From 009512f6ff8afc222af3d040e042d505a46ddb39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 8 Sep 2016 02:38:58 +0200 Subject: Fix a trivial formatting issue in the doc --- doc/biboumi.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index cf54ac0..9b7782a 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -438,7 +438,7 @@ between IRC modes and XMPP features is as follow: Sets the participant’s role to ``moderator`` and its affiliation to ``member``. ``+v`` - Sets the participant’s role to `participant` and its affiliation to ``member``. + Sets the participant’s role to ``participant`` and its affiliation to ``member``. Similarly, when a biboumi user changes some participant's affiliation or role, biboumi translates that in an IRC mode change. -- cgit v1.2.3 From 0a8bddf03dba53452cef8ef4529f2ba3c7592c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 9 Sep 2016 23:25:11 +0200 Subject: =?UTF-8?q?Didn=E2=80=99t=20I=20fix=20that=20typo=2012=20times=20a?= =?UTF-8?q?lready=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/biboumi.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 9b7782a..8dd84af 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -525,7 +525,7 @@ Thus, encryption is not used to connect to the local XMPP server because it is useless. If compiled with the Botan library, biboumi can use TLS when communicating -with the IRC serveres. It will first try ports 6697 and 6670 and use TLS if +with the IRC servers. It will first try ports 6697 and 6670 and use TLS if it succeeds, if connection fails on both these ports, the connection is established on port 6667 without any encryption. -- cgit v1.2.3 From 7160731179d526c6cebb07d35d7edab597cc9bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 9 Sep 2016 23:38:02 +0200 Subject: Add some configuration example --- doc/example.conf | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 doc/example.conf diff --git a/doc/example.conf b/doc/example.conf new file mode 100644 index 0000000..42631b2 --- /dev/null +++ b/doc/example.conf @@ -0,0 +1,14 @@ +# This is an example configuration for the biboumi component. +# It only contains the default values, and some example values for the +# required fields (hostname and password). + +hostname=biboumi.example.com +password=mypassword +xmpp_server_ip=127.0.0.1 +port=5347 +admin= +realname_customization=true +realname_from_jid=false +log_file= +ca_file= +outgoing_bind= -- cgit v1.2.3 From 4e5734b75b5c6e927feb7d57f049269a50d179e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 9 Sep 2016 23:38:44 +0200 Subject: Trivial doc fixes --- doc/biboumi.1.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 8dd84af..8f5eb9b 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -19,7 +19,7 @@ XMPP client as if these channels were XMPP MUCs. Synopsis ======== -biboumi [*config_filename*\ ] +biboumi [*config_filename*] Options ======= @@ -139,7 +139,7 @@ default is 0, but a more practical value for production use is 1. ca_file ------- -Specifies which file should be use as the list of trusted CA when +Specifies which file should be used as the list of trusted CA when negociating a TLS session. By default this value is unset and biboumi tries a list of well-known paths. -- cgit v1.2.3 From 06d36ccd03b13fbd4858e871645b59858d307999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 16 Sep 2016 23:19:38 +0200 Subject: Remove a useless line in the COPYING file --- COPYING | 2 -- 1 file changed, 2 deletions(-) diff --git a/COPYING b/COPYING index e9d67c3..203efbc 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,3 @@ -Copyright (c) 2015 Florent Le Coz - This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. -- cgit v1.2.3 From a8d28a38d4f61ffef29b3bf2325db6d206cf73ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 16 Sep 2016 23:20:22 +0200 Subject: In the debian docker, install aiodns from source instead of pip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because, I don’t know why but, this doesn’t work. --- docker/biboumi-test/debian/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/biboumi-test/debian/Dockerfile b/docker/biboumi-test/debian/Dockerfile index e294d2e..9aac3ec 100644 --- a/docker/biboumi-test/debian/Dockerfile +++ b/docker/biboumi-test/debian/Dockerfile @@ -39,6 +39,8 @@ RUN rm -rf /litesql RUN ldconfig # Install slixmpp, for e2e tests +RUN git clone https://github.com/saghul/aiodns.git +RUN cd aiodns && python3 setup.py build && python3 setup.py install RUN apt install -y python3-pip RUN git clone git://git.louiz.org/slixmpp RUN pip3 install pyasn1 -- cgit v1.2.3 From aaa2ca670ab5f19390e77a1f5ea88017b312ceaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 17 Sep 2016 00:34:28 +0200 Subject: Fix the parse_datetime by using %Z instead of %z If anybody knows why fedora accepts both, but it only works with %z on debian, please tell me. --- louloulibs/utils/time.cpp | 4 ++-- tests/utils.cpp | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/louloulibs/utils/time.cpp b/louloulibs/utils/time.cpp index 305b2ad..bc8b3f8 100644 --- a/louloulibs/utils/time.cpp +++ b/louloulibs/utils/time.cpp @@ -15,10 +15,10 @@ std::string to_string(const std::time_t& timestamp) std::time_t parse_datetime(const std::string& stamp) { struct tm tm; - if (!::strptime(stamp.data(), "%FT%T%z", &tm)) + if (!::strptime(stamp.data(), "%FT%T%Z", &tm)) return -1; auto res = ::timegm(&tm); return res; } -} \ No newline at end of file +} diff --git a/tests/utils.cpp b/tests/utils.cpp index 084a048..5913f8d 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -127,5 +127,7 @@ TEST_CASE("parse_datetime") const auto time = utils::parse_datetime("1970-01-01T00:00:00Z"); CHECK(time == 0); CHECK(utils::parse_datetime("2016-08-29T14:29:28Z") == 1472480968); + CHECK(utils::parse_datetime("2016-08-29T14:29:28UT") == 1472480968); + CHECK(utils::parse_datetime("2016-08-29T14:29:28GMT") == 1472480968); CHECK(utils::parse_datetime("blah") == -1); } \ No newline at end of file -- cgit v1.2.3 From f8112468a7a557db21aa76c3620d55172cb9bdb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 17 Sep 2016 01:08:32 +0200 Subject: make dist creates the archive with the current time, not the git time --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9db8a30..252f437 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,7 +247,7 @@ install(FILES conf/biboumi.cfg DESTINATION /etc/bib ## Generate a release tarball from the git sources # add_custom_command(OUTPUT ${ARCHIVE_NAME}.tar.xz - COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar HEAD + COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar HEAD^{tree} > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar # Append this specific file that is not part of the git repo COMMAND tar -rf ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar -P ${SOURCE_DIR}/single_include/catch.hpp --xform 's|/.*/|${ARCHIVE_NAME}/tests/|g' -- cgit v1.2.3 From 93ebf45f3050681f94497aac889d30885a503a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 20 Sep 2016 19:02:12 +0200 Subject: Make history messages work with fixed_irc_server fix #3209 --- src/bridge/bridge.cpp | 2 +- tests/end_to_end/__main__.py | 106 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index e24383e..2fc466a 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -764,7 +764,7 @@ void Bridge::send_room_history(const std::string& hostname, const std::string& c for (const auto& line: lines) { const auto seconds = line.date.value().timeStamp(); - this->xmpp.send_history_message(chan_name + "%" + hostname, line.nick.value(), line.body.value(), + this->xmpp.send_history_message(chan_name + utils::empty_if_fixed_server("%" + hostname), line.nick.value(), line.body.value(), this->user_jid + "/" + resource, seconds); } #endif diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index e1779de..a893935 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1052,7 +1052,6 @@ if __name__ == '__main__': Scenario("simple_mam", [ handshake_sequence(), - # First user joins partial(send_stanza, ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), @@ -1066,11 +1065,9 @@ if __name__ == '__main__': # Send two channel messages partial(send_stanza, "coucou"), - # Receive the message, forwarded to the two users partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), partial(send_stanza, "coucou 2"), - # Receive the message, forwarded to the two users partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"), # Retrieve the complete archive @@ -1113,6 +1110,109 @@ if __name__ == '__main__': partial(expect_stanza, "/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), ]), + Scenario("mam_on_fixed_server", + [ + handshake_sequence(), + + partial(send_stanza, + ""), + 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@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), + + partial(send_stanza, "coucou"), + partial(expect_stanza, "/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + partial(send_stanza, "coucou 2"), + partial(expect_stanza, "/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']"), + + # Retrieve the complete archive + partial(send_stanza, ""), + + partial(expect_stanza, + ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo@{biboumi_host}/{nick_one}'][@type='groupchat']/client:body[text()='coucou']") + ), + partial(expect_stanza, + ("/message/mam:result[@queryid='qid1']/forward:forwarded/delay:delay", + "/message/mam:result[@queryid='qid1']/forward:forwarded/client:message[@from='#foo@{biboumi_host}/{nick_one}'][@type='groupchat']/client:body[text()='coucou 2']") + ), + ], conf="fixed_server"), + Scenario("channel_history_on_fixed_server", + [ + handshake_sequence(), + # First user join + partial(send_stanza, + ""), + 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@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), + + # Send one channel message + partial(send_stanza, "coucou"), + partial(expect_stanza, "/message[@from='#foo@{biboumi_host}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + # Second user joins + partial(send_stanza, + ""), + # connection_sequence("irc.localhost", '{jid_one}/{resource_two}'), + # partial(expect_stanza, + # "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + # Receive the history message + partial(expect_stanza, ("/message[@from='#foo@{biboumi_host}/{nick_one}']/body[text()='coucou']", + "/message/delay:delay[@from='#foo@{biboumi_host}']")), + + partial(expect_stanza, "/message[@from='#foo@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), + ], conf="fixed_server"), + Scenario("channel_history", + [ + handshake_sequence(), + # First user join + partial(send_stanza, + ""), + 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'][@jid='~nick@localhost'][@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())]"), + + # Send one channel message + partial(send_stanza, "coucou"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + # Second user joins + partial(send_stanza, + ""), + # connection_sequence("irc.localhost", '{jid_one}/{resource_two}'), + # partial(expect_stanza, + # "/message/body[text()='Mode #foo [+nt] by {irc_host_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'][@jid='~nick@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + # Receive the history message + partial(expect_stanza, ("/message[@from='#foo%{irc_server_one}/{nick_one}']/body[text()='coucou']", + "/message/delay:delay[@from='#foo%{irc_server_one}']")), + + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + ]) ) failures = 0 -- cgit v1.2.3 From 35fc5d6f290eeccd6d2f7267eed7355ed59d356e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 21 Sep 2016 20:38:14 +0200 Subject: Some little style/typo fixes --- src/bridge/bridge.cpp | 39 ++++++++++++++++++++++++--------------- src/xmpp/biboumi_component.cpp | 2 +- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 2fc466a..d4c2b2a 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -498,7 +498,8 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s 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 + 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 { if (irc_hostname != hostname || message.arguments.size() < 2) return false; @@ -586,7 +587,8 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std 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 - irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool + irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid] + (const std::string& hostname, const IrcMessage& message) -> bool { if (irc_hostname != hostname) return false; @@ -659,15 +661,16 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick, this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text); } -void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource) +void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, + const std::string& resource) { if (!resource.empty()) - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource, - self); + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), + this->user_jid + "/" + resource, self); else for (const auto& res: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + res, - self); + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), + this->user_jid + "/" + res, self); IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) irc->send_quit_command(""); @@ -730,7 +733,8 @@ void Bridge::send_user_join(const std::string& hostname, const std::string& chan affiliation, role, this->user_jid + "/" + resource, self); } -void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who) +void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, + const std::string& who) { for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) { @@ -764,7 +768,8 @@ void Bridge::send_room_history(const std::string& hostname, const std::string& c for (const auto& line: lines) { const auto seconds = line.date.value().timeStamp(); - this->xmpp.send_history_message(chan_name + utils::empty_if_fixed_server("%" + hostname), line.nick.value(), line.body.value(), + this->xmpp.send_history_message(chan_name + utils::empty_if_fixed_server("%" + hostname), line.nick.value(), + line.body.value(), this->user_jid + "/" + resource, seconds); } #endif @@ -775,7 +780,7 @@ std::string Bridge::get_own_nick(const Iid& iid) IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc) return irc->get_own_nick(); - return ""; + return {}; } size_t Bridge::active_clients() const @@ -791,8 +796,9 @@ void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::stri void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) { - for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource, "cancel", "conflict", "409", ""); + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) + this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource, + "cancel", "conflict", "409", ""); } void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode) @@ -802,14 +808,16 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode); for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid + "/" + resource); + this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, + this->user_jid + "/" + resource); } void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname) { 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, @@ -821,7 +829,8 @@ 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::send_xmpp_invitation(const Iid& iid, const std::string& author) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 246ea95..fad87f9 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -168,7 +168,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) } else { - // An user wants to join an invalid IRC channel, return a presence error to him + // A user wants to join an invalid IRC channel, return a presence error to him/her if (type.empty()) this->send_invalid_room_error(to.local, to.resource, from_str); } -- cgit v1.2.3 From 07e2209596a1fbcc1d6f97d68bfda8e2add19ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 22 Sep 2016 00:00:13 +0200 Subject: Add the missing & for 3 std::string function arguments --- src/bridge/bridge.cpp | 2 +- src/bridge/bridge.hpp | 2 +- src/irc/iid.cpp | 2 +- src/irc/iid.hpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index d4c2b2a..d16875f 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -563,7 +563,7 @@ void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string& "", true); } -void Bridge::send_irc_invitation(const Iid& iid, const std::string to) +void Bridge::send_irc_invitation(const Iid& iid, const std::string& to) { IrcClient* irc = this->get_irc_client(iid.get_server()); Jid to_jid(to); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 0e1fbf4..b278ea7 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -111,7 +111,7 @@ public: void on_gateway_ping(const std::string& irc_hostname, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); - void send_irc_invitation(const Iid& iid, const std::string to); + void send_irc_invitation(const Iid& iid, const std::string& to); /*** ** diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index c951a49..ff14da5 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -5,7 +5,7 @@ #include -Iid::Iid(const std::string local, const std::string server, Iid::Type type): +Iid::Iid(const std::string& local, const std::string& server, Iid::Type type): type(type), local(local), server(server) diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index 7361c51..a857ae9 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -58,7 +58,7 @@ public: Iid(const std::string& iid, const std::set& chantypes); Iid(const std::string& iid, const std::initializer_list& chantypes); Iid(const std::string& iid, const Bridge* bridge); - Iid(const std::string local, const std::string server, Type type); + Iid(const std::string& local, const std::string& server, Type type); Iid() = default; Iid(const Iid&) = default; -- cgit v1.2.3 From 2922c68e8315190bfc1aa81fbf4959dbb052be3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 22 Sep 2016 19:01:31 +0200 Subject: Respond to disco#info requests on IRC server JIDs This makes it possible to execute an ad-hoc command on a server, with clients like Gajim, for example. --- src/xmpp/biboumi_component.cpp | 36 +++++++++++++++++++++++++++++++++++- src/xmpp/biboumi_component.hpp | 4 ++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index fad87f9..4a741f3 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -399,9 +399,10 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) const XmlNode* query; if ((query = stanza.get_child("query", DISCO_INFO_NS))) { // Disco info + Iid iid(to.local, {}); + const std::string node = query->get_tag("node"); if (to_str == this->served_hostname) { - const std::string node = query->get_tag("node"); if (node.empty()) { // On the gateway itself @@ -409,6 +410,14 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) stanza_error.disable(); } } + else if (iid.type == Iid::Type::Server) + { + if (node.empty()) + { + this->send_irc_server_disco_info(id, from, to_str); + stanza_error.disable(); + } + } } else if ((query = stanza.get_child("query", VERSION_NS))) { @@ -684,6 +693,31 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st this->send_stanza(iq); } +void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from) +{ + Jid from(jid_from); + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = jid_to; + iq["from"] = jid_from; + XmlNode query("query"); + query["xmlns"] = DISCO_INFO_NS; + XmlNode identity("identity"); + identity["category"] = "conference"; + identity["type"] = "irc"; + identity["name"] = "IRC server "s + from.local + " over Biboumi"; + query.add_child(std::move(identity)); + for (const char* ns: {DISCO_INFO_NS, ADHOC_NS, PING_NS, VERSION_NS}) + { + XmlNode feature("feature"); + feature["var"] = ns; + query.add_child(std::move(feature)); + } + iq.add_child(std::move(query)); + this->send_stanza(iq); +} + void BiboumiComponent::send_iq_version_request(const std::string& from, const std::string& jid_to) { diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 1844451..8b2ac78 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -61,6 +61,10 @@ public: * 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 disco informations regarding IRC server JIDs. + */ + void send_irc_server_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from); /** * Send an iq version request */ -- cgit v1.2.3 From daa7ea04a447d4c67a225eb116c0befc2866ea66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 28 Sep 2016 19:48:00 +0200 Subject: Add coverage_e2e, which runs e2e, and regenerate a global coverage report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This coverage is probably more useful than the other one, because it’s not really reasonable to try to cover everything with simple units test (e.g. on network code etc). However, we should strive to have 100% coverage with our e2e tests. --- .gitlab-ci.yml | 3 ++- CMakeLists.txt | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c1cc979..dff5a27 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -85,10 +85,11 @@ build:rpm: - make biboumi -j$(nproc) - make check - make coverage - - mkdir tests_outputs && pushd tests_outputs && make e2e -j$(nproc) -C .. && popd + - mkdir tests_outputs && pushd tests_outputs && make coverage_e2e -j$(nproc) -C .. && popd artifacts: paths: - build/coverage/ + - build/coverage_e2e/ - build/tests_outputs/ when: always diff --git a/CMakeLists.txt b/CMakeLists.txt index 252f437..35f11ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,6 +232,10 @@ if(CMAKE_BUILD_TYPE MATCHES Debug) test_suite coverage ) + SETUP_TARGET_FOR_COVERAGE(coverage_e2e + make + coverage_e2e + e2e) endif() # -- cgit v1.2.3 From 985da8f2b2fbd2119a15e88677ab23c0f74f4100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 28 Sep 2016 20:14:41 +0200 Subject: Remove unused code in sha1 module --- louloulibs/utils/sha1.cpp | 33 --------------------------------- louloulibs/utils/sha1.hpp | 2 -- 2 files changed, 35 deletions(-) diff --git a/louloulibs/utils/sha1.cpp b/louloulibs/utils/sha1.cpp index 76476df..f75bc2a 100644 --- a/louloulibs/utils/sha1.cpp +++ b/louloulibs/utils/sha1.cpp @@ -119,36 +119,3 @@ uint8_t* sha1_result(sha1nfo *s) { // Return pointer to hash (20 characters) return s->state.b; } - -#define HMAC_IPAD 0x36 -#define HMAC_OPAD 0x5c - -void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) { - uint8_t i; - memset(s->keyBuffer, 0, BLOCK_LENGTH); - if (keyLength > BLOCK_LENGTH) { - // Hash long keys - sha1_init(s); - for (;keyLength--;) sha1_writebyte(s, *key++); - memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH); - } else { - // Block length keys are used as is - memcpy(s->keyBuffer, key, keyLength); - } - // Start inner hash - sha1_init(s); - for (i=0; ikeyBuffer[i] ^ HMAC_IPAD); - } -} - -uint8_t* sha1_resultHmac(sha1nfo *s) { - uint8_t i; - // Complete inner hash - memcpy(s->innerHash,sha1_result(s),HASH_LENGTH); - // Calculate outer hash - sha1_init(s); - for (i=0; ikeyBuffer[i] ^ HMAC_OPAD); - for (i=0; iinnerHash[i]); - return sha1_result(s); -} diff --git a/louloulibs/utils/sha1.hpp b/louloulibs/utils/sha1.hpp index d02de75..d436782 100644 --- a/louloulibs/utils/sha1.hpp +++ b/louloulibs/utils/sha1.hpp @@ -31,5 +31,3 @@ void sha1_init(sha1nfo *s); void sha1_writebyte(sha1nfo *s, uint8_t data); void sha1_write(sha1nfo *s, const char *data, size_t len); uint8_t* sha1_result(sha1nfo *s); -void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength); -uint8_t* sha1_resultHmac(sha1nfo *s); -- cgit v1.2.3 From 20a06c953f8f29229af44de13fb29215dccc9d60 Mon Sep 17 00:00:00 2001 From: louiz Date: Wed, 28 Sep 2016 11:47:05 +0200 Subject: Add trendy badges to the README --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index ee41c81..172717e 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,12 @@ Biboumi ======= +.. image:: https://lab.louiz.org/louiz/biboumi/badges/master/build.svg + :target: https://lab.louiz.org/louiz/biboumi/commits/master + +.. image:: https://lab.louiz.org/louiz/biboumi/badges/master/coverage.svg + :target: https://lab.louiz.org/louiz/biboumi/commits/master + Biboumi is an XMPP gateway that connects to IRC servers and translates between the two protocols. It can be used to access IRC channels using any XMPP client as if these channels were XMPP MUCs. -- cgit v1.2.3 From 363a0bf02cf20592b2f9f034de1c5f54c8922b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 29 Sep 2016 20:11:35 +0200 Subject: Look for uuid/uuid.h instead of just uuid.h Avoids a conflict between /usr/include/uuid.h and /usr/local/include/uuid/uuid.h on freebsd --- louloulibs/cmake/Modules/FindLIBUUID.cmake | 2 +- louloulibs/xmpp/xmpp_component.cpp | 2 +- src/database/database.cpp | 2 +- src/xmpp/biboumi_component.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/louloulibs/cmake/Modules/FindLIBUUID.cmake b/louloulibs/cmake/Modules/FindLIBUUID.cmake index 17d3c42..f344249 100644 --- a/louloulibs/cmake/Modules/FindLIBUUID.cmake +++ b/louloulibs/cmake/Modules/FindLIBUUID.cmake @@ -19,7 +19,7 @@ include(FindPkgConfig) pkg_check_modules(LIBUUID uuid) if(NOT LIBUUID_FOUND) - find_path(LIBUUID_INCLUDE_DIRS NAMES uuid.h + find_path(LIBUUID_INCLUDE_DIRS NAMES uuid/uuid.h PATH_SUFFIXES uuid DOC "The libuuid include directory") diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index f437a15..b10479a 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include diff --git a/src/database/database.cpp b/src/database/database.cpp index e995d95..f7d309b 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 4a741f3..c810ce3 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #ifdef SYSTEMD_FOUND # include -- cgit v1.2.3 From ee4cf5dc2d3eaa43794a8ac736a6409e08082882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 29 Sep 2016 20:53:40 +0200 Subject: Add AdhocCommandHandlers::add_command to simplify the usage of this class And make things a little bit clearer --- louloulibs/xmpp/adhoc_commands_handler.cpp | 4 ++-- louloulibs/xmpp/adhoc_commands_handler.hpp | 4 ++-- src/xmpp/biboumi_component.cpp | 31 ++++++++++-------------------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/louloulibs/xmpp/adhoc_commands_handler.cpp b/louloulibs/xmpp/adhoc_commands_handler.cpp index 17c4e67..573c9ec 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.cpp +++ b/louloulibs/xmpp/adhoc_commands_handler.cpp @@ -15,9 +15,9 @@ const std::map& AdhocCommandsHandler::get return this->commands; } -std::map& AdhocCommandsHandler::get_commands() +void AdhocCommandsHandler::add_command(std::string name, AdhocCommand command) { - return this->commands; + this->commands.emplace(std::make_pair(std::move(name), std::move(command))); } XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, const std::string& to, XmlNode command_node) diff --git a/louloulibs/xmpp/adhoc_commands_handler.hpp b/louloulibs/xmpp/adhoc_commands_handler.hpp index 91eb5bd..e37d913 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.hpp +++ b/louloulibs/xmpp/adhoc_commands_handler.hpp @@ -31,9 +31,9 @@ public: */ const std::map& get_commands() const; /** - * This one can be used to add new commands. + * Add a command into the list, associated with the given name */ - std::map& get_commands(); + void add_command(std::string name, AdhocCommand command); /** * Find the requested command, create a new session or use an existing * one, and process the request (provide a new form, an error, or a diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index c810ce3..2b4ff18 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -56,35 +56,24 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st this->stanza_handlers.emplace("iq", std::bind(&BiboumiComponent::handle_iq, this,std::placeholders::_1)); - this->adhoc_commands_handler.get_commands() = { - {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, - {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)}, - {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true)}, - {"disconnect-from-irc-servers", AdhocCommand({&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false)}, - {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)} - }; + this->adhoc_commands_handler.add_command("ping", {{&PingStep1}, "Do a ping", false}); + this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false}); + this->adhoc_commands_handler.add_command("disconnect-user", {{&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true}); + this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false}); + this->adhoc_commands_handler.add_command("reload", {{&Reload}, "Reload biboumi’s configuration", true}); #ifdef USE_DATABASE AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false); AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false); + if (!Config::get("fixed_irc_server", "").empty()) - this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure", - configure_server_command)); + this->adhoc_commands_handler.add_command("configure", configure_server_command); else - this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure", - configure_global_command)); -#endif + this->adhoc_commands_handler.add_command("configure", configure_global_command); - this->irc_server_adhoc_commands_handler.get_commands() = { -#ifdef USE_DATABASE - {"configure", configure_server_command}, -#endif - }; - this->irc_channel_adhoc_commands_handler.get_commands() = { -#ifdef USE_DATABASE - {"configure", AdhocCommand({&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false)}, + this->irc_server_adhoc_commands_handler.add_command("configure", configure_server_command); + this->irc_channel_adhoc_commands_handler.add_command("configure", {{&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false}); #endif - }; } void BiboumiComponent::shutdown() -- cgit v1.2.3 From 265b5df61b5f3d4b5f30bbc6518f833f73bda1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 29 Sep 2016 23:10:41 +0200 Subject: Re-add the ad-hoc command the was removed by mistake in the previous commit Thank you, e2e tests --- louloulibs/xmpp/adhoc_commands_handler.cpp | 3 +++ src/xmpp/biboumi_component.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/louloulibs/xmpp/adhoc_commands_handler.cpp b/louloulibs/xmpp/adhoc_commands_handler.cpp index 573c9ec..540cac0 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.cpp +++ b/louloulibs/xmpp/adhoc_commands_handler.cpp @@ -17,6 +17,9 @@ const std::map& AdhocCommandsHandler::get void AdhocCommandsHandler::add_command(std::string name, AdhocCommand command) { + const auto found = this->commands.find(name); + if (found != this->commands.end()) + throw std::runtime_error("Trying to add an ad-hoc command that already exist: "s + name); this->commands.emplace(std::make_pair(std::move(name), std::move(command))); } diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 2b4ff18..3a016b9 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -59,7 +59,7 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st this->adhoc_commands_handler.add_command("ping", {{&PingStep1}, "Do a ping", false}); this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false}); this->adhoc_commands_handler.add_command("disconnect-user", {{&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true}); - this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false}); + this->adhoc_commands_handler.add_command("disconnect-from-irc-server", {{&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false}); this->adhoc_commands_handler.add_command("reload", {{&Reload}, "Reload biboumi’s configuration", true}); #ifdef USE_DATABASE -- cgit v1.2.3 From ffad4306b9e9c6065a01a5fcaca668d70af0db8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 2 Oct 2016 12:56:04 +0200 Subject: =?UTF-8?q?Use=20LIST=20*=20instead=20of=20just=20LIST,=20because?= =?UTF-8?q?=20some=20servers=20don=E2=80=99t=20accept=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also add a e2e test for the list query --- src/irc/irc_client.cpp | 2 +- tests/end_to_end/__main__.py | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 59da97e..85fdec5 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -428,7 +428,7 @@ void IrcClient::send_kick_command(const std::string& chan_name, const std::strin void IrcClient::send_list_command() { - this->send_message(IrcMessage("LIST", {})); + this->send_message(IrcMessage("LIST", {"*"})); } void IrcClient::send_invitation(const std::string& chan_name, const std::string& nick) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index a893935..d498ba7 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1212,7 +1212,39 @@ if __name__ == '__main__': "/message/delay:delay[@from='#foo%{irc_server_one}']")), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), - ]) + ]), + Scenario("simple_channel_list", + [ + handshake_sequence(), + + partial(log_message, "Join first channel #foo"), + partial(send_stanza, + ""), + 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())]"), + + partial(log_message, "Join second channel #bar"), + partial(send_stanza, + ""), + partial(expect_stanza, + "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + partial(log_message, "Request the whole channel list"), + partial(send_stanza, ""), + partial(expect_stanza, ( + "/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']" + )) + ]) ) failures = 0 -- cgit v1.2.3 From 76a8189b46177eb78eee12d1cb3266f282acd380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 3 Oct 2016 00:58:21 +0200 Subject: Implement result-set-management for LIST queries ref #2948 --- louloulibs/xmpp/xmpp_component.hpp | 4 + src/bridge/bridge.cpp | 181 ++++++++++++++++++++++++++------ src/bridge/bridge.hpp | 26 ++++- src/bridge/list_element.hpp | 7 +- src/bridge/result_set_management.hpp | 10 ++ src/xmpp/biboumi_component.cpp | 62 +++++++++-- src/xmpp/biboumi_component.hpp | 6 +- tests/end_to_end/__main__.py | 193 ++++++++++++++++++++++++++++++++++- 8 files changed, 441 insertions(+), 48 deletions(-) create mode 100644 src/bridge/result_set_management.hpp diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index b556ce2..4b6f37d 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -31,6 +31,7 @@ #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" /** * An XMPP component, communicating with an XMPP server using the protocole @@ -219,6 +220,9 @@ public: 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 */ diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index d16875f..8849ef9 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -10,6 +9,7 @@ #include #include #include +#include "result_set_management.hpp" using namespace std::string_literals; @@ -386,45 +386,164 @@ void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) irc->send_nick_command(new_nick); } -void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, - const std::string& to_jid) +void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid, + ResultSetInfo rs_info) { - IrcClient* irc = this->get_irc_client(iid.get_server()); + auto& list = channel_list_cache[iid.get_server()]; - irc->send_list_command(); + // We fetch the list from the IRC server only if we have a complete + // cached list that needs to be invalidated (that is, when the request + // doesn’t have a after or before, or when the list is empty). + // If the list is not complete, this means that a request is already + // ongoing, so we just need to add the callback. + // By default the list is complete and empty. + if (list.complete && + (list.channels.empty() || (rs_info.after.empty() && rs_info.before.empty()))) + { + IrcClient* irc = this->get_irc_client(iid.get_server()); + irc->send_list_command(); + + // Add a callback that will populate our list + list.channels.clear(); + list.complete = false; + irc_responder_callback_t cb = [this, iid](const std::string& irc_hostname, + const IrcMessage& message) -> bool + { + if (irc_hostname != iid.get_server()) + return false; - std::vector list; + auto& list = channel_list_cache[iid.get_server()]; + + if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES" + || message.command == "ERR_NOSUCHSERVER") + { + list.complete = true; + return true; + } + else if (message.command == "322" || message.command == "RPL_LIST") + { // Add element to list + if (message.arguments.size() == 4) + { + list.channels.emplace_back(message.arguments[1] + utils::empty_if_fixed_server("%" + iid.get_server()), + message.arguments[2], message.arguments[3]); + } + return false; + } + else if (message.command == "323" || message.command == "RPL_LISTEND") + { // Send the iq response with the content of the list + list.complete = true; + return true; + } + return false; + }; - irc_responder_callback_t cb = [this, iid, iq_id, to_jid, list=std::move(list)](const std::string& irc_hostname, - const IrcMessage& message) mutable -> bool + this->add_waiting_irc(std::move(cb)); + } + + // If the list is complete, we immediately send the answer. + // Otherwise, we install a callback, that will populate our list and send + // the answer when we can. + if (list.complete) { - if (irc_hostname != iid.get_server()) + this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid)); + } + else + { + // Add a callback to answer the request as soon as we can + irc_responder_callback_t cb = [this, iid, iq_id, to_jid, + rs_info=std::move(rs_info)](const std::string& irc_hostname, + const IrcMessage& message) -> bool + { + if (irc_hostname != iid.get_server()) + return false; + + if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES" + || message.command == "ERR_NOSUCHSERVER") + { + std::string text; + if (message.arguments.size() >= 2) + text = message.arguments[1]; + this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "wait", "service-unavailable", text, false); + return true; + } + else if (message.command == "322" || message.command == "RPL_LIST") + { + auto& list = channel_list_cache[iid.get_server()]; + const auto res = this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid)); + log_debug("We added a new channel in our list, can we send the result? ", std::boolalpha, res); + return res; + } + else if (message.command == "323" || message.command == "RPL_LISTEND") + { // Send the iq response with the content of the list + auto& list = channel_list_cache[iid.get_server()]; + this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid)); + return true; + } return false; - if (message.command == "263" || message.command == "RPL_TRYAGAIN" || - message.command == "ERR_TOOMANYMATCHES" || message.command == "ERR_NOSUCHSERVER") + }; + + this->add_waiting_irc(std::move(cb)); + } +} + +bool Bridge::send_matching_channel_list(const ChannelList& channel_list, const ResultSetInfo& rs_info, + const std::string& id, const std::string& to_jid, const std::string& from) +{ + auto begin = channel_list.channels.begin(); + auto end = channel_list.channels.begin(); + if (channel_list.complete) + { + begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element) + { + return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname(); + }); + if (begin == channel_list.channels.end()) + begin = channel_list.channels.begin(); + else + begin = std::next(begin); + end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element) + { + return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname(); + }); + if (rs_info.max >= 0) { - std::string text; - if (message.arguments.size() >= 2) - text = message.arguments[1]; - this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, - "wait", "service-unavailable", text, false); - return true; + if (std::distance(begin, end) >= rs_info.max) + end = begin + rs_info.max; } - else if (message.command == "322" || message.command == "RPL_LIST") - { // Add element to list - if (message.arguments.size() == 4) - list.emplace_back(message.arguments[1], message.arguments[2], - message.arguments[3]); - return false; + } + else + { + if (rs_info.after.empty() && rs_info.before.empty() && rs_info.max < 0) + return false; + if (!rs_info.after.empty()) + { + begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element) + { + return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname(); + }); + if (begin == channel_list.channels.end()) + return false; + begin = std::next(begin); } - else if (message.command == "323" || message.command == "RPL_LISTEND") - { // Send the iq response with the content of the list - this->xmpp.send_iq_room_list_result(iq_id, to_jid, std::to_string(iid), list); - return true; + if (!rs_info.before.empty()) + { + end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element) + { + return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname(); + }); + if (end == channel_list.channels.end()) + return false; } - return false; - }; - this->add_waiting_irc(std::move(cb)); + if (rs_info.max >= 0) + { + if (std::distance(begin, end) < rs_info.max) + return false; + else + end = begin + rs_info.max; + } + } + this->xmpp.send_iq_room_list_result(id, to_jid, from, channel_list, begin, end, rs_info); + return true; } void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, @@ -1002,4 +1121,4 @@ void Bridge::set_record_history(const bool val) { this->record_history = val; } -#endif \ No newline at end of file +#endif diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b278ea7..1a1d201 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include @@ -17,6 +19,7 @@ class BiboumiComponent; class Poller; +class ResultSetInfo; /** * A callback called for each IrcMessage we receive. If the message triggers @@ -87,8 +90,19 @@ public: void send_irc_version_request(const std::string& irc_hostname, const std::string& target, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); - void send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, - const std::string& to_jid); + void send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid, + ResultSetInfo rs_info); + /** + * Check if the channel list contains what is needed to answer the RSM request, + * if it does, send the iq result. If the list is complete but does not contain + * everything, send the result anyway (because there are no more available + * channels that could complete the list). + * + * Returns true if we sent the answer. + */ + bool send_matching_channel_list(const ChannelList& channel_list, + const ResultSetInfo& rs_info, const std::string& id, const std::string& to_jid, + const std::string& from); void forward_affiliation_role_change(const Iid& iid, const std::string& nick, const std::string& affiliation, const std::string& role); /** @@ -271,7 +285,6 @@ private: * response iq. */ std::vector waiting_irc; - /** * Resources to IRC channel/server mapping: */ @@ -300,6 +313,13 @@ private: * TODO: send message history */ void generate_channel_join_for_resource(const Iid& iid, const std::string& resource); + /** + * A cache of the channels list (as returned by the server on a LIST + * request), to be re-used on a subsequent XMPP list request that + * uses result-set-management. + */ + std::map channel_list_cache; + #ifdef USE_DATABASE bool record_history { true }; #endif diff --git a/src/bridge/list_element.hpp b/src/bridge/list_element.hpp index 1eff2ee..554c83d 100644 --- a/src/bridge/list_element.hpp +++ b/src/bridge/list_element.hpp @@ -1,6 +1,6 @@ #pragma once - +#include #include struct ListElement @@ -17,3 +17,8 @@ struct ListElement }; +struct ChannelList +{ + bool complete{true}; + std::vector channels{}; +}; diff --git a/src/bridge/result_set_management.hpp b/src/bridge/result_set_management.hpp new file mode 100644 index 0000000..6ff82ba --- /dev/null +++ b/src/bridge/result_set_management.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +struct ResultSetInfo +{ + int max{-1}; + std::string before{}; + std::string after{}; +}; diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 3a016b9..f43b5e0 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include @@ -27,6 +27,7 @@ #endif #include +#include using namespace std::string_literals; @@ -463,7 +464,22 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } 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); + ResultSetInfo rs_info; + const XmlNode* set_node = query->get_child("set", RSM_NS); + if (set_node) + { + const XmlNode* after = set_node->get_child("after", RSM_NS); + if (after) + rs_info.after = after->get_inner(); + const XmlNode* before = set_node->get_child("before", RSM_NS); + if (before) + rs_info.before = before->get_inner(); + const XmlNode* max = set_node->get_child("max", RSM_NS); + if (max) + rs_info.max = std::atoi(max->get_inner().data()); + + } + bridge->send_irc_channel_list_request(iid, id, from, std::move(rs_info)); stanza_error.disable(); } } @@ -749,10 +765,11 @@ void BiboumiComponent::send_ping_request(const std::string& from, this->waiting_iq[id] = result_cb; } -void BiboumiComponent::send_iq_room_list_result(const std::string& id, - const std::string& to_jid, - const std::string& from, - const std::vector& rooms_list) +void BiboumiComponent::send_iq_room_list_result(const std::string& id, const std::string& to_jid, + const std::string& from, const ChannelList& channel_list, + std::vector::const_iterator begin, + std::vector::const_iterator end, + const ResultSetInfo& rs_info) { Stanza iq("iq"); iq["from"] = from + "@" + this->served_hostname; @@ -761,12 +778,41 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id, iq["type"] = "result"; XmlNode query("query"); query["xmlns"] = DISCO_ITEMS_NS; - for (const auto& room: rooms_list) + + for (auto it = begin; it != end; ++it) { XmlNode item("item"); - item["jid"] = room.channel + "%" + from + "@" + this->served_hostname; + item["jid"] = it->channel + "@" + this->served_hostname; query.add_child(std::move(item)); } + + if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty())) + { + XmlNode set_node("set"); + set_node["xmlns"] = RSM_NS; + + if (begin != channel_list.channels.cend()) + { + XmlNode first_node("first"); + first_node["index"] = std::to_string(std::distance(channel_list.channels.cbegin(), begin)); + first_node.set_inner(begin->channel + "@" + this->served_hostname); + set_node.add_child(std::move(first_node)); + } + if (end != channel_list.channels.cbegin()) + { + XmlNode last_node("last"); + last_node.set_inner(std::prev(end)->channel + "@" + this->served_hostname); + set_node.add_child(std::move(last_node)); + } + if (channel_list.complete) + { + XmlNode count_node("count"); + count_node.set_inner(std::to_string(channel_list.channels.size())); + set_node.add_child(std::move(count_node)); + } + query.add_child(std::move(set_node)); + } + iq.add_child(std::move(query)); this->send_stanza(iq); } diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 8b2ac78..77104ed 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -79,9 +79,9 @@ public: /** * Send the channels list in one big stanza */ - void send_iq_room_list_result(const std::string& id, const std::string& to_jid, - const std::string& from, - const std::vector& rooms_list); + void send_iq_room_list_result(const std::string& id, const std::string& to_jid, const std::string& from, + const ChannelList& channel_list, std::vector::const_iterator begin, + std::vector::const_iterator end, const ResultSetInfo& rs_info); void send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick); /** * Handle the various stanza types diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d498ba7..f4decff 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -117,7 +117,8 @@ def match(stanza, xpath): 'mam': 'urn:xmpp:mam:1', 'delay': 'urn:xmpp:delay', 'forward': 'urn:xmpp:forward:0', - 'client': 'jabber:client'}) + 'client': 'jabber:client', + 'rsm': 'http://jabber.org/protocol/rsm'}) return matched @@ -1244,7 +1245,195 @@ if __name__ == '__main__': "/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']", "/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']" )) - ]) + ]), + Scenario("channel_list_with_rsm", + [ + handshake_sequence(), + + partial(log_message, "Join first channel #foo"), + partial(send_stanza, + ""), + 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())]"), + + partial(log_message, "Join second channel #bar"), + partial(send_stanza, + ""), + partial(expect_stanza, + "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + partial(log_message, "Join third channel #coucou"), + partial(send_stanza, + ""), + partial(expect_stanza, + "/message/body[text()='Mode #coucou [+nt] by {irc_host_one}']"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message[@from='#coucou%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + partial(log_message, "Request with max=0"), + partial(send_stanza, "0"), + partial(expect_stanza, ( + "/iq[@type='result']/disco_items:query", + )), + + partial(log_message, "Request with max=2"), + partial(send_stanza, "2"), + partial(expect_stanza, ( + "/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#coucou%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:first[text()='#bar%{irc_server_one}'][@index='0']", + "/iq/disco_items:query/rsm:set/rsm:last[text()='#coucou%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" + )), + + partial(log_message, "Request with max=12"), + partial(send_stanza, "12"), + partial(expect_stanza, ( + "/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#coucou%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#foo%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:first[text()='#bar%{irc_server_one}'][@index='0']", + "/iq/disco_items:query/rsm:set/rsm:last[text()='#foo%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" + )), + + partial(log_message, "Request with max=1 after=#bar"), + partial(send_stanza, "#bar%{irc_server_one}1"), + partial(expect_stanza, ( + "/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/disco_items:item[@jid='#coucou%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:first[text()='#coucou%{irc_server_one}'][@index='1']", + "/iq/disco_items:query/rsm:set/rsm:last[text()='#coucou%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" + )), + + partial(log_message, "Request with max=1 after=#bar"), + partial(send_stanza, "#bar%{irc_server_one}1"), + partial(expect_stanza, ( + "/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/disco_items:item[@jid='#coucou%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:first[text()='#coucou%{irc_server_one}'][@index='1']", + "/iq/disco_items:query/rsm:set/rsm:last[text()='#coucou%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:count[text()='3']" + )) + ]), + Scenario("complete_channel_list_with_pages_of_3", + [ + handshake_sequence(), + + partial(log_message, "Join 10 channels"), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(log_message, "Request the first page, with a limit of 3"), + partial(send_stanza, "3"), + partial(expect_stanza, ( + "/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/disco_items:item[@jid='#aaa%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#bbb%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#ccc%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:first[text()='#aaa%{irc_server_one}'][@index='0']", + "/iq/disco_items:query/rsm:set/rsm:last[text()='#ccc%{irc_server_one}']" + )), + + partial(log_message, "Request subsequent pages"), + partial(send_stanza, "#ccc%{irc_server_one}3"), + partial(expect_stanza, ( + "/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/disco_items:item[@jid='#ddd%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#eee%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#fff%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:first[text()='#ddd%{irc_server_one}'][@index='3']", + "/iq/disco_items:query/rsm:set/rsm:last[text()='#fff%{irc_server_one}']" + )), + + partial(send_stanza, "#fff%{irc_server_one}3"), + partial(expect_stanza, ( + "/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/disco_items:item[@jid='#ggg%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#hhh%{irc_server_one}']", + "/iq/disco_items:query/disco_items:item[@jid='#iii%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:first[text()='#ggg%{irc_server_one}'][@index='6']", + "/iq/disco_items:query/rsm:set/rsm:last[text()='#iii%{irc_server_one}']" + )), + + partial(send_stanza, "#iii%{irc_server_one}3"), + partial(expect_stanza, ( + "/iq[@type='result']/disco_items:query", + "/iq/disco_items:query/disco_items:item[@jid='#jjj%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:first[text()='#jjj%{irc_server_one}'][@index='9']", + "/iq/disco_items:query/rsm:set/rsm:last[text()='#jjj%{irc_server_one}']", + "/iq/disco_items:query/rsm:set/rsm:count[text()='10']" + )), + ]) ) failures = 0 -- cgit v1.2.3 From b2f14f9a464f695c69dd82c30fc19034fded7065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 3 Oct 2016 01:07:56 +0200 Subject: Modify the charybdis conf to disable the LIST throttling --- tests/end_to_end/ircd.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end/ircd.conf b/tests/end_to_end/ircd.conf index 7edb3a8..7327531 100644 --- a/tests/end_to_end/ircd.conf +++ b/tests/end_to_end/ircd.conf @@ -477,8 +477,8 @@ general { operspy_admin_only = no; operspy_dont_care_user_info = no; caller_id_wait = 1 minute; - pace_wait_simple = 1 second; - pace_wait = 10 seconds; + pace_wait_simple = 0 second; + pace_wait = 0 seconds; short_motd = no; ping_cookie = no; connect_timeout = 30 seconds; -- cgit v1.2.3 From 729078a238e563d071f7905e01d3029a880fd479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 3 Oct 2016 01:11:54 +0200 Subject: Add missing include file --- src/bridge/bridge.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 8849ef9..65171a9 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -10,6 +10,7 @@ #include #include #include "result_set_management.hpp" +#include using namespace std::string_literals; -- cgit v1.2.3 From fca25b7704014f7a7dec6185afe8e27b591776e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 3 Oct 2016 02:10:14 +0200 Subject: =?UTF-8?q?Remove=20build/*=20from=20the=20coverage,=20because=20t?= =?UTF-8?q?hat=20code=20isn=E2=80=99t=20part=20of=20biboumi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmake/Modules/CodeCoverage.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Modules/CodeCoverage.cmake b/cmake/Modules/CodeCoverage.cmake index 4f54327..4ff5851 100644 --- a/cmake/Modules/CodeCoverage.cmake +++ b/cmake/Modules/CodeCoverage.cmake @@ -157,7 +157,7 @@ FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) # Remove information about source files that are not part of # the test (system file, external libraries, etc) - COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'tests/*' '/usr/*' 'external/*' --output-file ${_outputname}.info.cleaned -q + COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'tests/*' '/usr/*' 'external/*' 'build/*' --output-file ${_outputname}.info.cleaned -q # Generate the report COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned -- cgit v1.2.3 From e5b392ece8c90605b86d0d93f0ca6989048bc1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 3 Oct 2016 23:30:20 +0200 Subject: Fix parse_datetime by always using a 'z' as the timezone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because some plateform accept Z and z, but some only accept z… --- louloulibs/utils/time.cpp | 3 ++- tests/utils.cpp | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/louloulibs/utils/time.cpp b/louloulibs/utils/time.cpp index bc8b3f8..abf0a84 100644 --- a/louloulibs/utils/time.cpp +++ b/louloulibs/utils/time.cpp @@ -14,8 +14,9 @@ std::string to_string(const std::time_t& timestamp) std::time_t parse_datetime(const std::string& stamp) { + auto stamp2 = stamp.substr(0, stamp.size() - 1) + "z"; struct tm tm; - if (!::strptime(stamp.data(), "%FT%T%Z", &tm)) + if (!::strptime(stamp2.data(), "%FT%T%Z", &tm)) return -1; auto res = ::timegm(&tm); return res; diff --git a/tests/utils.cpp b/tests/utils.cpp index 5913f8d..a18fc81 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -124,10 +124,7 @@ TEST_CASE("time_to_string") TEST_CASE("parse_datetime") { - const auto time = utils::parse_datetime("1970-01-01T00:00:00Z"); - CHECK(time == 0); - CHECK(utils::parse_datetime("2016-08-29T14:29:28Z") == 1472480968); - CHECK(utils::parse_datetime("2016-08-29T14:29:28UT") == 1472480968); - CHECK(utils::parse_datetime("2016-08-29T14:29:28GMT") == 1472480968); + CHECK(utils::parse_datetime("1970-01-01T00:00:00z") == 0); + CHECK(utils::parse_datetime("2016-08-29T14:29:29Z") == 1472480969); CHECK(utils::parse_datetime("blah") == -1); } \ No newline at end of file -- cgit v1.2.3 From b29225601a475efe7f28fe7002eba72e70f3272b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 4 Oct 2016 02:54:35 +0200 Subject: Fix some compilation warning/errors that appear on FreeBSD --- louloulibs/network/resolver.cpp | 1 + src/bridge/bridge.hpp | 2 +- src/irc/iid.cpp | 2 ++ src/irc/iid.hpp | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp index 9d6de23..d3ecd7c 100644 --- a/louloulibs/network/resolver.cpp +++ b/louloulibs/network/resolver.cpp @@ -2,6 +2,7 @@ #include #include #include +#include using namespace std::string_literals; diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 1a1d201..208de32 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -19,7 +19,7 @@ class BiboumiComponent; class Poller; -class ResultSetInfo; +struct ResultSetInfo; /** * A callback called for each IrcMessage we receive. If the message triggers diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index ff14da5..d442013 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -5,6 +5,8 @@ #include +constexpr char Iid::separator[]; + Iid::Iid(const std::string& local, const std::string& server, Iid::Type type): type(type), local(local), diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index a857ae9..44861c1 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -54,7 +54,7 @@ public: User, Server, }; - static constexpr auto separator = "%"; + static constexpr char separator[]{"%"}; Iid(const std::string& iid, const std::set& chantypes); Iid(const std::string& iid, const std::initializer_list& chantypes); Iid(const std::string& iid, const Bridge* bridge); -- cgit v1.2.3 From 45aebb8d8a3088058ae65b154496ce1fb2e3d94d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 3 Oct 2016 21:12:16 +0200 Subject: Avoid an exception due to some bad logic in the DNS resolution mechanic fix #3207 --- louloulibs/network/dns_socket_handler.cpp | 3 ++- louloulibs/network/poller.cpp | 5 +++++ louloulibs/network/poller.hpp | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/louloulibs/network/dns_socket_handler.cpp b/louloulibs/network/dns_socket_handler.cpp index 5fd08cb..403a5be 100644 --- a/louloulibs/network/dns_socket_handler.cpp +++ b/louloulibs/network/dns_socket_handler.cpp @@ -42,7 +42,8 @@ bool DNSSocketHandler::is_connected() const void DNSSocketHandler::remove_from_poller() { - this->poller->remove_socket_handler(this->socket); + if (this->poller->is_managing_socket(this->socket)) + this->poller->remove_socket_handler(this->socket); } #endif /* CARES_FOUND */ diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp index 8a6fd97..d341bb5 100644 --- a/louloulibs/network/poller.cpp +++ b/louloulibs/network/poller.cpp @@ -226,3 +226,8 @@ size_t Poller::size() const { return this->socket_handlers.size(); } + +bool Poller::is_managing_socket(const socket_t socket) const +{ + return (this->socket_handlers.find(socket) != this->socket_handlers.end()); +} diff --git a/louloulibs/network/poller.hpp b/louloulibs/network/poller.hpp index fc1a1a1..e39e438 100644 --- a/louloulibs/network/poller.hpp +++ b/louloulibs/network/poller.hpp @@ -74,6 +74,10 @@ public: * Returns the number of SocketHandlers managed by the poller. */ size_t size() const; + /** + * Whether the given socket is managed by the poller + */ + bool is_managing_socket(const socket_t socket) const; private: /** -- cgit v1.2.3 From 28f1dd76548fc9a7de3920d938903f68cdfffe0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 3 Oct 2016 21:35:16 +0200 Subject: Make version requests work with global user JIDs as well fix #3210 --- src/bridge/bridge.cpp | 2 +- src/xmpp/biboumi_component.cpp | 3 ++- tests/end_to_end/__main__.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 65171a9..7724ba7 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -713,7 +713,7 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std if (irc_hostname != hostname) return false; IrcUser user(message.prefix); - if (message.command == "NOTICE" && user.nick == target && + if (message.command == "NOTICE" && utils::tolower(user.nick) == utils::tolower(target) && message.arguments.size() >= 2 && message.arguments[1].substr(0, 9) == "\01VERSION ") { // remove the "\01VERSION " and the "\01" parts from the string diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f43b5e0..1e66b62 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -412,7 +412,8 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) else if ((query = stanza.get_child("query", VERSION_NS))) { Iid iid(to.local, bridge); - if (iid.type != Iid::Type::Server && !to.resource.empty()) + if ((iid.type == Iid::Type::Channel && !to.resource.empty()) || + (iid.type == Iid::Type::User)) { // Get the IRC user version std::string target; diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index f4decff..25cf29c 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -999,6 +999,36 @@ if __name__ == '__main__': partial(expect_stanza, "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_version']"), ]), + Scenario("version_on_global_nick", + [ + partial(log_message, "Joining the channel"), + handshake_sequence(), + partial(send_stanza, + ""), + 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())]"), + + partial(log_message, "Send a version request to ourself"), + partial(send_stanza, + ""), + + partial(log_message, "Receive our own request"), + partial(expect_stanza, + "/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'))), + partial(log_message, "Respond to the request"), + partial(send_stanza, + "e2e test1.0Fedora"), + partial(expect_stanza, + "/iq[@from='{lower_nick_one}%{irc_server_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']"), + + ]), Scenario("self_invite", [ handshake_sequence(), -- cgit v1.2.3 From 1d197ff26ce5a88ba851969edb3ea915759c3477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 4 Oct 2016 19:32:45 +0200 Subject: Respond to muc#traffic requests fix #3069 --- louloulibs/xmpp/xmpp_component.hpp | 1 + src/xmpp/biboumi_component.cpp | 26 ++++++++++++++++++++++++++ src/xmpp/biboumi_component.hpp | 5 +++++ tests/end_to_end/__main__.py | 35 ++++++++++++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 4b6f37d..1cb1845 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -32,6 +32,7 @@ #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 diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 1e66b62..b9a8779 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -408,6 +408,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) stanza_error.disable(); } } + else if (iid.type == Iid::Type::Channel) + { + if (node == MUC_TRAFFIC_NS) + { + this->send_irc_channel_muc_traffic_info(id, from, to_str); + } + } } else if ((query = stanza.get_child("query", VERSION_NS))) { @@ -724,6 +731,25 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s this->send_stanza(iq); } +void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["from"] = jid_from; + iq["to"] = jid_to; + + XmlNode query("query"); + query["xmlns"] = DISCO_INFO_NS; + query["node"] = MUC_TRAFFIC_NS; + // We drop all “special” traffic (like xhtml-im, chatstates, etc), so + // don’t include any + iq.add_child(std::move(iq)); + + this->send_stanza(iq); + +} + void BiboumiComponent::send_iq_version_request(const std::string& from, const std::string& jid_to) { diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 77104ed..d5b87e9 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -65,6 +65,11 @@ public: * Send a result IQ with the disco informations regarding IRC server JIDs. */ void send_irc_server_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from); + /** + * Sends the allowed namespaces in MUC message, according to + * http://xmpp.org/extensions/xep-0045.html#impl-service-traffic + */ + void send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to); /** * Send an iq version request */ diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 25cf29c..a4fc239 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -110,6 +110,8 @@ def match(stanza, xpath): tree = lxml.etree.parse(io.StringIO(str(stanza))) matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', 'muc_user': 'http://jabber.org/protocol/muc#user', + 'disco_info': 'http://jabber.org/protocol/disco#info', + 'muc_traffic': 'http://jabber.org/protocol/muc#traffic', 'disco_items': 'http://jabber.org/protocol/disco#items', 'commands': 'http://jabber.org/protocol/commands', 'dataform': 'jabber:x:data', @@ -1463,7 +1465,38 @@ if __name__ == '__main__': "/iq/disco_items:query/rsm:set/rsm:last[text()='#jjj%{irc_server_one}']", "/iq/disco_items:query/rsm:set/rsm:count[text()='10']" )), - ]) + + partial(log_message, "Leaving the 10 channels"), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unavailable']") + ]), + Scenario("muc_traffic_info", + [ + handshake_sequence(), + + partial(send_stanza, + ""), + partial(expect_stanza, "/iq[@type='result']/disco_info:query[@node='{muc_traffic}']"), + ]), + ) failures = 0 -- cgit v1.2.3 From 55daa3e61c04946500c4106cfca7413b6e243435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 4 Oct 2016 19:51:28 +0200 Subject: Update the changelog --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d7a4511..7470528 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,11 +6,14 @@ Version 4.0 remember). The distinction between a JID refering to a channel and a JID refering to a nickname is based on the first character (# or & by default, but this can be customized by the server with the ISUPPORT - extension) + extension). - Save all channel messages into the database, with an ad-hoc option to disable this feature. - When joining a room, biboumi sends an history of the most recents messages found in the database. + - Channel history can be retrieved using Message Archive Management. + - Result Set Management can be used to request only parts of the IRC channel + list. Version 3.0 - 2016-08-03 ======================== -- cgit v1.2.3 From 3620d533ee88a8804317d2745320c0186192ddaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 5 Oct 2016 00:17:22 +0200 Subject: Avoid sending PART command for unjoined channels fix #3205 --- src/bridge/bridge.cpp | 10 +++++++++- src/irc/irc_channel.cpp | 6 ------ src/irc/irc_channel.hpp | 15 +++++++++------ 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 7724ba7..fb7ea42 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -360,7 +360,15 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con const auto resources = this->number_of_resources_in_chan(key); if (resources == 1) { - irc->send_part_command(iid.get_local(), status_message); + // Do not send a PART message if we actually are not in that channel + // or if we already sent a PART but we are just waiting for the + // acknowledgment from the server + IrcChannel* channel = irc->get_channel(iid.get_local()); + if (channel->joined && !channel->parting) + { + irc->send_part_command(iid.get_local(), status_message); + channel->parting = true; + } // Since there are no resources left in that channel, we don't // want to receive private messages using this room's JID this->remove_all_preferred_from_jid_of_room(iid.get_local()); diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index e769245..40d7f54 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -1,12 +1,6 @@ #include #include -IrcChannel::IrcChannel(): - joined(false), - self(nullptr) -{ -} - void IrcChannel::set_self(const std::string& name) { this->self = std::make_unique(name); diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index 2bcefaf..7c269b9 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -14,16 +14,19 @@ class IrcChannel { public: - explicit IrcChannel(); + IrcChannel() = default; IrcChannel(const IrcChannel&) = delete; IrcChannel(IrcChannel&&) = delete; IrcChannel& operator=(const IrcChannel&) = delete; IrcChannel& operator=(IrcChannel&&) = delete; - bool joined; - std::string topic; - std::string topic_author; + bool joined{false}; + // Set to true if we sent a PART but didn’t yet receive the PART ack from + // the server + bool parting{false}; + std::string topic{}; + std::string topic_author{}; void set_self(const std::string& name); IrcUser* get_self() const; IrcUser* add_user(const std::string& name, @@ -35,8 +38,8 @@ public: { return this->users; } protected: - std::unique_ptr self; - std::vector> users; + std::unique_ptr self{}; + std::vector> users{}; }; /** -- cgit v1.2.3 From 92c99bb9dd1e431de000f085e0c6c05565fee650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 6 Oct 2016 23:53:22 +0200 Subject: Remove a branch that execute identical code in both cases fix coverity CID 134469 --- louloulibs/network/dns_handler.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp index e267944..fef0cfc 100644 --- a/louloulibs/network/dns_handler.cpp +++ b/louloulibs/network/dns_handler.cpp @@ -46,11 +46,7 @@ void DNSHandler::destroy() void DNSHandler::gethostbyname(const std::string& name, ares_host_callback callback, void* data, int family) { - if (family == AF_INET) - ::ares_gethostbyname(this->channel, name.data(), family, - callback, data); - else - ::ares_gethostbyname(this->channel, name.data(), family, + ::ares_gethostbyname(this->channel, name.data(), family, callback, data); } -- cgit v1.2.3 From 954d271d509356ab8042976b9add577150254b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 6 Oct 2016 23:54:39 +0200 Subject: Fix the argument of strerror after bind() fix coverity CID 134470 --- louloulibs/network/tcp_socket_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 5420b1c..967fefe 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -80,7 +80,7 @@ void TCPSocketHandler::init_socket(const struct addrinfo* rp) } if (!rp) log_error("Failed to bind socket to ", this->bind_addr, ": ", - strerror(bind_error)); + strerror(errno)); else log_info("Socket successfully bound to ", this->bind_addr); } -- cgit v1.2.3 From 5477db7999b00cc6525ea964dbd51049b6fa4c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 6 Oct 2016 22:00:13 +0200 Subject: Add a coverity job that runs cov-build and submit the archive to coverity.com --- .gitlab-ci.yml | 12 +++++++++++- docker/biboumi-test/fedora/Dockerfile | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dff5a27..7731b2c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -101,4 +101,14 @@ test:debian: test:fedora: stage: test image: biboumi-test-fedora:latest - <<: *basic_test \ No newline at end of file + <<: *basic_test + +test:coverity: + stage: test + image: biboumi-test-fedora:latest + script: + - export PATH=$PATH:~/coverity/bin + - cmake .. -DWITHOUT_SYSTEMD=1 + - cov-build --dir cov-int make biboumi test_suite -j$(nproc) + - tar czvf biboumi_coverity.tgz cov-int + - curl --form token=$COVERITY_TOKEN --form email=louiz@louiz.org --form file=@biboumi_coverity.tgz --form version="$(git rev-parse --short HEAD)" --form description="Automatic submission by gitlab-ci" https://scan.coverity.com/builds?project=louiz%2Fbiboumi diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile index 5370627..fb6dab4 100644 --- a/docker/biboumi-test/fedora/Dockerfile +++ b/docker/biboumi-test/fedora/Dockerfile @@ -59,6 +59,8 @@ RUN rm -rf /charybdis RUN su - tester -c "echo export LANG=en_GB.utf-8 >> /home/tester/.bashrc" +COPY coverity /home/tester/coverity + WORKDIR /home/tester USER tester -- cgit v1.2.3 From 0049b3e32d1d65acb4314208ddfdd52728d17162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 6 Oct 2016 22:23:04 +0200 Subject: Remove a potential nullptr dereference, on mam queryid fix coverity CID 153376 --- src/xmpp/biboumi_component.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index b9a8779..49a1fd5 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -598,9 +598,8 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), -1, start, end); for (const db::MucLogLine& line: lines) { - const auto queryid = query->get_tag("queryid"); if (!line.nick.value().empty()) - this->send_archived_message(line, to.full(), from.full(), queryid); + this->send_archived_message(line, to.full(), from.full(), query_id); } this->send_iq_result_full_jid(id, from.full(), to.full()); return true; @@ -617,7 +616,8 @@ void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, con XmlNode result("result"); result["xmlns"] = MAM_NS; - result["queryid"] = queryid; + if (!queryid.empty()) + result["queryid"] = queryid; result["id"] = log_line.uuid.value(); XmlNode forwarded("forwarded"); -- cgit v1.2.3 From b7f02f79fa21ad7fda17f0498d84f4fc24a543e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 6 Oct 2016 22:26:59 +0200 Subject: Add a coverity badge to the readme --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 172717e..22ae8d0 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,10 @@ Biboumi .. image:: https://lab.louiz.org/louiz/biboumi/badges/master/coverage.svg :target: https://lab.louiz.org/louiz/biboumi/commits/master - + +.. image:: https://scan.coverity.com/projects/3726/badge.svg + :target: https://scan.coverity.com/projects/louiz-biboumi + Biboumi is an XMPP gateway that connects to IRC servers and translates between the two protocols. It can be used to access IRC channels using any XMPP client as if these channels were XMPP MUCs. -- cgit v1.2.3 From 6ca8cd505f0b21ea50d4124babdf57fe7d7add24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 7 Oct 2016 23:25:26 +0200 Subject: Cleanup __main__.py a little bit --- tests/end_to_end/__main__.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index a4fc239..b2aea37 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -91,11 +91,12 @@ class XMPPComponent(slixmpp.BaseXMPP): self.run_scenario() def run_scenario(self): - if scenario.steps: - step = scenario.steps.pop(0) + if self.scenario.steps: + step = self.scenario.steps.pop(0) step(self, self.biboumi) else: - self.biboumi.stop() + if self.biboumi: + self.biboumi.stop() @asyncio.coroutine def accept_routine(self): @@ -194,10 +195,11 @@ class ProcessRunner: class BiboumiRunner(ProcessRunner): - def __init__(self, name, with_valgrind): + def __init__(self, name): super().__init__() self.name = name self.fd = open("biboumi_%s_output.txt" % (name,), "w") + with_valgrind = os.environ.get("E2E_WITH_VALGRIND") is not None if with_valgrind: self.create = asyncio.create_subprocess_exec("valgrind", "--suppressions=" + (os.environ.get("E2E_BIBOUMI_SUPP_DIR") or "") + "biboumi.supp", "--leak-check=full", "--show-leak-kinds=all", "--errors-for-leak-kinds=all", "--error-exitcode=16", @@ -248,7 +250,8 @@ class BiboumiTest: self.scenario = scenario self.expected_code = expected_code - def run(self, with_valgrind=True): + def run(self): + with_valgrind = os.environ.get("E2E_WITH_VALGRIND") is not None print("Running scenario: %s%s" % (self.scenario.name, " (with valgrind)" if with_valgrind else '')) # Redirect the slixmpp logging into a specific file output_filename = "slixmpp_%s_output.txt" % (self.scenario.name,) @@ -259,7 +262,7 @@ class BiboumiTest: filename=output_filename) with open("test.conf", "w") as fd: - fd.write(confs[scenario.conf]) + fd.write(confs[self.scenario.conf]) try: os.remove("e2e_test.sqlite") @@ -267,7 +270,7 @@ class BiboumiTest: pass # Start the XMPP component and biboumi - biboumi = BiboumiRunner(scenario.name, with_valgrind) + biboumi = BiboumiRunner(self.scenario.name) xmpp = XMPPComponent(self.scenario, biboumi) asyncio.get_event_loop().run_until_complete(biboumi.start()) @@ -276,7 +279,7 @@ class BiboumiTest: xmpp.process() code = asyncio.get_event_loop().run_until_complete(biboumi.wait()) xmpp.biboumi = None - scenario.steps.clear() + self.scenario.steps.clear() failed = False if not xmpp.failed: if code != self.expected_code: @@ -1516,11 +1519,11 @@ if __name__ == '__main__': print("irc server started.") print("Running %s checks for biboumi." % (len(scenarios))) - for scenario in scenarios: - test = BiboumiTest(scenario) - if not test.run(os.getenv("E2E_BIBOUMI_VALGRIND") is not None): + for s in scenarios: + test = BiboumiTest(s) + if not test.run(): print("You can check the files slixmpp_%s_output.txt and biboumi_%s_output.txt to help you debug." % - (scenario.name, scenario.name)) + (s.name, s.name)) failures += 1 print("Waiting for irc server to exit…") -- cgit v1.2.3 From 8cf292fa446e26012cf4a8ff186105d8e762f79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 7 Oct 2016 23:26:44 +0200 Subject: e2e: add expec_unordered to be able to test things even if the arrive in a different order Also, some tests that were raising some exception and thus were not ran at all fix #3213 --- tests/end_to_end/__main__.py | 192 +++++++++++++++++++++++-------------------- 1 file changed, 103 insertions(+), 89 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index b2aea37..a104ded 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -137,6 +137,29 @@ def check_xpath(xpaths, xmpp, after, stanza): else: after(stanza, xmpp) +def all_xpaths_match(stanza, xpaths): + for xpath in xpaths: + matched = match(stanza, xpath) + if not matched: + return False + return True + +def check_list_of_xpath(list_of_xpaths, xmpp, stanza): + found = None + for i, xpaths in enumerate(list_of_xpaths): + if all_xpaths_match(stanza, xpaths): + found = i + break + + if found is None: + raise StanzaError("Received stanza “%s” did not match any of the expected xpaths:\n%s" % (stanza, list_of_xpaths)) + + list_of_xpaths.pop(i) + + if list_of_xpaths: + step = partial(expect_unordered_already_formatted, list_of_xpaths) + xmpp.scenario.steps.insert(0, step) + def check_xpath_optional(xpaths, xmpp, after, stanza): try: @@ -233,6 +256,21 @@ def expect_stanza(xpaths, xmpp, biboumi, optional=False, after=None): else: print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) +# list_of_xpaths: [(xpath, xpath), (xpath, xpath), (xpath)] +def expect_unordered(list_of_xpaths, xmpp, biboumi): + formatted_list_of_xpaths = [] + for xpaths in list_of_xpaths: + formatted_xpaths = [] + for xpath in xpaths: + formatted_xpath = xpath.format_map(common_replacements) + formatted_xpaths.append(formatted_xpath) + formatted_list_of_xpaths.append(tuple(formatted_xpaths)) + # formatted_list_of_xpaths = [tuple(xpath.format_map(common_replacements) for xpath in xpaths) for xpaths in list_of_xpaths] + + expect_unordered_already_formatted(formatted_list_of_xpaths, xmpp, biboumi) + +def expect_unordered_already_formatted(formatted_list_of_xpaths, xmpp, biboumi): + xmpp.stanza_checker = partial(check_list_of_xpath, formatted_list_of_xpaths, xmpp) def log_message(message, xmpp, biboumi): print("%s" % (message,)) @@ -479,24 +517,13 @@ if __name__ == '__main__': partial(send_stanza, ""), connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - # Our presence, sent to the other user - partial(log_message, - "Our presence sent to the other user"), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), - # The other user presence - partial(log_message, - "The other user presence"), - partial(expect_stanza, - "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']"), - # Our own presence - partial(log_message, - "Our own presence"), - partial(expect_stanza, - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + partial(expect_unordered, [ + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant'][@jid='~bobby@localhost']",), + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']",), + ("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",), + ]), ]), Scenario("channel_custom_topic", [ @@ -527,7 +554,7 @@ if __name__ == '__main__': ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), # The other user presence partial(expect_stanza, - "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']"), + "/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"), # Our own presence partial(expect_stanza, ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", @@ -636,28 +663,29 @@ if __name__ == '__main__': partial(send_stanza, ""), # We receive our own join - 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_unordered, + [("/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']"), + ("/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]",)] ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), # A different user joins the same room partial(send_stanza, ""), connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']",)), - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']",)), + partial(expect_unordered, [ + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']",), + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']",) + ] + ), - partial(expect_stanza, - "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"), - partial(expect_stanza, - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), + partial(expect_unordered, [ + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']",), + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']",), + ] + ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), # That second user sends a private message to the first one @@ -679,8 +707,6 @@ if __name__ == '__main__': # 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']"), - - ]), Scenario("channel_messages", [ @@ -692,7 +718,7 @@ if __name__ == '__main__': 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'][@jid='~nick@localhost'][@role='moderator']", + ("/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())]"), @@ -702,23 +728,21 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), # Our presence, sent to the other user - partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), - # The other user presence - partial(expect_stanza, - "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']"), - # Our own presence - partial(expect_stanza, - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", - "/presence/muc_user:x/muc_user:status[@code='110']") - ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + partial(expect_unordered, [ + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",), + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + ("/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]",) + ]), # Send a channel message partial(send_stanza, "coucou"), # Receive the message, forwarded to the two users - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + partial(expect_unordered, [ + ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']",), + ("/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='coucou']",) + ]), # Send a private message, to a in-room JID partial(send_stanza, "coucou in private"), @@ -851,34 +875,30 @@ if __name__ == '__main__': partial(send_stanza, ""), connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - partial(expect_stanza, - "/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), - - partial(expect_stanza, - "/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"), - partial(expect_stanza, - "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, "/message/subject"), + partial(expect_unordered, [ + ("/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), + ("/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), + ("/presence/muc_user:x/muc_user:status[@code='110']",), + ("/message/subject",), + ]), # Moderator kicks participant partial(log_message, "Moderator kicks participant"), partial(send_stanza, "reported"), partial(log_message, "Presence is sent to everyone"), - partial(expect_stanza, - ("/presence[@type='unavailable'][@to='{jid_second}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + partial(expect_unordered, [ + ("/presence[@type='unavailable'][@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", "/presence/muc_user:x/muc_user:status[@code='307']", "/presence/muc_user:x/muc_user:status[@code='110']" - )), - partial(expect_stanza, - ("/presence[@type='unavailable']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + ), + ("/presence[@type='unavailable'][@to='{jid_one}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", "/presence/muc_user:x/muc_user:status[@code='307']", ), - ), - partial(expect_stanza, - "/iq[@id='kick1'][@type='result']"), + ("/iq[@id='kick1'][@type='result']",), + ]), ]), Scenario("multisession_kick", [ @@ -891,18 +911,16 @@ if __name__ == '__main__': partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), partial(expect_stanza, "/message[@type='groupchat']/subject"), - # Second user joins, from two resources + # Second user joins, fprom two resources partial(send_stanza, ""), connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - partial(expect_stanza, - "/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), - - partial(expect_stanza, - "/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"), - partial(expect_stanza, - "/presence/muc_user:x/muc_user:status[@code='110']"), - partial(expect_stanza, "/message/subject"), + partial(expect_unordered, [ + ("/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), + ("/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",), + ("/presence/muc_user:x/muc_user:status[@code='110']",), + ("/message/subject",), + ]), partial(send_stanza, ""), @@ -919,26 +937,23 @@ if __name__ == '__main__': partial(send_stanza, "reported"), partial(log_message, "Unavailable presence is sent to the two resources"), - partial(expect_stanza, - ("/presence[@type='unavailable'][@to='{jid_second}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + partial(expect_unordered, [ + ("/presence[@type='unavailable'][@to='{jid_two}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", "/presence/muc_user:x/muc_user:status[@code='307']", "/presence/muc_user:x/muc_user:status[@code='110']" - )), - partial(expect_stanza, - ("/presence[@type='unavailable'][@to='{jid_second}/{resource_two}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + ), + ("/presence[@type='unavailable'][@to='{jid_two}/{resource_two}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", "/presence/muc_user:x/muc_user:status[@code='307']", "/presence/muc_user:x/muc_user:status[@code='110']" - )), - partial(expect_stanza, + ), ("/presence[@type='unavailable']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", "/presence/muc_user:x/muc_user:status[@code='307']", ), - ), - partial(expect_stanza, - "/iq[@id='kick1'][@type='result']"), + ("/iq[@id='kick1'][@type='result']",), + ]), ]), Scenario("self_version", [ @@ -1094,7 +1109,7 @@ if __name__ == '__main__': 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'][@jid='~nick@localhost'][@role='moderator']", + ("/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())]"), @@ -1156,7 +1171,7 @@ if __name__ == '__main__': partial(expect_stanza, "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo@{biboumi_host}/{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@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), @@ -1496,10 +1511,9 @@ if __name__ == '__main__': handshake_sequence(), partial(send_stanza, - ""), - partial(expect_stanza, "/iq[@type='result']/disco_info:query[@node='{muc_traffic}']"), + ""), + partial(expect_stanza, "/iq[@type='result']/disco_info:query[@node='http://jabber.org/protocol/muc#traffic']"), ]), - ) failures = 0 -- cgit v1.2.3 From 8ac8d2b2425d19eb995a36efa808b664979e358f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 7 Oct 2016 23:28:40 +0200 Subject: Correctly set status="110" in the presence for the target of a kick --- louloulibs/xmpp/xmpp_component.cpp | 13 ++++++++----- louloulibs/xmpp/xmpp_component.hpp | 7 ++----- src/bridge/bridge.cpp | 5 +++-- src/bridge/bridge.hpp | 3 ++- src/irc/irc_client.cpp | 5 +++-- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index b10479a..1cf3e85 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -509,11 +509,8 @@ void XmppComponent::send_nick_change(const std::string& muc_name, this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self); } -void XmppComponent::kick_user(const std::string& muc_name, - const std::string& target, - const std::string& txt, - const std::string& author, - const std::string& jid_to) +void XmppComponent::kick_user(const std::string& muc_name, const std::string& target, const std::string& txt, + const std::string& author, const std::string& jid_to, const bool self) { Stanza presence("presence"); presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; @@ -535,6 +532,12 @@ void XmppComponent::kick_user(const std::string& muc_name, XmlNode status("status"); status["code"] = "307"; x.add_child(std::move(status)); + if (self) + { + XmlNode status("status"); + status["code"] = "110"; + x.add_child(std::move(status)); + } presence.add_child(std::move(x)); this->send_stanza(presence); } diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 1cb1845..232d47a 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -164,11 +164,8 @@ public: /** * 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); + 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 */ diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index fb7ea42..ab42876 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -916,10 +916,11 @@ size_t Bridge::active_clients() const return this->irc_clients.size(); } -void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author) +void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author, + const bool self) { for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) - this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource); + this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource, self); } void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 208de32..b2432f0 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -181,7 +181,8 @@ public: const std::string& new_nick, const char user_mode, const bool self); - void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author); + void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author, + const bool self); void send_nickname_conflict_error(const Iid& iid, const std::string& nickname); /** * Send a role/affiliation change, matching the change of mode for that user diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 85fdec5..c301af0 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -964,14 +964,15 @@ void IrcClient::on_kick(const IrcMessage& message) IrcChannel* channel = this->get_channel(chan_name); if (!channel->joined) return ; - if (channel->get_self()->nick == target) + const bool self = channel->get_self()->nick == target; + if (self) channel->joined = false; IrcUser author(message.prefix); Iid iid; iid.set_local(chan_name); iid.set_server(this->hostname); iid.type = Iid::Type::Channel; - this->bridge.kick_muc_user(std::move(iid), target, reason, author.nick); + this->bridge.kick_muc_user(std::move(iid), target, reason, author.nick, self); } void IrcClient::on_invite(const IrcMessage& message) -- cgit v1.2.3 From 116472920ce4bcd4c9512db67108cdbf9895e8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 7 Oct 2016 23:29:14 +0200 Subject: Fix the muc#traffic response Was completely broken, and the test was just useless --- src/xmpp/biboumi_component.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 49a1fd5..86bef2d 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -389,7 +389,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) const XmlNode* query; if ((query = stanza.get_child("query", DISCO_INFO_NS))) { // Disco info - Iid iid(to.local, {}); + Iid iid(to.local, {'#', '&'}); const std::string node = query->get_tag("node"); if (to_str == this->served_hostname) { @@ -413,6 +413,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) if (node == MUC_TRAFFIC_NS) { this->send_irc_channel_muc_traffic_info(id, from, to_str); + stanza_error.disable(); } } } @@ -744,7 +745,7 @@ void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, c query["node"] = MUC_TRAFFIC_NS; // We drop all “special” traffic (like xhtml-im, chatstates, etc), so // don’t include any - iq.add_child(std::move(iq)); + iq.add_child(std::move(query)); this->send_stanza(iq); -- cgit v1.2.3 From 548e4ad473e7be22f971184312cc5ce9b8fe56b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 11 Oct 2016 00:18:48 +0200 Subject: Parse the timezone myself, instead of using the broken strptime See https://lab.louiz.org/louiz/biboumi/issues/3215 https://github.com/andikleen/glibc/blob/master/time/strptime_l.c#L746-L747 for why strptime() sucks We use std::get_time now, to parse the date and time. And we parse the timezone by hand. fix #3215 --- louloulibs/utils/time.cpp | 44 +++++++++++++++++++++++++++++++++++++++----- tests/utils.cpp | 16 ++++++++++++++-- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/louloulibs/utils/time.cpp b/louloulibs/utils/time.cpp index abf0a84..7ad6663 100644 --- a/louloulibs/utils/time.cpp +++ b/louloulibs/utils/time.cpp @@ -1,6 +1,10 @@ #include #include +#include +#include +#include + namespace utils { std::string to_string(const std::time_t& timestamp) @@ -14,12 +18,42 @@ std::string to_string(const std::time_t& timestamp) std::time_t parse_datetime(const std::string& stamp) { - auto stamp2 = stamp.substr(0, stamp.size() - 1) + "z"; - struct tm tm; - if (!::strptime(stamp2.data(), "%FT%T%Z", &tm)) + static const char* format = "%Y-%m-%dT%H:%M:%S"; + std::tm t = {}; + std::istringstream ss(stamp); + ss.imbue(std::locale("en_US.utf-8")); + + std::string timezone; + ss >> std::get_time(&t, format) >> timezone; + if (ss.fail()) + return -1; + + if (timezone.empty()) return -1; - auto res = ::timegm(&tm); - return res; + + if (timezone.compare(0, 1, "Z") != 0) + { + std::stringstream tz_ss; + tz_ss << timezone; + int multiplier = -1; + char prefix; + int hours; + char sep; + int minutes; + tz_ss >> prefix >> hours >> sep >> minutes; + if (tz_ss.fail()) + return -1; + if (prefix == '-') + multiplier = +1; + else if (prefix != '+') + return -1; + + t.tm_hour += multiplier * hours; + t.tm_min += multiplier * minutes; + } + return ::timegm(&t); } } + + diff --git a/tests/utils.cpp b/tests/utils.cpp index a18fc81..48951da 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -124,7 +124,19 @@ TEST_CASE("time_to_string") TEST_CASE("parse_datetime") { - CHECK(utils::parse_datetime("1970-01-01T00:00:00z") == 0); + CHECK(utils::parse_datetime("1970-01-01T00:00:00Z") == 0); + + const int twenty_three_hours = 82800; + CHECK(utils::parse_datetime("1970-01-01T23:00:12Z") == twenty_three_hours + 12); + CHECK(utils::parse_datetime("1970-01-01T23:00:12Z") == utils::parse_datetime("1970-01-01T23:00:12+00:00")); + CHECK(utils::parse_datetime("1970-01-01T23:00:12Z") == utils::parse_datetime("1970-01-01T23:00:12-00:00")); + CHECK(utils::parse_datetime("1970-01-02T00:00:12Z") == utils::parse_datetime("1970-01-01T23:00:12-01:00")); + CHECK(utils::parse_datetime("1970-01-02T00:00:12Z") == utils::parse_datetime("1970-01-02T01:00:12+01:00")); + CHECK(utils::parse_datetime("2016-08-29T14:29:29Z") == 1472480969); + CHECK(utils::parse_datetime("blah") == -1); -} \ No newline at end of file + CHECK(utils::parse_datetime("1970-01-02T00:00:12B") == -1); + CHECK(utils::parse_datetime("1970-01-02T00:00:12*00:00") == -1); + CHECK(utils::parse_datetime("1970-01-02T00:00:12+0000") == -1); +} -- cgit v1.2.3 From dfc0793ef2fec12d2613b53b27f1a7f85dae2688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 11 Oct 2016 00:43:46 +0200 Subject: Include a private and no-copy nodes in private to avoid carbon duplication --- louloulibs/xmpp/xmpp_component.cpp | 15 ++++++++++++++- louloulibs/xmpp/xmpp_component.hpp | 5 ++--- src/bridge/bridge.cpp | 6 +++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 1cf3e85..6690567 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -273,7 +273,8 @@ void* XmppComponent::get_receive_buffer(const size_t size) const return this->parser.get_buffer(size); } -void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, const std::string& type, const bool fulljid) +void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, + const std::string& type, const bool fulljid, const bool nocopy) { XmlNode node("message"); node["to"] = to; @@ -294,6 +295,18 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con html.add_child(std::move(std::get<1>(body))); node.add_child(std::move(html)); } + + if (nocopy) + { + XmlNode private_node("private"); + private_node["xmlns"] = "urn:xmpp:carbons:2"; + node.add_child(std::move(private_node)); + + XmlNode nocopy("no-copy"); + nocopy["xmlns"] = "urn:xmpp:hints"; + node.add_child(std::move(nocopy)); + } + this->send_stanza(node); } diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 232d47a..45a4038 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -109,9 +109,8 @@ public: * 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=false); + 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 */ diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ab42876..9300d45 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -771,13 +771,13 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st 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); + this->user_jid + "/" + resource, "chat", true, true); } else { for (const auto& resource: this->resources_in_server[iid.get_server()]) this->xmpp.send_message(std::to_string(iid), this->make_xmpp_body(body, encoding), - this->user_jid + "/" + resource, "chat", false); + this->user_jid + "/" + resource, "chat", false, true); } } } @@ -835,7 +835,7 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho 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"); + this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, false); } } -- cgit v1.2.3 From c54f28d29d5f1d7a2bb973609beffbe5ad56d422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 11 Oct 2016 21:00:56 +0200 Subject: =?UTF-8?q?Conditionally=20use=20strptime=20if=20we=20don=E2=80=99?= =?UTF-8?q?t=20have=20std::get=5Ftime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- louloulibs/CMakeLists.txt | 13 +++++++++++++ louloulibs/louloulibs.h.cmake | 3 ++- louloulibs/utils/time.cpp | 11 +++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/louloulibs/CMakeLists.txt b/louloulibs/CMakeLists.txt index bf53504..1858bb3 100644 --- a/louloulibs/CMakeLists.txt +++ b/louloulibs/CMakeLists.txt @@ -143,4 +143,17 @@ if(SYSTEMD_FOUND) target_link_libraries(xmpplib ${SYSTEMD_LIBRARIES}) endif() +# +## Check if we have std::get_time +# +include(CheckCXXSourceCompiles) + +check_cxx_source_compiles(" + #include + int main() + { std::get_time(nullptr, \"\"); }" + HAS_GET_TIME) + +mark_as_advanced(HAS_GET_TIME) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/louloulibs.h.cmake ${CMAKE_BINARY_DIR}/src/louloulibs.h) diff --git a/louloulibs/louloulibs.h.cmake b/louloulibs/louloulibs.h.cmake index 2feaf4e..d5328b8 100644 --- a/louloulibs/louloulibs.h.cmake +++ b/louloulibs/louloulibs.h.cmake @@ -6,4 +6,5 @@ #cmakedefine BOTAN_FOUND #cmakedefine CARES_FOUND #cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}" -#cmakedefine PROJECT_NAME "${PROJECT_NAME}" \ No newline at end of file +#cmakedefine PROJECT_NAME "${PROJECT_NAME}" +#cmakedefine HAS_GET_TIME diff --git a/louloulibs/utils/time.cpp b/louloulibs/utils/time.cpp index 7ad6663..afd6117 100644 --- a/louloulibs/utils/time.cpp +++ b/louloulibs/utils/time.cpp @@ -5,6 +5,8 @@ #include #include +#include "louloulibs.h" + namespace utils { std::string to_string(const std::time_t& timestamp) @@ -20,6 +22,7 @@ std::time_t parse_datetime(const std::string& stamp) { static const char* format = "%Y-%m-%dT%H:%M:%S"; std::tm t = {}; +#ifdef HAS_GET_TIME std::istringstream ss(stamp); ss.imbue(std::locale("en_US.utf-8")); @@ -27,6 +30,14 @@ std::time_t parse_datetime(const std::string& stamp) ss >> std::get_time(&t, format) >> timezone; if (ss.fail()) return -1; +#else + /* Y - m - d T H : M : S */ + constexpr std::size_t stamp_size_without_tz = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2; + if (!strptime(stamp.data(), format, &t)) { + return -1; + } + const std::string timezone(stamp.data() + stamp_size_without_tz); +#endif if (timezone.empty()) return -1; -- cgit v1.2.3 From 5f2e4820df51374ddd61b68abf35f9ee75f5a117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 12 Oct 2016 20:51:46 +0200 Subject: Fix an off-by-one issue in the POLL code --- louloulibs/network/poller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp index d341bb5..9868236 100644 --- a/louloulibs/network/poller.cpp +++ b/louloulibs/network/poller.cpp @@ -95,7 +95,7 @@ void Poller::remove_socket_handler(const socket_t socket) void Poller::watch_send_events(SocketHandler* socket_handler) { #if POLLER == POLL - for (size_t i = 0; i <= this->nfds; ++i) + for (size_t i = 0; i < this->nfds; ++i) { if (this->fds[i].fd == socket_handler->get_socket()) { @@ -171,7 +171,7 @@ int Poller::poll(const std::chrono::milliseconds& timeout) // We cannot possibly have more ready events than the number of fds we are // watching assert(static_cast(nb_events) <= this->nfds); - for (size_t i = 0; i <= this->nfds && nb_events != 0; ++i) + for (size_t i = 0; i < this->nfds && nb_events != 0; ++i) { auto socket_handler = this->socket_handlers.at(this->fds[i].fd); if (this->fds[i].revents == 0) -- cgit v1.2.3 From 827a1eedf8936e90fe25fa851e7a13b1730f37f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 12 Oct 2016 20:53:41 +0200 Subject: On EINPROGRESS, we need to also check for read events (because openBSD lies) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “It is possible to select(2) or poll(2) for completion by selecting the socket for writing” Yeah, sure, “writing”… --- louloulibs/network/poller.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp index 9868236..9f5bcfb 100644 --- a/louloulibs/network/poller.cpp +++ b/louloulibs/network/poller.cpp @@ -186,7 +186,8 @@ int Poller::poll(const std::chrono::milliseconds& timeout) socket_handler->on_send(); nb_events--; } - else if (this->fds[i].revents & POLLOUT) + else if (this->fds[i].revents & POLLOUT || + this->fds[i].revents & POLLIN) { socket_handler->connect(); nb_events--; -- cgit v1.2.3 From c3a7cd36279a123feec6d3665a4ba64b7ac1866b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 12 Oct 2016 20:59:13 +0200 Subject: e2e: the port to use should be an int, not a string --- tests/end_to_end/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index a104ded..1218c89 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -101,7 +101,7 @@ class XMPPComponent(slixmpp.BaseXMPP): @asyncio.coroutine def accept_routine(self): self.accepting_server = yield from self.loop.create_server(lambda: self, - "127.0.0.1", "8811", reuse_address=True) + "127.0.0.1", 8811, reuse_address=True) def check_stanza_against_all_expected_xpaths(self): pass -- cgit v1.2.3 From 44f35a256dd7fc3238ae81f9bd42a42c013dba90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 12 Oct 2016 20:59:37 +0200 Subject: e2e: test the private and no-copy thingy --- tests/end_to_end/__main__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 1218c89..9cad963 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -121,7 +121,9 @@ def match(stanza, xpath): 'delay': 'urn:xmpp:delay', 'forward': 'urn:xmpp:forward:0', 'client': 'jabber:client', - 'rsm': 'http://jabber.org/protocol/rsm'}) + 'rsm': 'http://jabber.org/protocol/rsm', + 'carbon': 'urn:xmpp:carbons:2', + 'hints': 'urn:xmpp:hints'}) return matched @@ -691,7 +693,9 @@ if __name__ == '__main__': # That second user sends a private message to the first one partial(send_stanza, "RELLO"), # 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_one}'][@type='chat']/body[text()='RELLO']", + "/message/hints:no-copy", + "/message/carbon:private")), 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. -- cgit v1.2.3 From 86c927ae17f553b77f9e8fb31cf15335599d4928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 12 Oct 2016 22:18:16 +0200 Subject: ci: Add a test on openbsd --- .gitlab-ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7731b2c..a2d0fc6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -103,6 +103,15 @@ test:fedora: image: biboumi-test-fedora:latest <<: *basic_test +test:openbsd: + variables: + COMPILER: "clang++" + SYSTEMD: "-DWITHOUT_SYSTEMD=1" + stage: test + tags: + - openbsd + <<: *basic_test + test:coverity: stage: test image: biboumi-test-fedora:latest -- cgit v1.2.3 From 34bd8b93da7f7dd570a100b125941eef6331410e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 12 Oct 2016 22:18:53 +0200 Subject: Disable the output in the config unit test --- tests/config.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/config.cpp b/tests/config.cpp index ddea151..a6fa92a 100644 --- a/tests/config.cpp +++ b/tests/config.cpp @@ -1,9 +1,14 @@ #include "catch.hpp" +#include "io_tester.hpp" + +#include #include TEST_CASE("Config basic") { + // Disable all output for this test + IoTester out(std::cout); // Write a value in the config file Config::read_conf("test.cfg"); Config::set("coucou", "bonjour", true); -- cgit v1.2.3 From 7dfc08ca9bed56ce0ea09692e86925e3f665078d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 13 Oct 2016 10:22:37 +0200 Subject: Use g++ for the openbsd tests --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a2d0fc6..9692c5e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -105,7 +105,7 @@ test:fedora: test:openbsd: variables: - COMPILER: "clang++" + COMPILER: "g++" SYSTEMD: "-DWITHOUT_SYSTEMD=1" stage: test tags: -- cgit v1.2.3 From 501a6f26614116e70cdbb1c9633aeefd0a7d5399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 14 Oct 2016 23:51:04 +0200 Subject: Add two e2e tests on adhoc commands --- tests/end_to_end/__main__.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 9cad963..5526e19 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -123,7 +123,8 @@ def match(stanza, xpath): 'client': 'jabber:client', 'rsm': 'http://jabber.org/protocol/rsm', 'carbon': 'urn:xmpp:carbons:2', - 'hints': 'urn:xmpp:hints'}) + 'hints': 'urn:xmpp:hints', + 'stanza': 'urn:ietf:params:xml:ns:xmpp-stanzas'}) return matched @@ -647,6 +648,35 @@ if __name__ == '__main__': partial(send_stanza, "COUCOU"), partial(expect_stanza, "/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']") ]), + Scenario("execute_forbidden_adhoc_command", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='error'][@id='command1']/commands:command[@node='disconnect-user']", + "/iq/commands:command/commands:error[@type='cancel']/stanza:forbidden")), + ]), + Scenario("execute_disconnect_user_adhoc_command", + [ + handshake_sequence(), + + partial(log_message, "Join a channel"), + partial(send_stanza, ""), + connection_sequence("irc.localhost", '{jid_admin}/{resource_one}'), + partial(expect_stanza, "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='disconnect-user'][@sessionid][@status='executing']", + "/iq/commands:command/commands:actions/commands:next", + ), + after = partial(save_value, "sessionid", partial(extract_attribute, "/iq/commands:command[@node='disconnect-user']", "sessionid")) + ), + partial(send_stanza, "{jid_admin}Disconnected by e2e"), + partial(expect_stanza, "/iq[@type='result']/commands:command[@node='disconnect-user'][@status='completed']/commands:note[@type='info'][text()='1 user has been disconnected.']"), + # Note, charybdis ignores our QUIT message, so we can't test it + partial(expect_stanza, "/presence[@type='unavailable'][@to='{jid_admin}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"), + ]), Scenario("multisessionnick", [ handshake_sequence(), -- cgit v1.2.3 From ef36a769a51bdb244570b8452a83dd4658a29079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 15 Oct 2016 01:55:39 +0200 Subject: Fix the indent in the coverage.cmake file [skip-ci] --- cmake/Modules/CodeCoverage.cmake | 122 +++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/cmake/Modules/CodeCoverage.cmake b/cmake/Modules/CodeCoverage.cmake index 4ff5851..c07a3df 100644 --- a/cmake/Modules/CodeCoverage.cmake +++ b/cmake/Modules/CodeCoverage.cmake @@ -47,23 +47,23 @@ # # 3. Set compiler flags to turn off optimization and enable coverage: # SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") -# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") +# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") # # 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target # which runs your test executable and produces a lcov code coverage report: # Example: -# SETUP_TARGET_FOR_COVERAGE( -# my_coverage_target # Name for custom target. -# test_driver # Name of the test driver executable that runs the tests. -# # NOTE! This should always have a ZERO as exit code -# # otherwise the coverage generation will not complete. -# coverage # Name of output directory. -# ) +# SETUP_TARGET_FOR_COVERAGE( +# my_coverage_target # Name for custom target. +# test_driver # Name of the test driver executable that runs the tests. +# # NOTE! This should always have a ZERO as exit code +# # otherwise the coverage generation will not complete. +# coverage # Name of output directory. +# ) # # 4. Build a Debug build: -# cmake -DCMAKE_BUILD_TYPE=Debug .. -# make -# make my_coverage_target +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target # # @@ -76,9 +76,9 @@ FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) # Display an error when the target is called. If no error is found, this # function will be overridden by the real one later in this file FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) - ADD_CUSTOM_TARGET(${_targetname} + ADD_CUSTOM_TARGET(${_targetname} COMMAND echo "Coverage is not available: ${ERROR_MSG}" - ) + ) ENDFUNCTION() IF(NOT GCOV_PATH) @@ -130,50 +130,50 @@ ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" # Param _targetname The name of new the custom make target # Param _testrunner The name of the target which runs the tests. -# MUST return ZERO always, even on errors. -# If not, no coverage report will be created! +# MUST return ZERO always, even on errors. +# If not, no coverage report will be created! # Param _outputname lcov output is generated as _outputname.info # HTML report is generated in _outputname/index.html # Optional fifth parameter is passed as arguments to _testrunner # Pass them in list form, e.g.: "-j;2" for -j 2 FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) - # Setup target - ADD_CUSTOM_TARGET(${_targetname} + # Setup target + ADD_CUSTOM_TARGET(${_targetname} - # Cleanup lcov - COMMAND ${LCOV_PATH} --directory . --zerocounters + # Cleanup lcov + COMMAND ${LCOV_PATH} --directory . --zerocounters - # Create baseline coverage data file - COMMAND ${LCOV_PATH} -c -i -d . -o ${_outputname}.baseline.info -q + # Create baseline coverage data file + COMMAND ${LCOV_PATH} -c -i -d . -o ${_outputname}.baseline.info -q - # Run tests - COMMAND ${_testrunner} ${ARGV3} + # Run tests + COMMAND ${_testrunner} ${ARGV3} - # Capturing lcov counters and generating report - COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info -q - # Combine the baseline and the test data - COMMAND ${LCOV_PATH} -a ${_outputname}.info -a ${_outputname}.baseline.info -o ${_outputname}.info -q + # Capturing lcov counters and generating report + COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info -q + # Combine the baseline and the test data + COMMAND ${LCOV_PATH} -a ${_outputname}.info -a ${_outputname}.baseline.info -o ${_outputname}.info -q - # Remove information about source files that are not part of - # the test (system file, external libraries, etc) - COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'tests/*' '/usr/*' 'external/*' 'build/*' --output-file ${_outputname}.info.cleaned -q + # Remove information about source files that are not part of + # the test (system file, external libraries, etc) + COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'tests/*' '/usr/*' 'external/*' 'build/*' --output-file ${_outputname}.info.cleaned -q - # Generate the report - COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned + # Generate the report + COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned - # Clean the temporary files we created - COMMAND ${CMAKE_COMMAND} -E remove ${_outputname}.info ${_outputname}.info.cleaned + # Clean the temporary files we created + COMMAND ${CMAKE_COMMAND} -E remove ${_outputname}.info ${_outputname}.info.cleaned - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." - ) + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) - # Show info where to find the report - ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD - COMMAND ; - COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." - ) + # Show info where to find the report + ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD + COMMAND ; + COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." + ) ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE @@ -184,30 +184,30 @@ ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE # Pass them in list form, e.g.: "-j;2" for -j 2 FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname) - IF(NOT PYTHON_EXECUTABLE) - MESSAGE(FATAL_ERROR "Python not found! Aborting...") - ENDIF() # NOT PYTHON_EXECUTABLE + IF(NOT PYTHON_EXECUTABLE) + MESSAGE(FATAL_ERROR "Python not found! Aborting...") + ENDIF() # NOT PYTHON_EXECUTABLE - IF(NOT GCOVR_PATH) - MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") - ENDIF() # NOT GCOVR_PATH + IF(NOT GCOVR_PATH) + MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") + ENDIF() # NOT GCOVR_PATH MARK_AS_ADVANCED(GCOVR_PATH) - ADD_CUSTOM_TARGET(${_targetname} + ADD_CUSTOM_TARGET(${_targetname} - # Run tests - ${_testrunner} ${ARGV3} + # Run tests + ${_testrunner} ${ARGV3} - # Running gcovr - COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Running gcovr to produce Cobertura code coverage report." - ) + # Running gcovr + COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) - # Show info where to find the report - ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD - COMMAND ; - COMMENT "Cobertura code coverage report saved in ${_outputname}.xml." - ) + # Show info where to find the report + ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${_outputname}.xml." + ) ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA -- cgit v1.2.3 From 2a0f5f1b6a92540cebb38340683a9713309a8d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 17 Oct 2016 19:48:51 +0200 Subject: Add tests for the nick change, and the nick conflict --- tests/end_to_end/__main__.py | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 5526e19..6942073 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -268,8 +268,6 @@ def expect_unordered(list_of_xpaths, xmpp, biboumi): formatted_xpath = xpath.format_map(common_replacements) formatted_xpaths.append(formatted_xpath) formatted_list_of_xpaths.append(tuple(formatted_xpaths)) - # formatted_list_of_xpaths = [tuple(xpath.format_map(common_replacements) for xpath in xpaths) for xpaths in list_of_xpaths] - expect_unordered_already_formatted(formatted_list_of_xpaths, xmpp, biboumi) def expect_unordered_already_formatted(formatted_list_of_xpaths, xmpp, biboumi): @@ -367,6 +365,7 @@ common_replacements = { 'jid_two': 'second@example.com', 'jid_admin': 'admin@example.com', 'nick_two': 'Bobby', + 'nick_three': 'Bernard', 'lower_nick_one': 'nick', 'lower_nick_two': 'bobby', } @@ -728,16 +727,49 @@ if __name__ == '__main__': "/message/carbon:private")), partial(expect_stanza, "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']"), + + partial(log_message, "Nickname conflict"), + # First occupant (with the two resources) changes her/his nick + partial(send_stanza, ""), + partial(expect_unordered, [ + ("/message[@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='irc.localhost: Bobby: Nickname is already in use.']",), + ("/message[@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='irc.localhost: Bobby: Nickname is already in use.']",), + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']",), + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}'][@type='error']",), + ]), + + # partial(log_message, "Nickname change"), + # First occupant (with the two resources) changes her/his nick + partial(send_stanza, ""), + partial(expect_unordered, [ + ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']", + "/presence/muc_user:x/muc_user:status[@code='303']"), + ("/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_two}/{resource_one}']",), + ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + ("/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_one}']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + + ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_two}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bernard']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + ("/presence[@from='#foo%{irc_server_one}/{nick_three}'][@to='{jid_one}/{resource_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + ]), + # One resource leaves the server entirely. partial(send_stanza, ""), # The leave is forwarded only to us partial(expect_stanza, ("/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']", - "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']") + "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']", + ) ), + # The second user sends two new private messages to the first user - partial(send_stanza, "first"), - partial(send_stanza, "second"), + partial(send_stanza, "first"), + partial(send_stanza, "second"), # 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']"), -- cgit v1.2.3 From 6b4d2e8e3ea6a019778624106b7a839d875152cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 17 Oct 2016 19:49:45 +0200 Subject: Use expect_unordered in a few more places --- tests/end_to_end/__main__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 6942073..d04238e 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -707,16 +707,13 @@ if __name__ == '__main__': partial(expect_unordered, [ ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']",), - ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']",) + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']",), + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']",), + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']",), ] ), - partial(expect_unordered, [ - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']",), - ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", - "/presence/muc_user:x/muc_user:status[@code='110']",), - ] - ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), # That second user sends a private message to the first one @@ -771,8 +768,11 @@ if __name__ == '__main__': partial(send_stanza, "first"), partial(send_stanza, "second"), # 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_unordered, [ + ("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='first']",), + ("/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='second']",), + ]), ]), Scenario("channel_messages", [ -- cgit v1.2.3 From ce06c25e93183282be42ab79bfed2ab7c02791ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 20 Oct 2016 19:32:20 +0200 Subject: Very little optimization by using a simpler scope_guard when possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The version with the vector, that can be disabled etc, is “very” slow, so we use unique_ptr when we don’t need to disable it, and when it only contains one function --- louloulibs/network/tcp_socket_handler.cpp | 2 -- louloulibs/utils/encoding.cpp | 8 ++++---- louloulibs/utils/scopeguard.hpp | 7 +++++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 967fefe..ca267cd 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -103,8 +103,6 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po this->port = port; this->use_tls = tls; - utils::ScopeGuard sg; - struct addrinfo* addr_res; if (!this->connecting) diff --git a/louloulibs/utils/encoding.cpp b/louloulibs/utils/encoding.cpp index 507f38a..4b20797 100644 --- a/louloulibs/utils/encoding.cpp +++ b/louloulibs/utils/encoding.cpp @@ -76,7 +76,7 @@ namespace utils { // The given string MUST be a valid utf-8 string unsigned char* res = new unsigned char[original.size()]; - ScopeGuard sg([&res]() { delete[] res;}); + const auto sg = utils::make_scope_guard([&res](auto&&) { delete[] res;}); // pointer where we write valid chars unsigned char* r = res; @@ -140,7 +140,7 @@ namespace utils else throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars"); } - return std::string(reinterpret_cast(res), r-res); + return {reinterpret_cast(res), static_cast(r-res)}; } std::string convert_to_utf8(const std::string& str, const char* charset) @@ -152,7 +152,7 @@ namespace utils throw std::runtime_error("Cannot convert into UTF-8"); // Make sure cd is always closed when we leave this function - ScopeGuard sg([&]{ iconv_close(cd); }); + const auto sg = utils::make_scope_guard([&](auto&&){ iconv_close(cd); }); size_t inbytesleft = str.size(); @@ -169,7 +169,7 @@ namespace utils char* outbuf_ptr = outbuf; // Make sure outbuf is always deleted when we leave this function - sg.add_callback([&]{ delete[] outbuf; }); + const auto sg2 = utils::make_scope_guard([&](auto&&){ delete[] outbuf; }); bool done = false; while (done == false) diff --git a/louloulibs/utils/scopeguard.hpp b/louloulibs/utils/scopeguard.hpp index ee1e2ef..cd0e89e 100644 --- a/louloulibs/utils/scopeguard.hpp +++ b/louloulibs/utils/scopeguard.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include /** @@ -85,5 +86,11 @@ private: }; +template +auto make_scope_guard(F&& f) +{ + return std::unique_ptr>{(void*)1, std::forward(f)}; +} + } -- cgit v1.2.3 From aa4255224eb19ca55a963574fb527e1f07ff9cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 20 Oct 2016 20:08:29 +0200 Subject: Optimize tcp_socket::on_send by using vector::erase() only once per call --- louloulibs/network/tcp_socket_handler.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index ca267cd..9d8cfea 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -305,23 +305,24 @@ void TCPSocketHandler::on_send() else { // remove all the strings that were successfully sent. - for (auto it = this->out_buf.begin(); - it != this->out_buf.end();) + auto it = this->out_buf.begin(); + while (it != this->out_buf.end()) { - if (static_cast(res) >= (*it).size()) + if (static_cast(res) >= it->size()) { - res -= (*it).size(); - it = this->out_buf.erase(it); + res -= it->size(); + ++it; } else { // If one string has partially been sent, we use substr to // crop it if (res > 0) - (*it) = (*it).substr(res, std::string::npos); + *it = it->substr(res, std::string::npos); break; } } + this->out_buf.erase(this->out_buf.begin(), it); if (this->out_buf.empty()) this->poller->stop_watching_send_events(this); } -- cgit v1.2.3 From 52f0aca24671c3f3b1e037f988e61e0851088c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 21 Oct 2016 00:05:21 +0200 Subject: Coverity upload is allowed to fail and is manual. Also name the artifacts --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9692c5e..978bd6b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -76,6 +76,7 @@ build:rpm: - build/rpmbuild/RPMS - build/rpmbuild/SRPMS when: always + name: $CI_PROJECT_NAME-rpm-$CI_BUILD_ID .template:basic_test: &basic_test @@ -92,6 +93,7 @@ build:rpm: - build/coverage_e2e/ - build/tests_outputs/ when: always + name: $CI_PROJECT_NAME-rpm-$CI_BUILD_ID test:debian: stage: test @@ -115,6 +117,8 @@ test:openbsd: test:coverity: stage: test image: biboumi-test-fedora:latest + allow_failure: true + when: manual script: - export PATH=$PATH:~/coverity/bin - cmake .. -DWITHOUT_SYSTEMD=1 -- cgit v1.2.3 From 479708c83eeea96986692ccf85cf06944a1ca919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 20 Oct 2016 18:55:18 +0200 Subject: Add an other kewl badge --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 22ae8d0..f1aefa3 100644 --- a/README.rst +++ b/README.rst @@ -10,6 +10,9 @@ Biboumi .. image:: https://scan.coverity.com/projects/3726/badge.svg :target: https://scan.coverity.com/projects/louiz-biboumi +.. image:: https://bestpractices.coreinfrastructure.org/projects/450/badge + :target: https://bestpractices.coreinfrastructure.org/projects/450 + Biboumi is an XMPP gateway that connects to IRC servers and translates between the two protocols. It can be used to access IRC channels using any XMPP client as if these channels were XMPP MUCs. -- cgit v1.2.3 From 4388b9c31122f98c4291f8fba5a0d3524902b8d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 21 Oct 2016 01:00:52 +0200 Subject: Remove an unused variable in e2e --- tests/end_to_end/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d04238e..56d7a6d 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -129,7 +129,7 @@ def match(stanza, xpath): def check_xpath(xpaths, xmpp, after, stanza): - for i, xpath in enumerate(xpaths): + for xpath in enumerate(xpaths): matched = match(stanza, xpath) if not matched: raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) -- cgit v1.2.3 From a4d67ce041f50e0d25e2b47d04cc25bdad86a048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 21 Oct 2016 01:05:15 +0200 Subject: Use ensure_future instead of async --- tests/end_to_end/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 56d7a6d..4a42820 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -57,7 +57,7 @@ class XMPPComponent(slixmpp.BaseXMPP): self.add_event_handler("session_end", self.on_end_session) - asyncio.async(self.accept_routine()) + asyncio.ensure_future(self.accept_routine()) self.scenario = scenario self.biboumi = biboumi -- cgit v1.2.3 From 82a6fa6d098c82914ff8a6ed44398eb0cf8c4d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 21 Oct 2016 01:05:38 +0200 Subject: e2e: Fix some logic in check_list_of_xpath --- tests/end_to_end/__main__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 4a42820..fedfa6a 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -151,14 +151,13 @@ def check_list_of_xpath(list_of_xpaths, xmpp, stanza): found = None for i, xpaths in enumerate(list_of_xpaths): if all_xpaths_match(stanza, xpaths): - found = i + found = True + list_of_xpaths.pop(i) break - if found is None: + if not found: raise StanzaError("Received stanza “%s” did not match any of the expected xpaths:\n%s" % (stanza, list_of_xpaths)) - list_of_xpaths.pop(i) - if list_of_xpaths: step = partial(expect_unordered_already_formatted, list_of_xpaths) xmpp.scenario.steps.insert(0, step) -- cgit v1.2.3 From ac32d257386fe1c8df2632cf1d6452b1875eb645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 21 Oct 2016 18:50:04 +0200 Subject: Fix the broken commit 4388b9c --- tests/end_to_end/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index fedfa6a..fd515e2 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -129,7 +129,7 @@ def match(stanza, xpath): def check_xpath(xpaths, xmpp, after, stanza): - for xpath in enumerate(xpaths): + for xpath in xpaths: matched = match(stanza, xpath) if not matched: raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) -- cgit v1.2.3 From 8b8bbceeb39b32b90f76a9c0beb3f7d30ed6fd7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 21 Oct 2016 19:12:25 +0200 Subject: Revert "Use ensure_future instead of async" This reverts commit a4d67ce041f50e0d25e2b47d04cc25bdad86a048. --- tests/end_to_end/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index fd515e2..747c8f5 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -57,7 +57,7 @@ class XMPPComponent(slixmpp.BaseXMPP): self.add_event_handler("session_end", self.on_end_session) - asyncio.ensure_future(self.accept_routine()) + asyncio.async(self.accept_routine()) self.scenario = scenario self.biboumi = biboumi -- cgit v1.2.3 From 00eb18bae8cf62d49f4b5d42aed8507fcca3c03c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 23 Oct 2016 23:58:03 +0200 Subject: Refactor channel->parting to a new location --- src/bridge/bridge.cpp | 5 +---- src/irc/irc_client.cpp | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 9300d45..ac69ebc 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -365,10 +365,7 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con // acknowledgment from the server IrcChannel* channel = irc->get_channel(iid.get_local()); if (channel->joined && !channel->parting) - { - irc->send_part_command(iid.get_local(), status_message); - channel->parting = true; - } + irc->send_part_command(iid.get_local(), status_message); // Since there are no resources left in that channel, we don't // want to receive private messages using this room's JID this->remove_all_preferred_from_jid_of_room(iid.get_local()); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index c301af0..9877806 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -502,6 +502,7 @@ void IrcClient::send_part_command(const std::string& chan_name, const std::strin this->leave_dummy_channel(status_message); else this->send_message(IrcMessage("PART", {chan_name, status_message})); + channel->parting = true; } } -- cgit v1.2.3 From c3bb9fe2e2c2a0b2773e9b9824c4e675448b862f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 23 Oct 2016 23:59:21 +0200 Subject: Handle forced-join by just sending an invitation fix #3116 --- src/bridge/bridge.cpp | 15 +++++++++++++-- src/xmpp/biboumi_component.cpp | 5 ++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ac69ebc..67eb805 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -839,8 +839,19 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, const IrcUser* user, const char user_mode, const bool self) { - for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) - this->send_user_join(hostname, chan_name, user, user_mode, self, resource); + const auto resources = this->resources_in_chan[ChannelKey{chan_name, hostname}]; + if (self && resources.empty()) + { // This was a forced join: no client ever asked to join this room, + // but the server tells us we are in that room anyway. XMPP can’t + // do that, so we invite all the resources to join that channel. + const Iid iid(chan_name, hostname, Iid::Type::Channel); + this->send_xmpp_invitation(iid, ""); + } + else + { + for (const auto& resource: resources) + this->send_user_join(hostname, chan_name, user, user_mode, self, resource); + } } void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 86bef2d..f3405df 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -855,7 +855,10 @@ void BiboumiComponent::send_invitation(const std::string& room_target, XmlNode x("x"); x["xmlns"] = MUC_USER_NS; XmlNode invite("invite"); - invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick; + if (author_nick.empty()) + invite["from"] = room_target + "@" + this->served_hostname; + else + invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick; x.add_child(std::move(invite)); message.add_child(std::move(x)); this->send_stanza(message); -- cgit v1.2.3 From 5990a8bf8ae622f075a7e2a12b2f5ac0cab8713b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 24 Oct 2016 00:18:34 +0200 Subject: Correctly handle the nick change inside the virtual channel --- src/irc/irc_client.cpp | 51 ++++++++++++++++++++++++++------------------ tests/end_to_end/__main__.py | 34 ++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 9877806..6a87b99 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -931,29 +931,38 @@ void IrcClient::on_quit(const IrcMessage& message) void IrcClient::on_nick(const IrcMessage& message) { - const std::string new_nick = message.arguments[0]; + const std::string new_nick = IrcUser(message.arguments[0]).nick; + const std::string current_nick = IrcUser(message.prefix).nick; + const auto change_nick_func = [&](const std::string& chan_name, const IrcChannel* channel) + { + IrcUser* user; + if (channel->get_self() && channel->get_self()->nick == current_nick) + user = channel->get_self(); + else + user = channel->find_user(current_nick); + if (user) + { + std::string old_nick = user->nick; + Iid iid(chan_name, this->hostname, 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); + user->nick = new_nick; + if (self) + { + channel->get_self()->nick = new_nick; + this->current_nick = new_nick; + } + } + }; + + if (this->get_dummy_channel().joined) + { + change_nick_func("", &this->get_dummy_channel()); + } for (auto it = this->channels.begin(); it != this->channels.end(); ++it) { - const std::string chan_name = it->first; - IrcChannel* channel = it->second.get(); - IrcUser* user = channel->find_user(message.prefix); - if (user) - { - std::string old_nick = user->nick; - Iid iid; - iid.set_local(chan_name); - iid.set_server(this->hostname); - 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); - user->nick = new_nick; - if (self) - { - channel->get_self()->nick = new_nick; - this->current_nick = new_nick; - } - } + change_nick_func(it->first, it->second.get()); } } diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 747c8f5..890043f 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -482,7 +482,7 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), ]), - Scenario("virtual_channel_join", + Scenario("virtual_channel", [ handshake_sequence(), partial(send_stanza, @@ -494,6 +494,38 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='%{irc_server_one}'][@type='groupchat']/subject[re:test(text(), '^This is a virtual channel.*$')]"), connection_end_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable'][@from='%{irc_server_one}/{nick_one}']"), + partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Closing Link: localhost (Client Quit)']"), + partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Connection closed.']"), + ]), + Scenario("irc_server_disconnection", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='%{irc_server_one}'][@type='groupchat']/subject[re:test(text(), '^This is a virtual channel.*$')]"), + connection_end_sequence("irc.localhost", '{jid_one}/{resource_one}'), + + partial(send_stanza, ""), + + partial(expect_unordered, [ + ("/presence[@from='%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']", + "/presence/muc_user:x/muc_user:status[@code='303']"), + ("/presence[@from='%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + ]), + + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable'][@from='%{irc_server_one}/{nick_two}']"), + partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Closing Link: localhost (Client Quit)']"), + partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Connection closed.']"), ]), Scenario("channel_join_with_two_users", [ -- cgit v1.2.3 From 7b2215b8824295efaa77eea61a4d4eed4cd77ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 24 Oct 2016 19:01:52 +0200 Subject: Add a CONTRIBUTING file --- CONTRIBUTING.rst | 44 ++++++++++++++++++++++++++++++++++++++++++++ README.rst | 9 ++------- 2 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 CONTRIBUTING.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..f80b4a9 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,44 @@ +Contributing to biboumi +======================= + +Biboumi’s main workplace is at https://lab.louiz.org/louiz/biboumi + +The repository is also mirrored on other websites, for example on github, but +that’s mainly for the convenience of users. + +Before doing anything, you can come on the `XMPP chatroom`_ to discuss your +changes, issues or ideas. + +Bug reports, feature requests +----------------------------- +To open a bug report, or a feature request, please do so on +`our gitlab’s bug tracker`_. + +If the issue you’re reporting may have security implications, please select +the “confidential” flag in your bug report. + + +Code +---- +To contribute code, you can do so using git: commit your changes on any +publicly available git repository and communicate us its address. This +can be done with a `gitlab merge request`_, or a `github pull request`_ +or just by sending a message into the `XMPP chatroom`_. + + +Coding style +------------ +Please try to follow the existing style: + +- Use only spaces, not tabs. +- Curly brackets are on their own lines. +- Use this-> everywhere it’s possible. +- Don’t start class attributes with “m_” or similar. +- Type names are in PascalCase. +- Everything else is in snake_case. + + +.. _our gitlab’s bug tracker: https://lab.louiz.org/louiz/biboumi/issues/new +.. _gitlab merge request: https://lab.louiz.org/louiz/biboumi/merge_requests/new +.. _github pull request: https://github.com/louiz/biboumi/pulls +.. _XMPP chatroom: xmpp:biboumi@muc.poez.io diff --git a/README.rst b/README.rst index f1aefa3..fad4206 100644 --- a/README.rst +++ b/README.rst @@ -52,13 +52,7 @@ Contact/Support * XMPP ChatRoom: biboumi@muc.poez.io * Report a bug: https://lab.louiz.org/louiz/biboumi/issues/new -To contribute, the preferred way is to commit your changes on some -publicly-available git repository (a fork on gitlab or github or on your own -repository) and to notify the developers with a ticket on the bug tracker, -or a merge request on gitlab or a pull request on github. - -Optionally you can come discuss your changes on the XMPP chat room, -beforehand. +Also, see the `contributing`_ page. Licence @@ -71,3 +65,4 @@ Please read the COPYING file for details. .. _INSTALL: INSTALL.rst .. _the documentation: doc/biboumi.1.rst +.. _contributing: CONTRIBUTING.rst -- cgit v1.2.3 From 00a2e8dd706e523ca0d6543d2c4b604b4ed3cdcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 24 Oct 2016 19:22:46 +0200 Subject: Suggest adding tests when contributing --- CONTRIBUTING.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f80b4a9..dfafa89 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -25,6 +25,12 @@ publicly available git repository and communicate us its address. This can be done with a `gitlab merge request`_, or a `github pull request`_ or just by sending a message into the `XMPP chatroom`_. +It is suggested that you use gitlab’s merge requests: this will automatically +run our continuous integration tests. + +It is also recommended to add some unit or end-to-end tests for the prosposed +changes. + Coding style ------------ -- cgit v1.2.3 From fed21b7e9f3d08af7b4c30e5c75e2fabf2e80542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 24 Oct 2016 10:23:09 +0200 Subject: ci: Rename the test archive --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 978bd6b..484cdc0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -93,7 +93,7 @@ build:rpm: - build/coverage_e2e/ - build/tests_outputs/ when: always - name: $CI_PROJECT_NAME-rpm-$CI_BUILD_ID + name: $CI_PROJECT_NAME-test-$CI_BUILD_ID test:debian: stage: test -- cgit v1.2.3 From 4359680f9df92475c439f16a163bf5a9b37813b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 24 Oct 2016 11:20:35 +0200 Subject: Fix test encoding by setting LANG and LC_ALL to C.UTF-8 --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 484cdc0..29124eb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,8 @@ before_script: - uname -a - whoami + - export LC_ALL=C.UTF-8 + - export LANG=C.UTF-8 - echo $LANG - g++ --version - clang++ --version -- cgit v1.2.3 From b141f3ddeeb4d3cb66eca0a919595c3c661af2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 24 Oct 2016 11:33:06 +0200 Subject: Explicitely use the docker runner on some builds --- .gitlab-ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 29124eb..b975134 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,6 +26,8 @@ variables: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi -j$(nproc) - make check -j$(nproc) + tags: + - docker image: biboumi-test-fedora:latest @@ -70,6 +72,9 @@ build:6: build:rpm: stage: build + image: biboumi-test-fedora:latest + tags: + - docker script: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make rpm -j$(nproc) @@ -100,11 +105,15 @@ build:rpm: test:debian: stage: test image: biboumi-test-debian:latest + tags: + - docker <<: *basic_test test:fedora: stage: test image: biboumi-test-fedora:latest + tags: + - docker <<: *basic_test test:openbsd: -- cgit v1.2.3 From 5ffd976d7f36d7db530c1a518e4a5911404b4ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 25 Oct 2016 01:17:58 +0200 Subject: CONTRIBUTING.rst formatting and a typo --- CONTRIBUTING.rst | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index dfafa89..ed3915f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -3,16 +3,17 @@ Contributing to biboumi Biboumi’s main workplace is at https://lab.louiz.org/louiz/biboumi -The repository is also mirrored on other websites, for example on github, but -that’s mainly for the convenience of users. +The repository is also mirrored on other websites, for example on github, +but that’s mainly for the convenience of users. Before doing anything, you can come on the `XMPP chatroom`_ to discuss your changes, issues or ideas. Bug reports, feature requests ----------------------------- -To open a bug report, or a feature request, please do so on -`our gitlab’s bug tracker`_. + +To open a bug report, or a feature request, please do so on `our gitlab’s +bug tracker`_. If the issue you’re reporting may have security implications, please select the “confidential” flag in your bug report. @@ -20,15 +21,16 @@ the “confidential” flag in your bug report. Code ---- + To contribute code, you can do so using git: commit your changes on any -publicly available git repository and communicate us its address. This -can be done with a `gitlab merge request`_, or a `github pull request`_ -or just by sending a message into the `XMPP chatroom`_. +publicly available git repository and communicate us its address. This can +be done with a `gitlab merge request`_, or a `github pull request`_ or just +by sending a message into the `XMPP chatroom`_. -It is suggested that you use gitlab’s merge requests: this will automatically -run our continuous integration tests. +It is suggested that you use gitlab’s merge requests: this will +automatically run our continuous integration tests. -It is also recommended to add some unit or end-to-end tests for the prosposed +It is also recommended to add some unit or end-to-end tests for the proposed changes. -- cgit v1.2.3 From 3fdae6b6249dd2727578fba8bf84e3a7593d9009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 27 Oct 2016 00:19:56 +0200 Subject: Add a new badge to our collection! (Sonarqube) --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index fad4206..bb3adfc 100644 --- a/README.rst +++ b/README.rst @@ -3,10 +3,13 @@ Biboumi .. image:: https://lab.louiz.org/louiz/biboumi/badges/master/build.svg :target: https://lab.louiz.org/louiz/biboumi/commits/master - + .. image:: https://lab.louiz.org/louiz/biboumi/badges/master/coverage.svg :target: https://lab.louiz.org/louiz/biboumi/commits/master +.. image:: https://sonarqube.com/api/badges/gate?key=biboumi + :target: https://sonarqube.com/component_issues/index?id=biboumi + .. image:: https://scan.coverity.com/projects/3726/badge.svg :target: https://scan.coverity.com/projects/louiz-biboumi -- cgit v1.2.3 From 021f025cb039011ad07158b0d94f1b430a409e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 26 Oct 2016 21:19:53 +0200 Subject: Refactor the sha1 digest into its own function, and do not use sprintf --- louloulibs/xmpp/auth.cpp | 21 +++++++++++++++++++++ louloulibs/xmpp/auth.hpp | 6 ++++++ louloulibs/xmpp/xmpp_component.cpp | 17 +++-------------- tests/xmpp.cpp | 7 +++++++ 4 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 louloulibs/xmpp/auth.cpp create mode 100644 louloulibs/xmpp/auth.hpp diff --git a/louloulibs/xmpp/auth.cpp b/louloulibs/xmpp/auth.cpp new file mode 100644 index 0000000..c20f95d --- /dev/null +++ b/louloulibs/xmpp/auth.cpp @@ -0,0 +1,21 @@ +#include + +#include + +#include +#include + +std::string get_handshake_digest(const std::string& stream_id, const std::string& secret) +{ + sha1nfo sha1; + sha1_init(&sha1); + sha1_write(&sha1, stream_id.data(), stream_id.size()); + sha1_write(&sha1, secret.data(), secret.size()); + const uint8_t* result = sha1_result(&sha1); + + std::ostringstream digest; + for (int i = 0; i < HASH_LENGTH; i++) + digest << std::hex << std::setfill('0') << std::setw(2) << static_cast(result[i]); + + return digest.str(); +} diff --git a/louloulibs/xmpp/auth.hpp b/louloulibs/xmpp/auth.hpp new file mode 100644 index 0000000..34a2116 --- /dev/null +++ b/louloulibs/xmpp/auth.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include + +std::string get_handshake_digest(const std::string& stream_id, const std::string& secret); + diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 6690567..17fde20 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -5,15 +5,14 @@ #include #include -#include -#include #include +#include +#include #include #include #include -#include #include #include @@ -139,17 +138,7 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) } // Try to authenticate - char digest[HASH_LENGTH * 2 + 1]; - sha1nfo sha1; - sha1_init(&sha1); - sha1_write(&sha1, this->stream_id.data(), this->stream_id.size()); - sha1_write(&sha1, this->secret.data(), this->secret.size()); - const uint8_t* result = sha1_result(&sha1); - for (int i=0; i < HASH_LENGTH; i++) - sprintf(digest + (i*2), "%02x", result[i]); - digest[HASH_LENGTH * 2] = '\0'; - - auto data = ""s + digest + ""; + auto data = ""s + get_handshake_digest(this->stream_id, this->secret) + ""; log_debug("XMPP SENDING: ", data); this->send_data(std::move(data)); } diff --git a/tests/xmpp.cpp b/tests/xmpp.cpp index 6aab8c4..37d2b54 100644 --- a/tests/xmpp.cpp +++ b/tests/xmpp.cpp @@ -1,6 +1,7 @@ #include "catch.hpp" #include +#include TEST_CASE("Test basic XML parsing") { @@ -45,3 +46,9 @@ TEST_CASE("XML escape") const std::string unescaped = "'coucou'/&\"gaga\""; CHECK(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); } + +TEST_CASE("handshake_digest") +{ + const auto res = get_handshake_digest("id1234", "S4CR3T"); + CHECK(res == "c92901b5d376ad56269914da0cce3aab976847df"); +} -- cgit v1.2.3 From 6d5d7eff6835ff0dbeca8d84bfadee127918c3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 27 Oct 2016 01:15:26 +0200 Subject: Directly use Botan::byte instead of char, to avoid an unnecessary cast --- louloulibs/network/tcp_socket_handler.cpp | 14 +++++++------- louloulibs/network/tcp_socket_handler.hpp | 5 +---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 9d8cfea..1adbaac 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -417,15 +417,14 @@ void TCPSocketHandler::start_tls() void TCPSocketHandler::tls_recv() { static constexpr size_t buf_size = 4096; - char recv_buf[buf_size]; + Botan::byte recv_buf[buf_size]; const ssize_t size = this->do_recv(recv_buf, buf_size); if (size > 0) { const bool was_active = this->tls->is_active(); try { - this->tls->received_data(reinterpret_cast(recv_buf), - static_cast(size)); + this->tls->received_data(recv_buf, static_cast(size)); } catch (const Botan::TLS::TLS_Exception& e) { // May happen if the server sends malformed TLS data (buggy server, // or more probably we are just connected to a server that sends @@ -448,9 +447,8 @@ void TCPSocketHandler::tls_send(std::string&& data) const bool was_active = this->tls->is_active(); if (!this->pre_buf.empty()) { - this->tls->send(reinterpret_cast(this->pre_buf.data()), - this->pre_buf.size()); - this->pre_buf = ""; + this->tls->send(this->pre_buf.data(), this->pre_buf.size()); + this->pre_buf.clear(); } if (!data.empty()) this->tls->send(reinterpret_cast(data.data()), @@ -459,7 +457,9 @@ void TCPSocketHandler::tls_send(std::string&& data) this->on_tls_activated(); } else - this->pre_buf += data; + this->pre_buf.insert(this->pre_buf.end(), + std::make_move_iterator(data.begin()), + std::make_move_iterator(data.end())); } void TCPSocketHandler::tls_data_cb(const Botan::byte* data, size_t size) diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index b0ba493..7bbe4d4 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -266,9 +266,6 @@ private: * An additional buffer to keep data that the user wants to send, but * cannot because the handshake is not done. */ - std::string pre_buf; + std::vector pre_buf; #endif // BOTAN_FOUND }; - - - -- cgit v1.2.3 From 6d65e9527e90dda7b76168be23690dfead73c847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 27 Oct 2016 01:16:04 +0200 Subject: Remove calls to INFO() in catch, they are useless --- tests/encoding.cpp | 2 -- tests/timed_events.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/tests/encoding.cpp b/tests/encoding.cpp index 389cf23..b5192ff 100644 --- a/tests/encoding.cpp +++ b/tests/encoding.cpp @@ -11,7 +11,6 @@ TEST_CASE("UTF-8 validation") CHECK_FALSE(utils::is_valid_utf8("\xFE\xFE\xFF\xFF")); std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; - INFO(in); CHECK(utils::is_valid_utf8(in.data())); } @@ -49,7 +48,6 @@ TEST_CASE("Remove invalid XML chars") { std::string without_ctrl_char("𤭢€¢$"); std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; - INFO(in); CHECK(utils::remove_invalid_xml_chars(without_ctrl_char) == without_ctrl_char); CHECK(utils::remove_invalid_xml_chars(in) == in); CHECK(utils::remove_invalid_xml_chars("\acouco\u0008u\uFFFEt\uFFFFe\r\n♥") == "coucoute\r\n♥"); diff --git a/tests/timed_events.cpp b/tests/timed_events.cpp index d63abef..fece422 100644 --- a/tests/timed_events.cpp +++ b/tests/timed_events.cpp @@ -38,7 +38,6 @@ TEST_CASE("Test timed event expiration") CHECK(TimedEventsManager::instance().execute_expired_events() == 0); std::chrono::milliseconds timoute = TimedEventsManager::instance().get_timeout(); - INFO("Sleeping for " << timoute.count() << "ms"); std::this_thread::sleep_for(timoute + 1ms); // Event is now expired -- cgit v1.2.3 From ac61450184112ccb22971cff6cfa6117b4ddfbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 27 Oct 2016 01:37:55 +0200 Subject: Refactor remove_invalid_xml_chars to use correct types directly --- louloulibs/utils/encoding.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/louloulibs/utils/encoding.cpp b/louloulibs/utils/encoding.cpp index 4b20797..712028e 100644 --- a/louloulibs/utils/encoding.cpp +++ b/louloulibs/utils/encoding.cpp @@ -75,13 +75,12 @@ namespace utils std::string remove_invalid_xml_chars(const std::string& original) { // The given string MUST be a valid utf-8 string - unsigned char* res = new unsigned char[original.size()]; - const auto sg = utils::make_scope_guard([&res](auto&&) { delete[] res;}); + std::vector res(original.size(), '\0'); // pointer where we write valid chars - unsigned char* r = res; + char* r = res.data(); - const unsigned char* str = reinterpret_cast(original.c_str()); + const char* str = original.c_str(); std::bitset<20> codepoint; while (*str) @@ -140,7 +139,7 @@ namespace utils else throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars"); } - return {reinterpret_cast(res), static_cast(r-res)}; + return {res.data(), static_cast(r - res.data())}; } std::string convert_to_utf8(const std::string& str, const char* charset) -- cgit v1.2.3 From 8c5c217a9e807281632c9feeb6487ace959ab0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 28 Oct 2016 18:55:10 +0200 Subject: =?UTF-8?q?Default=20to=20make=20-j1=20if=20nproc=20doesn=E2=80=99?= =?UTF-8?q?t=20exist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b975134..08cb686 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,8 +24,8 @@ variables: script: - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL}" - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make biboumi -j$(nproc) - - make check -j$(nproc) + - make biboumi -j$(nproc || 1) + - make check -j$(nproc || 1) tags: - docker @@ -77,7 +77,7 @@ build:rpm: - docker script: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make rpm -j$(nproc) + - make rpm -j$(nproc || 1) artifacts: paths: - build/rpmbuild/RPMS @@ -90,10 +90,10 @@ build:rpm: stage: test script: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make biboumi -j$(nproc) + - make biboumi -j$(nproc || 1) - make check - make coverage - - mkdir tests_outputs && pushd tests_outputs && make coverage_e2e -j$(nproc) -C .. && popd + - mkdir tests_outputs && pushd tests_outputs && make coverage_e2e -j$(nproc || 1) -C .. && popd artifacts: paths: - build/coverage/ @@ -133,6 +133,6 @@ test:coverity: script: - export PATH=$PATH:~/coverity/bin - cmake .. -DWITHOUT_SYSTEMD=1 - - cov-build --dir cov-int make biboumi test_suite -j$(nproc) + - cov-build --dir cov-int make biboumi test_suite -j$(nproc || 1) - tar czvf biboumi_coverity.tgz cov-int - curl --form token=$COVERITY_TOKEN --form email=louiz@louiz.org --form file=@biboumi_coverity.tgz --form version="$(git rev-parse --short HEAD)" --form description="Automatic submission by gitlab-ci" https://scan.coverity.com/builds?project=louiz%2Fbiboumi -- cgit v1.2.3 From f7f4e3f638ed8e6cd932eaf761ef4a41cd43a1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 28 Oct 2016 19:07:12 +0200 Subject: Do the previous commit, but correctly --- .gitlab-ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 08cb686..bbdef05 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,8 +24,8 @@ variables: script: - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL}" - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make biboumi -j$(nproc || 1) - - make check -j$(nproc || 1) + - make biboumi -j$(nproc || echo 1) + - make check -j$(nproc || echo 1) tags: - docker @@ -77,7 +77,7 @@ build:rpm: - docker script: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make rpm -j$(nproc || 1) + - make rpm -j$(nproc || echo 1) artifacts: paths: - build/rpmbuild/RPMS @@ -90,10 +90,10 @@ build:rpm: stage: test script: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make biboumi -j$(nproc || 1) + - make biboumi -j$(nproc || echo 1) - make check - make coverage - - mkdir tests_outputs && pushd tests_outputs && make coverage_e2e -j$(nproc || 1) -C .. && popd + - mkdir tests_outputs && pushd tests_outputs && make coverage_e2e -j$(nproc || echo 1) -C .. && popd artifacts: paths: - build/coverage/ @@ -133,6 +133,6 @@ test:coverity: script: - export PATH=$PATH:~/coverity/bin - cmake .. -DWITHOUT_SYSTEMD=1 - - cov-build --dir cov-int make biboumi test_suite -j$(nproc || 1) + - cov-build --dir cov-int make biboumi test_suite -j$(nproc || echo 1) - tar czvf biboumi_coverity.tgz cov-int - curl --form token=$COVERITY_TOKEN --form email=louiz@louiz.org --form file=@biboumi_coverity.tgz --form version="$(git rev-parse --short HEAD)" --form description="Automatic submission by gitlab-ci" https://scan.coverity.com/builds?project=louiz%2Fbiboumi -- cgit v1.2.3 From 0d8a2bfd13ecd9b118f8800531ac68ba8ef8100b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 28 Oct 2016 00:11:17 +0200 Subject: Rename a variable that shadows a class member --- louloulibs/network/resolver.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp index d3ecd7c..2987aaa 100644 --- a/louloulibs/network/resolver.cpp +++ b/louloulibs/network/resolver.cpp @@ -117,12 +117,13 @@ void Resolver::fill_ares_addrinfo4(const struct hostent* hostent) current->ai_protocol = 0; current->ai_addrlen = sizeof(struct sockaddr_in); - struct sockaddr_in* addr = new struct sockaddr_in; - addr->sin_family = hostent->h_addrtype; - addr->sin_port = htons(strtoul(this->port.data(), nullptr, 10)); - addr->sin_addr.s_addr = (*address)->s_addr; + struct sockaddr_in* ai_addr = new struct sockaddr_in; + + ai_addr->sin_family = hostent->h_addrtype; + ai_addr->sin_port = htons(std::strtoul(this->port.data(), nullptr, 10)); + ai_addr->sin_addr.s_addr = (*address)->s_addr; - current->ai_addr = reinterpret_cast(addr); + current->ai_addr = reinterpret_cast(ai_addr); current->ai_next = nullptr; current->ai_canonname = nullptr; @@ -148,14 +149,14 @@ void Resolver::fill_ares_addrinfo6(const struct hostent* hostent) current->ai_protocol = 0; current->ai_addrlen = sizeof(struct sockaddr_in6); - struct sockaddr_in6* addr = new struct sockaddr_in6; - addr->sin6_family = hostent->h_addrtype; - addr->sin6_port = htons(strtoul(this->port.data(), nullptr, 10)); - ::memcpy(addr->sin6_addr.s6_addr, (*address)->s6_addr, 16); - addr->sin6_flowinfo = 0; - addr->sin6_scope_id = 0; + struct sockaddr_in6* ai_addr = new struct sockaddr_in6; + ai_addr->sin6_family = hostent->h_addrtype; + ai_addr->sin6_port = htons(std::strtoul(this->port.data(), nullptr, 10)); + ::memcpy(ai_addr->sin6_addr.s6_addr, (*address)->s6_addr, sizeof(ai_addr->sin6_addr.s6_addr)); + ai_addr->sin6_flowinfo = 0; + ai_addr->sin6_scope_id = 0; - current->ai_addr = reinterpret_cast(addr); + current->ai_addr = reinterpret_cast(ai_addr); current->ai_canonname = nullptr; current->ai_next = prev; -- cgit v1.2.3 From 911258bc65b3855022ee2715454076cf818d7098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 28 Oct 2016 00:11:36 +0200 Subject: Make AddrinfoDeleter a class --- louloulibs/network/resolver.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/louloulibs/network/resolver.hpp b/louloulibs/network/resolver.hpp index afe6e2b..29e6f3a 100644 --- a/louloulibs/network/resolver.hpp +++ b/louloulibs/network/resolver.hpp @@ -11,8 +11,9 @@ #include #include -struct AddrinfoDeleter +class AddrinfoDeleter { + public: void operator()(struct addrinfo* addr) { #ifdef CARES_FOUND -- cgit v1.2.3 From 3e7c8ab4bc1ea15f02dbeee51ca8894bdd70eeab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 28 Oct 2016 00:22:20 +0200 Subject: Trivial cleanup --- louloulibs/network/socket_handler.hpp | 2 +- louloulibs/network/tcp_socket_handler.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/louloulibs/network/socket_handler.hpp b/louloulibs/network/socket_handler.hpp index eeb41fe..ea79a18 100644 --- a/louloulibs/network/socket_handler.hpp +++ b/louloulibs/network/socket_handler.hpp @@ -14,7 +14,7 @@ public: poller(poller), socket(socket) {} - virtual ~SocketHandler() {} + virtual ~SocketHandler() = default; SocketHandler(const SocketHandler&) = delete; SocketHandler(SocketHandler&&) = delete; SocketHandler& operator=(const SocketHandler&) = delete; diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 1adbaac..9decee1 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -292,7 +292,8 @@ void TCPSocketHandler::on_send() // unconsting the content of s is ok, sendmsg will never modify it msg_iov[msg.msg_iovlen].iov_base = const_cast(s.data()); msg_iov[msg.msg_iovlen].iov_len = s.size(); - if (++msg.msg_iovlen == UIO_FASTIOV) + msg.msg_iovlen++; + if (msg.msg_iovlen == UIO_FASTIOV) break; } ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); -- cgit v1.2.3 From 1ff71ca79ef701b0623728ce91c27a0b57a9a85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 00:35:03 +0100 Subject: Test raw messages --- tests/end_to_end/__main__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 890043f..95aa1b6 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1611,6 +1611,18 @@ if __name__ == '__main__': ""), partial(expect_stanza, "/iq[@type='result']/disco_info:query[@node='http://jabber.org/protocol/muc#traffic']"), ]), + Scenario("raw_message", + [ + handshake_sequence(), + partial(send_stanza, ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, "WHOIS {nick_one}"), + partial(expect_stanza, "/message[@from='{irc_server_one}'][@type='chat']/body[text()='irc.localhost: {nick_one} ~{nick_one} localhost * {nick_one}']"), + ]), ) failures = 0 -- cgit v1.2.3 From ec555d05dfff976e5584db7b15768fff0fab448c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 00:40:41 +0100 Subject: e2e: add the possibility to launch a specific list of scenarios only --- tests/end_to_end/__main__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 95aa1b6..d235c02 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1627,6 +1627,7 @@ if __name__ == '__main__': failures = 0 + scenar_list = sys.argv[1:] irc_output = open("irc_output.txt", "w") irc = IrcServerRunner() print("Starting irc server…") @@ -1643,6 +1644,8 @@ if __name__ == '__main__': print("Running %s checks for biboumi." % (len(scenarios))) for s in scenarios: + if s.name not in scenar_list: + continue test = BiboumiTest(s) if not test.run(): print("You can check the files slixmpp_%s_output.txt and biboumi_%s_output.txt to help you debug." % -- cgit v1.2.3 From 1d6aa89ab9656878689d86bd1aa74140756740fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 01:12:39 +0100 Subject: Test self disco --- tests/end_to_end/__main__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d235c02..a591bc9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1623,6 +1623,19 @@ if __name__ == '__main__': partial(send_stanza, "WHOIS {nick_one}"), partial(expect_stanza, "/message[@from='{irc_server_one}'][@type='chat']/body[text()='irc.localhost: {nick_one} ~{nick_one} localhost * {nick_one}']"), ]), + Scenario("self_disco_info", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, + ("/iq[@type='result']/disco_info:query/disco_info:identity[@category='conference'][@type='irc'][@name='Biboumi XMPP-IRC gateway']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", + "/iq/disco_info:query/disco_info:feature[@var='http://jabber.org/protocol/commands']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:ping']", + "/iq/disco_info:query/disco_info:feature[@var='urn:xmpp:mam:1']", + "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", + )), + ]), ) failures = 0 -- cgit v1.2.3 From fdf336afa017c24eb483f741e4037226f8a08401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 01:12:50 +0100 Subject: Use ensure_future if available, otherwise use asyncio.async --- tests/end_to_end/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index a591bc9..c98c3a6 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -13,6 +13,8 @@ import os from functools import partial from slixmpp.xmlstream.matcher.base import MatcherBase +if not hasattr(asyncio, "ensure_future"): + asyncio.ensure_future = getattr(asyncio, "async") class MatchAll(MatcherBase): """match everything""" @@ -57,7 +59,7 @@ class XMPPComponent(slixmpp.BaseXMPP): self.add_event_handler("session_end", self.on_end_session) - asyncio.async(self.accept_routine()) + asyncio.ensure_future(self.accept_routine()) self.scenario = scenario self.biboumi = biboumi -- cgit v1.2.3 From 1d2f20600a937cd00181a2fad055cd9bb56f058e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 01:38:26 +0100 Subject: Handle IRC message 341 (invite confirmation) --- src/irc/irc_client.cpp | 9 +++++++++ src/irc/irc_client.hpp | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 6a87b99..033671e 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -46,6 +46,7 @@ static const std::unordered_mapmotd.erase(); } +void IrcClient::on_invited(const IrcMessage& message) +{ + const std::string& chan_name = message.arguments[2]; + const std::string& invited_nick = message.arguments[1]; + + this->bridge.send_xmpp_message(this->hostname, "", invited_nick + " has been invited to " + chan_name); +} + void IrcClient::on_empty_topic(const IrcMessage& message) { const std::string chan_name = utils::tolower(message.arguments[1]); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 9f53c3c..1b4d892 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -213,6 +213,10 @@ public: * Empty the topic */ void on_empty_topic(const IrcMessage& message); + /** + * The IRC server is confirming that the invitation has been forwarded + */ + void on_invited(const IrcMessage& message); /** * The channel has been completely joined (self presence, topic, all names * received etc), send the self presence and topic to the XMPP user. -- cgit v1.2.3 From db7ac6f13d4cb1f280afcfed7c17533244a1d010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 01:39:04 +0100 Subject: Test successful invite of an other user --- tests/end_to_end/__main__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index c98c3a6..d618fda 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1638,6 +1638,24 @@ if __name__ == '__main__': "/iq/disco_info:query/disco_info:feature[@var='jabber:iq:version']", )), ]), + Scenario("invite_other", + [ + handshake_sequence(), + partial(send_stanza, ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_two}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + partial(send_stanza, ""), + partial(expect_stanza, "/message/body[text()='{nick_two} has been invited to #foo']"), + partial(expect_stanza, "/message[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}']/muc_user:x/muc_user:invite[@from='#foo%{irc_server_one}/{nick_one}']"), + ]), ) failures = 0 -- cgit v1.2.3 From 056bc3da066bbf0345db27ac41bca0433ba4db0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 01:45:09 +0100 Subject: Actually run all the e2e scenario if nothing is specified --- tests/end_to_end/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d618fda..e2a497d 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1677,7 +1677,7 @@ if __name__ == '__main__': print("Running %s checks for biboumi." % (len(scenarios))) for s in scenarios: - if s.name not in scenar_list: + if scenar_list and s.name not in scenar_list: continue test = BiboumiTest(s) if not test.run(): -- cgit v1.2.3 From 8c26b4d19f6d3464050a34c782694059b2a7b013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 02:01:01 +0100 Subject: Remove unused roster code --- louloulibs/xmpp/roster.cpp | 21 -------------- louloulibs/xmpp/roster.hpp | 71 ---------------------------------------------- 2 files changed, 92 deletions(-) delete mode 100644 louloulibs/xmpp/roster.cpp delete mode 100644 louloulibs/xmpp/roster.hpp diff --git a/louloulibs/xmpp/roster.cpp b/louloulibs/xmpp/roster.cpp deleted file mode 100644 index a14a384..0000000 --- a/louloulibs/xmpp/roster.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include - -RosterItem::RosterItem(const std::string& jid, const std::string& name, - std::vector& groups): - jid(jid), - name(name), - groups(groups) -{ -} - -RosterItem::RosterItem(const std::string& jid, const std::string& name): - jid(jid), - name(name), - groups{} -{ -} - -void Roster::clear() -{ - this->items.clear(); -} diff --git a/louloulibs/xmpp/roster.hpp b/louloulibs/xmpp/roster.hpp deleted file mode 100644 index aa1b449..0000000 --- a/louloulibs/xmpp/roster.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - - -#include -#include -#include - -class RosterItem -{ -public: - RosterItem(const std::string& jid, const std::string& name, - std::vector& groups); - RosterItem(const std::string& jid, const std::string& name); - RosterItem() = default; - ~RosterItem() = default; - RosterItem(const RosterItem&) = default; - RosterItem(RosterItem&&) = default; - RosterItem& operator=(const RosterItem&) = default; - RosterItem& operator=(RosterItem&&) = default; - - std::string jid; - std::string name; - std::vector groups; - -private: -}; - -/** - * Keep track of the last known stat of a JID's roster - */ -class Roster -{ -public: - Roster() = default; - ~Roster() = default; - - void clear(); - - template - RosterItem* add_item(ArgsType&&... args) - { - this->items.emplace_back(std::forward(args)...); - auto it = this->items.end() - 1; - return &*it; - } - RosterItem* get_item(const std::string& jid) - { - auto it = std::find_if(this->items.begin(), this->items.end(), - [this, &jid](const auto& item) - { - return item.jid == jid; - }); - if (it != this->items.end()) - return &*it; - return nullptr; - } - const std::vector& get_items() const - { - return this->items; - } - -private: - std::vector items; - - Roster(const Roster&) = delete; - Roster(Roster&&) = delete; - Roster& operator=(const Roster&) = delete; - Roster& operator=(Roster&&) = delete; -}; - - -- cgit v1.2.3 From ae02e58b9dc276b247be84e1d708ca50a1f5bbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 13:54:25 +0100 Subject: Some cleanups --- louloulibs/utils/encoding.cpp | 8 ++------ louloulibs/xmpp/xmpp_component.cpp | 3 +-- src/irc/irc_client.cpp | 2 +- src/main.cpp | 4 ++-- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/louloulibs/utils/encoding.cpp b/louloulibs/utils/encoding.cpp index 712028e..cb953c0 100644 --- a/louloulibs/utils/encoding.cpp +++ b/louloulibs/utils/encoding.cpp @@ -196,12 +196,8 @@ namespace utils outbuf_ptr++; done = true; break; - case E2BIG: - // This should never happen - done = true; - break; - default: - // This should happen even neverer + case E2BIG: // This should never happen + default: // This should happen even neverer done = true; break; } diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 17fde20..3ac617c 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -222,9 +222,8 @@ void XmppComponent::close_document() this->doc_open = false; } -void XmppComponent::handle_handshake(const Stanza& stanza) +void XmppComponent::handle_handshake(const Stanza&) { - (void)stanza; this->authenticated = true; this->ever_auth = true; log_info("Authenticated with the XMPP server"); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 033671e..b0d3a47 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -942,7 +942,7 @@ void IrcClient::on_nick(const IrcMessage& message) { const std::string new_nick = IrcUser(message.arguments[0]).nick; const std::string current_nick = IrcUser(message.prefix).nick; - const auto change_nick_func = [&](const std::string& chan_name, const IrcChannel* channel) + const auto change_nick_func = [this, &new_nick, ¤t_nick](const std::string& chan_name, const IrcChannel* channel) { IrcUser* user; if (channel->get_self() && channel->get_self()->nick == current_nick) diff --git a/src/main.cpp b/src/main.cpp index 53f3193..5585672 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,9 +14,9 @@ #include // A flag set by the SIGINT signal handler. -static volatile std::atomic stop(false); +static std::atomic stop(false); // Flag set by the SIGUSR1/2 signal handler. -static volatile std::atomic reload(false); +static std::atomic reload(false); // A flag indicating that we are wanting to exit the process. i.e: if this // flag is set and all connections are closed, we can exit properly. static bool exiting = false; -- cgit v1.2.3 From 835999d5f54280727bef7b6c853dd77f839681b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 14:09:20 +0100 Subject: ci: Fix the nproc thingy --- .gitlab-ci.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bbdef05..b700cd6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,14 +18,15 @@ variables: SYSTEMD: "-DWITH_SYSTEMD=1" LIBIDN: "-DWITH_LIBIDN=1" LITESQL: "-DWITH_LITESQL=1" + NPROC: "$(nproc)" .template:basic_build: &basic_build stage: build script: - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL}" - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make biboumi -j$(nproc || echo 1) - - make check -j$(nproc || echo 1) + - make biboumi -j${NPROC} + - make check -j${NPROC} tags: - docker @@ -77,7 +78,7 @@ build:rpm: - docker script: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make rpm -j$(nproc || echo 1) + - make rpm -j${NPROC} artifacts: paths: - build/rpmbuild/RPMS @@ -90,10 +91,10 @@ build:rpm: stage: test script: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make biboumi -j$(nproc || echo 1) + - make biboumi -j${NPROC} - make check - make coverage - - mkdir tests_outputs && pushd tests_outputs && make coverage_e2e -j$(nproc || echo 1) -C .. && popd + - mkdir tests_outputs && pushd tests_outputs && make coverage_e2e -j${NPROC} -C .. && popd artifacts: paths: - build/coverage/ @@ -120,6 +121,7 @@ test:openbsd: variables: COMPILER: "g++" SYSTEMD: "-DWITHOUT_SYSTEMD=1" + NPROC: 1 stage: test tags: - openbsd @@ -133,6 +135,6 @@ test:coverity: script: - export PATH=$PATH:~/coverity/bin - cmake .. -DWITHOUT_SYSTEMD=1 - - cov-build --dir cov-int make biboumi test_suite -j$(nproc || echo 1) + - cov-build --dir cov-int make biboumi test_suite -j${NPROC} - tar czvf biboumi_coverity.tgz cov-int - curl --form token=$COVERITY_TOKEN --form email=louiz@louiz.org --form file=@biboumi_coverity.tgz --form version="$(git rev-parse --short HEAD)" --form description="Automatic submission by gitlab-ci" https://scan.coverity.com/builds?project=louiz%2Fbiboumi -- cgit v1.2.3 From bf8348186bde589733de34e6bf988f85fefef444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 14:11:08 +0100 Subject: Coin coin --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b700cd6..3ecc65a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -121,7 +121,7 @@ test:openbsd: variables: COMPILER: "g++" SYSTEMD: "-DWITHOUT_SYSTEMD=1" - NPROC: 1 + NPROC: "1" stage: test tags: - openbsd -- cgit v1.2.3 From 03cdf75757db9d1d49631cd218941f3e80613a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 15:06:44 +0100 Subject: Revert the whole nproc thing --- .gitlab-ci.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3ecc65a..bbdef05 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,15 +18,14 @@ variables: SYSTEMD: "-DWITH_SYSTEMD=1" LIBIDN: "-DWITH_LIBIDN=1" LITESQL: "-DWITH_LITESQL=1" - NPROC: "$(nproc)" .template:basic_build: &basic_build stage: build script: - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL}" - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make biboumi -j${NPROC} - - make check -j${NPROC} + - make biboumi -j$(nproc || echo 1) + - make check -j$(nproc || echo 1) tags: - docker @@ -78,7 +77,7 @@ build:rpm: - docker script: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make rpm -j${NPROC} + - make rpm -j$(nproc || echo 1) artifacts: paths: - build/rpmbuild/RPMS @@ -91,10 +90,10 @@ build:rpm: stage: test script: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - - make biboumi -j${NPROC} + - make biboumi -j$(nproc || echo 1) - make check - make coverage - - mkdir tests_outputs && pushd tests_outputs && make coverage_e2e -j${NPROC} -C .. && popd + - mkdir tests_outputs && pushd tests_outputs && make coverage_e2e -j$(nproc || echo 1) -C .. && popd artifacts: paths: - build/coverage/ @@ -121,7 +120,6 @@ test:openbsd: variables: COMPILER: "g++" SYSTEMD: "-DWITHOUT_SYSTEMD=1" - NPROC: "1" stage: test tags: - openbsd @@ -135,6 +133,6 @@ test:coverity: script: - export PATH=$PATH:~/coverity/bin - cmake .. -DWITHOUT_SYSTEMD=1 - - cov-build --dir cov-int make biboumi test_suite -j${NPROC} + - cov-build --dir cov-int make biboumi test_suite -j$(nproc || echo 1) - tar czvf biboumi_coverity.tgz cov-int - curl --form token=$COVERITY_TOKEN --form email=louiz@louiz.org --form file=@biboumi_coverity.tgz --form version="$(git rev-parse --short HEAD)" --form description="Automatic submission by gitlab-ci" https://scan.coverity.com/builds?project=louiz%2Fbiboumi -- cgit v1.2.3 From 1f652542313a43e328e01fd92171ea0555784846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 15:19:49 +0100 Subject: Fix the openbsd test, do not use coverage on it --- .gitlab-ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bbdef05..f0509ef 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -121,9 +121,13 @@ test:openbsd: COMPILER: "g++" SYSTEMD: "-DWITHOUT_SYSTEMD=1" stage: test + script: + - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} + - make biboumi + - make check + - mkdir tests_outputs && pushd tests_outputs && make e2e tags: - openbsd - <<: *basic_test test:coverity: stage: test -- cgit v1.2.3 From 7144a39a1ef35e9962236f7f041a681bc62ea5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 15:45:56 +0100 Subject: Automate the sonar-qube scanner submission in the ci process --- .gitlab-ci.yml | 8 ++++++++ docker/biboumi-test/fedora/Dockerfile | 1 + 2 files changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f0509ef..035cf3b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -140,3 +140,11 @@ test:coverity: - cov-build --dir cov-int make biboumi test_suite -j$(nproc || echo 1) - tar czvf biboumi_coverity.tgz cov-int - curl --form token=$COVERITY_TOKEN --form email=louiz@louiz.org --form file=@biboumi_coverity.tgz --form version="$(git rev-parse --short HEAD)" --form description="Automatic submission by gitlab-ci" https://scan.coverity.com/builds?project=louiz%2Fbiboumi + +test:sonar-qube: + stage: test + image: biboumi-test-fedora:latest + script: + - cmake .. + - ~/sonar-scanner/bin/build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir ./bw-outputs make biboumi test_suite + - ~/sonar-scanner/bin/sonar-scanner -Dsonar.host.url=https://sonarqube.com -Dsonar.login=$SONAR_LOGIN diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile index fb6dab4..0d0a9ea 100644 --- a/docker/biboumi-test/fedora/Dockerfile +++ b/docker/biboumi-test/fedora/Dockerfile @@ -60,6 +60,7 @@ RUN rm -rf /charybdis RUN su - tester -c "echo export LANG=en_GB.utf-8 >> /home/tester/.bashrc" COPY coverity /home/tester/coverity +COPY sonar-scanner-2.8 /home/tester/sonar-scanner WORKDIR /home/tester USER tester -- cgit v1.2.3 From eed31b6aff7dab854397a7d8b7bf8653c5aa5285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 31 Oct 2016 15:51:30 +0100 Subject: Fix the openbsd test, again --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 035cf3b..d55d473 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -125,7 +125,7 @@ test:openbsd: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi - make check - - mkdir tests_outputs && pushd tests_outputs && make e2e + - mkdir tests_outputs && pushd tests_outputs && make e2e -C .. tags: - openbsd -- cgit v1.2.3 From 8b178aa82fab6a67c2c73385132fd06edad2530b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 1 Nov 2016 17:10:11 +0100 Subject: Fix the sonar-qube submission --- .gitlab-ci.yml | 4 +++- docker/biboumi-test/fedora/Dockerfile | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d55d473..3c0da4b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -147,4 +147,6 @@ test:sonar-qube: script: - cmake .. - ~/sonar-scanner/bin/build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir ./bw-outputs make biboumi test_suite - - ~/sonar-scanner/bin/sonar-scanner -Dsonar.host.url=https://sonarqube.com -Dsonar.login=$SONAR_LOGIN + - cd .. + - ~/sonar-scanner/bin/sonar-scanner -Dsonar.host.url=https://sonarqube.com -Dsonar.login=$SONAR_LOGIN -Dsonar.language=cpp -Dsonar.cfamily.build-wrapper-output=bw-outputs -Dsonar.sourceEncoding=UTF-8 -Dsonar.sources=src/,louloulibs/,tests/ -Dsonar.projectKey=biboumi -Dsonar.projectName=Biboumi -Dsonar.projectVersion=3.0 + diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile index 0d0a9ea..ebcb4e4 100644 --- a/docker/biboumi-test/fedora/Dockerfile +++ b/docker/biboumi-test/fedora/Dockerfile @@ -62,6 +62,8 @@ RUN su - tester -c "echo export LANG=en_GB.utf-8 >> /home/tester/.bashrc" COPY coverity /home/tester/coverity COPY sonar-scanner-2.8 /home/tester/sonar-scanner +RUN dnf install -y which java-1.8.0-openjdk + WORKDIR /home/tester USER tester -- cgit v1.2.3 From e565efd348e6337c7ab46765777fc653ea02a1cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 1 Nov 2016 17:25:49 +0100 Subject: =?UTF-8?q?And=20fix=20the=20bw-output=20path=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip-ci] --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c0da4b..789f4d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -148,5 +148,5 @@ test:sonar-qube: - cmake .. - ~/sonar-scanner/bin/build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir ./bw-outputs make biboumi test_suite - cd .. - - ~/sonar-scanner/bin/sonar-scanner -Dsonar.host.url=https://sonarqube.com -Dsonar.login=$SONAR_LOGIN -Dsonar.language=cpp -Dsonar.cfamily.build-wrapper-output=bw-outputs -Dsonar.sourceEncoding=UTF-8 -Dsonar.sources=src/,louloulibs/,tests/ -Dsonar.projectKey=biboumi -Dsonar.projectName=Biboumi -Dsonar.projectVersion=3.0 + - ~/sonar-scanner/bin/sonar-scanner -Dsonar.host.url=https://sonarqube.com -Dsonar.login=$SONAR_LOGIN -Dsonar.language=cpp -Dsonar.cfamily.build-wrapper-output=build/bw-outputs -Dsonar.sourceEncoding=UTF-8 -Dsonar.sources=src/,louloulibs/,tests/ -Dsonar.projectKey=biboumi -Dsonar.projectName=Biboumi -Dsonar.projectVersion=3.0 -- cgit v1.2.3 From e1934a0b5fb1589bcdd85b86b315d652488d0577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 1 Nov 2016 17:38:59 +0100 Subject: Disable e2e tests on openbsd --- .gitlab-ci.yml | 1 - src/main.cpp | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 789f4d9..cd32742 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -125,7 +125,6 @@ test:openbsd: - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi - make check - - mkdir tests_outputs && pushd tests_outputs && make e2e -C .. tags: - openbsd diff --git a/src/main.cpp b/src/main.cpp index 5585672..019dff0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include #include +#include // A flag set by the SIGINT signal handler. static std::atomic stop(false); @@ -84,7 +85,7 @@ int main(int ac, char** av) try { open_database(); - } catch (...) { + } catch (const litesql::DatabaseError&) { return 1; } -- cgit v1.2.3 From f50f50653dc064575e4730c31b5615301f00e057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 1 Nov 2016 19:43:56 +0100 Subject: Refactor load_certs() --- louloulibs/network/credentials_manager.cpp | 47 +++++++++++++++++------------- louloulibs/network/credentials_manager.hpp | 1 + louloulibs/utils/encoding.cpp | 4 +-- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp index ee83c3b..ed04d24 100644 --- a/louloulibs/network/credentials_manager.cpp +++ b/louloulibs/network/credentials_manager.cpp @@ -29,7 +29,7 @@ BasicCredentialsManager::BasicCredentialsManager(const TCPSocketHandler* const s socket_handler(socket_handler), trusted_fingerprint{} { - this->load_certs(); + BasicCredentialsManager::load_certs(); } void BasicCredentialsManager::set_trusted_fingerprint(const std::string& fingerprint) @@ -62,17 +62,8 @@ void BasicCredentialsManager::verify_certificate_chain(const std::string& type, } } -void BasicCredentialsManager::load_certs() +bool BasicCredentialsManager::try_to_open_one_ca_bundle(const std::vector& paths) { - // Only load the certificates the first time - if (BasicCredentialsManager::certs_loaded) - return; - const std::string conf_path = Config::get("ca_file", ""); - std::vector paths; - if (conf_path.empty()) - paths = default_cert_files; - else - paths.push_back(conf_path); for (const auto& path: paths) { try @@ -87,25 +78,39 @@ void BasicCredentialsManager::load_certs() // will be ignored. As a result, some TLS connection may be refused // because the certificate is signed by an issuer that was ignored. try { - const Botan::X509_Certificate cert(bundle); - BasicCredentialsManager::certificate_store.add_certificate(cert); - } catch (const Botan::Decoding_Error& error) - { + Botan::X509_Certificate cert(bundle); + BasicCredentialsManager::certificate_store.add_certificate(std::move(cert)); + } catch (const Botan::Decoding_Error& error) { continue; } } // Only use the first file that can successfully be read. - goto success; + return true; } - catch (Botan::Stream_IO_Error& e) + catch (const Botan::Stream_IO_Error& e) { log_debug(e.what()); } } - // If we could not open one of the files, print a warning - log_warning("The CA could not be loaded, TLS negociation will probably fail."); - success: - BasicCredentialsManager::certs_loaded = true; + return false; +} + +void BasicCredentialsManager::load_certs() +{ + // Only load the certificates the first time + if (BasicCredentialsManager::certs_loaded) + return; + const std::string conf_path = Config::get("ca_file", ""); + std::vector paths; + if (conf_path.empty()) + paths = default_cert_files; + else + paths.push_back(conf_path); + + if (BasicCredentialsManager::try_to_open_one_ca_bundle(paths)) + BasicCredentialsManager::certs_loaded = true; + else + log_warning("The CA could not be loaded, TLS negociation will probably fail."); } std::vector BasicCredentialsManager::trusted_certificate_authorities(const std::string&, const std::string&) diff --git a/louloulibs/network/credentials_manager.hpp b/louloulibs/network/credentials_manager.hpp index 0fc4b89..7557372 100644 --- a/louloulibs/network/credentials_manager.hpp +++ b/louloulibs/network/credentials_manager.hpp @@ -29,6 +29,7 @@ public: private: const TCPSocketHandler* const socket_handler; + static bool try_to_open_one_ca_bundle(const std::vector& paths); static void load_certs(); static Botan::Certificate_Store_In_Memory certificate_store; static bool certs_loaded; diff --git a/louloulibs/utils/encoding.cpp b/louloulibs/utils/encoding.cpp index cb953c0..60f2212 100644 --- a/louloulibs/utils/encoding.cpp +++ b/louloulibs/utils/encoding.cpp @@ -151,7 +151,7 @@ namespace utils throw std::runtime_error("Cannot convert into UTF-8"); // Make sure cd is always closed when we leave this function - const auto sg = utils::make_scope_guard([&](auto&&){ iconv_close(cd); }); + const auto sg = utils::make_scope_guard([&cd](auto&&){ iconv_close(cd); }); size_t inbytesleft = str.size(); @@ -168,7 +168,7 @@ namespace utils char* outbuf_ptr = outbuf; // Make sure outbuf is always deleted when we leave this function - const auto sg2 = utils::make_scope_guard([&](auto&&){ delete[] outbuf; }); + const auto sg2 = utils::make_scope_guard([outbuf](auto&&){ delete[] outbuf; }); bool done = false; while (done == false) -- cgit v1.2.3 From c70301e503fdb887387a54fcf5284d593e65d837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 2 Nov 2016 21:25:26 +0100 Subject: Fix the presences sent, when multiple resources join the virtual channel fix #3216 --- src/bridge/bridge.cpp | 21 ++++++++++++------ tests/end_to_end/__main__.py | 53 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 67eb805..a0ecc6e 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -177,16 +177,23 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const { // Join the dummy channel if (irc->is_welcomed()) { - if (irc->get_dummy_channel().joined) + if (res_in_chan) return false; // Immediately simulate a message coming from the IRC server saying that we // joined the channel - const IrcMessage join_message(irc->get_nick(), "JOIN", {""}); - irc->on_channel_join(join_message); - const IrcMessage end_join_message(std::string(iid.get_server()), "366", - {irc->get_nick(), - "", "End of NAMES list"}); - irc->on_channel_completely_joined(end_join_message); + if (irc->get_dummy_channel().joined) + { + this->generate_channel_join_for_resource(iid, resource); + } + else + { + const IrcMessage join_message(irc->get_nick(), "JOIN", {""}); + irc->on_channel_join(join_message); + const IrcMessage end_join_message(std::string(iid.get_server()), "366", + {irc->get_nick(), + "", "End of NAMES list"}); + irc->on_channel_completely_joined(end_join_message); + } } else { diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index e2a497d..26e7b05 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1656,6 +1656,59 @@ if __name__ == '__main__': partial(expect_stanza, "/message/body[text()='{nick_two} has been invited to #foo']"), partial(expect_stanza, "/message[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}']/muc_user:x/muc_user:invite[@from='#foo%{irc_server_one}/{nick_one}']"), ]), + Scenario("virtual_channel_multisession", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='%{irc_server_one}'][@type='groupchat']/subject[re:test(text(), '^This is a virtual channel.*$')]"), + connection_end_sequence("irc.localhost", '{jid_one}/{resource_one}'), + + partial(send_stanza, + ""), + + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_two}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@to='{jid_one}/{resource_two}'][@from='%{irc_server_one}'][@type='groupchat']/subject[re:test(text(), '^This is a virtual channel.*$')]"), + + + partial(log_message, "Nick change"), + partial(send_stanza, ""), + + partial(expect_unordered, [ + ("/presence[@from='%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_two}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bobby']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + ("/presence[@from='%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + + ("/presence[@from='%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='unavailable']/muc_user:x/muc_user:item[@nick='Bobby']", + "/presence/muc_user:x/muc_user:status[@code='303']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + ("/presence[@from='%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}']", + "/presence/muc_user:x/muc_user:status[@code='110']"), + ]), + + + partial(log_message, "First resource leaves."), + partial(send_stanza, ""), + partial(expect_stanza, ("/presence[@type='unavailable'][@from='%{irc_server_one}/{nick_two}'][@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.']",) + ), + + partial(log_message, "Second resource leaves."), + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable'][@from='%{irc_server_one}/{nick_two}']"), + partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Closing Link: localhost (Client Quit)']"), + partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Connection closed.']"), + ]), ) failures = 0 -- cgit v1.2.3 From 50d7590db426ce821148af769ea18d556e97f393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 3 Nov 2016 22:02:07 +0100 Subject: Test the global and IRC server configure commands --- tests/end_to_end/__main__.py | 90 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 26e7b05..a7a5f69 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1709,8 +1709,98 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Closing Link: localhost (Client Quit)']"), partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Connection closed.']"), ]), + Scenario("global_configure", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='20']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='true']", + "/iq/commands:command/commands:actions/commands:next", + ), + after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + partial(send_stanza, "042"), + partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure some global default settings.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure your global settings for the component.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='max_history_length']/dataform:value[text()='42']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='record_history']/dataform:value[text()='false']", + "/iq/commands:command/commands:actions/commands:next", + ), + after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + partial(send_stanza, ""), + partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), + ]), + Scenario("irc_server_configure", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='ports']/dataform:value[text()='6667']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6670']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6697']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='verify_cert']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='fingerprint']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='pass']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='after_connect_command']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='username']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='realname']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='ISO-8859-1']", + "/iq/commands:command/commands:actions/commands:next", + ), + after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + partial(send_stanza, "" + "" + "" + "" + "66976698" + "1" + "12:12:12" + "coucou" + "INVALID command" + "username" + "realname" + "UTF-8" + "latin-1" + ""), + partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"), + + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6697']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-multi'][@var='tls_ports']/dataform:value[text()='6698']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='verify_cert']/dataform:value[text()='true']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='fingerprint']/dataform:value[text()='12:12:12']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-private'][@var='pass']/dataform:value[text()='coucou']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='after_connect_command']/dataform:value[text()='INVALID command']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='username']/dataform:value[text()='username']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='realname']/dataform:value[text()='realname']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']", + "/iq/commands:command/commands:actions/commands:next", + ), + after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid")) + ), + partial(send_stanza, ""), + partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), + ]), ) + failures = 0 scenar_list = sys.argv[1:] -- cgit v1.2.3 From 7376831bc8f6dbec8eaf4f4c0a6bba819a0a1e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 14:43:07 +0100 Subject: Add get-irc-connection-info adhoc command fix #3171 --- doc/biboumi.1.rst | 9 ++++- louloulibs/network/tcp_socket_handler.cpp | 12 ++++++ louloulibs/network/tcp_socket_handler.hpp | 3 ++ src/bridge/bridge.hpp | 4 ++ src/xmpp/biboumi_adhoc_commands.cpp | 67 ++++++++++++++++++++++++++++++- src/xmpp/biboumi_adhoc_commands.hpp | 2 + src/xmpp/biboumi_component.cpp | 6 +++ src/xmpp/biboumi_component.hpp | 2 +- tests/end_to_end/__main__.py | 42 ++++++++++++++++++- 9 files changed, 141 insertions(+), 6 deletions(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 8f5eb9b..49c0fe4 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -488,13 +488,18 @@ On the gateway itself (e.g on the JID biboumi.example.com): On a server JID (e.g on the JID chat.freenode.org@biboumi.example.com) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Configure: Lets each user configure some options that applies to the +- configure: Lets each user configure some options that applies to the concerned IRC server. +- get-irc-connection-info: Returns some information about the IRC server, + for the executing user. It lets the user know if they are connected to + this server, from what port, with or without TLS, and it gives the list + of joined IRC channel, with a detailed list of which resource is in which + channel. On a channel JID (e.g on the JID #test%chat.freenode.org@biboumi.example.com) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Configure: Lets each user configure some options that applies to the +- configure: Lets each user configure some options that applies to the concerned IRC channel. Some of these options, if not configured for a specific channel, defaults to the value configured at the IRC server level. For example the encoding can be specified for both the channel diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 9decee1..1dddde5 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -179,6 +179,8 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po if (this->use_tls) this->start_tls(); #endif + this->connection_date = std::chrono::system_clock::now(); + this->on_connected(); return ; } @@ -397,6 +399,16 @@ bool TCPSocketHandler::is_connecting() const return this->connecting || this->resolver.is_resolving(); } +bool TCPSocketHandler::is_using_tls() const +{ + return this->use_tls; +} + +std::string TCPSocketHandler::get_port() const +{ + return this->port; +} + void* TCPSocketHandler::get_receive_buffer(const size_t) const { return nullptr; diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 7bbe4d4..6c4235e 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -106,6 +106,9 @@ public: #endif bool is_connected() const override final; bool is_connecting() const; + bool is_using_tls() const; + std::string get_port() const; + std::chrono::system_clock::time_point connection_date; private: /** diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b2432f0..18ebfeb 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -248,10 +248,12 @@ private: * a IRCServerNotConnected error in that case. */ IrcClient* get_irc_client(const std::string& hostname); +public: /** * Idem, but returns nullptr if the server does not exist. */ IrcClient* find_irc_client(const std::string& hostname) const; +private: /** * The bare JID of the user associated with this bridge. Messages from/to this * JID are only managed by this bridge. @@ -293,7 +295,9 @@ private: using ChannelName = std::string; using IrcHostname = std::string; using ChannelKey = std::tuple; +public: std::map> resources_in_chan; +private: std::map> resources_in_server; /** * Manage which resource is in which channel diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index af7e473..87b8d96 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -1,10 +1,12 @@ #include #include +#include +#include #include #include #include #include -#include +#include #include @@ -720,3 +722,66 @@ void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession& note.set_inner(msg); command_node.add_child(std::move(note)); } + +void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, XmlNode& command_node) +{ + BiboumiComponent& biboumi_component = static_cast(component); + + const Jid owner(session.get_owner_jid()); + const Jid target(session.get_target_jid()); + + std::string message{}; + + // As the function is exited, set the message in the response. + utils::ScopeGuard sg([&message, &command_node]() + { + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + note.set_inner(message); + command_node.add_child(std::move(note)); + }); + + Bridge* bridge = biboumi_component.get_user_bridge(owner.bare()); + if (!bridge) + { + message = "You are not connected to anything."; + return; + } + + std::string hostname; + if ((hostname = Config::get("fixed_irc_server", "")).empty()) + hostname = target.local; + + IrcClient* irc = bridge->find_irc_client(hostname); + if (!irc || !irc->is_connected()) + { + message = "You are not connected to the IRC server "s + hostname; + return; + } + + std::ostringstream ss; + ss << "Connected to IRC server " << irc->get_hostname() << " on port " << irc->get_port(); + if (irc->is_using_tls()) + ss << " (using TLS)"; + const std::time_t now_c = std::chrono::system_clock::to_time_t(irc->connection_date); + ss << " since " << std::put_time(std::localtime(&now_c), "%F %T"); + ss << " (" << std::chrono::duration_cast(std::chrono::system_clock::now() - irc->connection_date).count() << " seconds ago)."; + + for (const auto& it: bridge->resources_in_chan) + { + const auto& channel_key = it.first; + const auto& irc_hostname = std::get<1>(channel_key); + const auto& resources = it.second; + + if (irc_hostname == irc->get_hostname() && !resources.empty()) + { + const auto& channel_name = std::get<0>(channel_key); + ss << "\n" << channel_name << " from " << resources.size() << " resource" << (resources.size() > 1 ? "s": "") << ": "; + for (const auto& resource: resources) + ss << resource << " "; + } + } + + message = ss.str(); +} diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index 7be5509..b5fce61 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -22,3 +22,5 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep3(XmppComponent&, AdhocSession& session, XmlNode& command_node); + +void GetIrcConnectionInfoStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f3405df..4398562 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -63,6 +63,12 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st this->adhoc_commands_handler.add_command("disconnect-from-irc-server", {{&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false}); this->adhoc_commands_handler.add_command("reload", {{&Reload}, "Reload biboumi’s configuration", true}); + AdhocCommand get_irc_connection_info{{&GetIrcConnectionInfoStep1}, "Returns various information about your connection to this IRC server.", false}; + if (!Config::get("fixed_irc_server", "").empty()) + this->adhoc_commands_handler.add_command("get-irc-connection-info", get_irc_connection_info); + else + this->irc_server_adhoc_commands_handler.add_command("get-irc-connection-info", get_irc_connection_info); + #ifdef USE_DATABASE AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false); AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false); diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index d5b87e9..999001f 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -101,13 +101,13 @@ public: const std::string& queryid); #endif -private: /** * Return the bridge associated with the bare JID. Create a new one * if none already exist. */ Bridge* get_user_bridge(const std::string& user_jid); +private: /** * A map of id -> callback. When we want to wait for an iq result, we add * the callback to this map, with the iq id as the key. When an iq result diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index a7a5f69..239f044 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -631,7 +631,7 @@ if __name__ == '__main__': handshake_sequence(), partial(send_stanza, ""), partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", - "/iq/disco_items:query/disco_items:item[3]")), + "/iq/disco_items:query/disco_items:item[5]")), ], conf='fixed_server'), Scenario("list_admin_adhoc_fixed_server", [ @@ -647,7 +647,7 @@ if __name__ == '__main__': handshake_sequence(), partial(send_stanza, ""), partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", - "/iq/disco_items:query/disco_items:item[1]")), + "/iq/disco_items:query/disco_items:item[2]")), ]), Scenario("list_adhoc_irc_fixed_server", [ @@ -1798,6 +1798,44 @@ if __name__ == '__main__': partial(send_stanza, ""), partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), ]), + Scenario("get_irc_connection_info", + [ + handshake_sequence(), + + partial(log_message, "Not connected yet"), + partial(send_stanza, ""), + partial(expect_stanza, "/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"), + + partial(log_message, "Join one room"), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, ""), + partial(expect_stanza, r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), + ]), + Scenario("get_irc_connection_info_fixed", + [ + handshake_sequence(), + + partial(log_message, "Not connected yet"), + partial(send_stanza, ""), + partial(expect_stanza, "/iq/commands:command/commands:note[text()='You are not connected to the IRC server irc.localhost']"), + + partial(log_message, "Join one room"), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, ""), + partial(expect_stanza, r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), + ], conf='fixed_server'), ) -- cgit v1.2.3 From 962bac476cc69362b748675db58ea6eb364501e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 14:43:52 +0100 Subject: Trivial refactor of get_user_bridge function --- src/xmpp/biboumi_component.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 4398562..010b415 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -663,8 +663,7 @@ Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid) } catch (const std::out_of_range& exception) { - this->bridges.emplace(bare_jid, std::make_unique(bare_jid, *this, this->poller)); - return this->bridges.at(bare_jid).get(); + return this->bridges.emplace(bare_jid, std::make_unique(bare_jid, *this, this->poller)).first->second.get(); } } -- cgit v1.2.3 From 94cebfcd77484bc79ed75307a45b6534d1899b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 14:49:51 +0100 Subject: Add a missing include --- louloulibs/network/tcp_socket_handler.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 6c4235e..20a3e5a 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include -- cgit v1.2.3 From 597cfce4b88f9318ed8b580e8b26df9be8fc637a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 14:52:08 +0100 Subject: Add back --- src/xmpp/biboumi_adhoc_commands.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 87b8d96..1349a66 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From a52baa52e25c9767d1be95a10b2a56334aaeb471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 20:26:28 +0100 Subject: Workaround for debian and other old OS that do not have std::put_time --- louloulibs/CMakeLists.txt | 8 ++++++++ louloulibs/louloulibs.h.cmake | 1 + src/xmpp/biboumi_adhoc_commands.cpp | 12 ++++++++++++ 3 files changed, 21 insertions(+) diff --git a/louloulibs/CMakeLists.txt b/louloulibs/CMakeLists.txt index 1858bb3..908c35f 100644 --- a/louloulibs/CMakeLists.txt +++ b/louloulibs/CMakeLists.txt @@ -156,4 +156,12 @@ check_cxx_source_compiles(" mark_as_advanced(HAS_GET_TIME) +check_cxx_source_compiles(" + #include + int main() + { std::put_time(nullptr, \"\"); }" + HAS_PUT_TIME) + +mark_as_advanced(HAS_PUT_TIME) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/louloulibs.h.cmake ${CMAKE_BINARY_DIR}/src/louloulibs.h) diff --git a/louloulibs/louloulibs.h.cmake b/louloulibs/louloulibs.h.cmake index d5328b8..6131b70 100644 --- a/louloulibs/louloulibs.h.cmake +++ b/louloulibs/louloulibs.h.cmake @@ -8,3 +8,4 @@ #cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}" #cmakedefine PROJECT_NAME "${PROJECT_NAME}" #cmakedefine HAS_GET_TIME +#cmakedefine HAS_PUT_TIME diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 1349a66..003b901 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -15,6 +15,10 @@ #include #endif +#ifndef HAS_PUT_TIME +#include +#endif + using namespace std::string_literals; void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& command_node) @@ -766,7 +770,15 @@ void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, if (irc->is_using_tls()) ss << " (using TLS)"; const std::time_t now_c = std::chrono::system_clock::to_time_t(irc->connection_date); +#ifdef HAS_PUT_TIME ss << " since " << std::put_time(std::localtime(&now_c), "%F %T"); +#else + constexpr std::size_t timestamp_size{10 + 1 + 8 + 1}; + char buf[timestamp_size] = {}; + const auto res = std::strftime(buf, timestamp_size, "%F %T", std::localtime(&now_c)); + if (res > 0) + ss << " since " << buf; +#endif ss << " (" << std::chrono::duration_cast(std::chrono::system_clock::now() - irc->connection_date).count() << " seconds ago)."; for (const auto& it: bridge->resources_in_chan) -- cgit v1.2.3 From f36bccd7bbd685cc34370da4ab4b7d124bcc69a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 21:51:39 +0100 Subject: Test the error on joining invalid room --- tests/end_to_end/__main__.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 239f044..7658d92 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -113,6 +113,7 @@ def match(stanza, xpath): tree = lxml.etree.parse(io.StringIO(str(stanza))) matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', 'muc_user': 'http://jabber.org/protocol/muc#user', + 'muc': 'http://jabber.org/protocol/muc', 'disco_info': 'http://jabber.org/protocol/disco#info', 'muc_traffic': 'http://jabber.org/protocol/muc#traffic', 'disco_items': 'http://jabber.org/protocol/disco#items', @@ -1836,6 +1837,22 @@ if __name__ == '__main__': partial(send_stanza, ""), partial(expect_stanza, r"/iq/commands:command/commands:note[re:test(text(), 'Connected to IRC server irc.localhost on port 6667 since \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \(\d+ seconds ago\)\.\n#foo from 1 resource: {resource_one}.*')]"), ], conf='fixed_server'), + Scenario("invalid_room_jid", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/presence[@type='error'][@to='{jid_one}/{resource_one}'][@from='invalid%{irc_server_one}/{nick_one}']/error[@type='cancel']/stanza:item-not-found", + "/presence/muc:x", + "/presence/error/stanza:text")), + ]), + Scenario("invalid_room_jid_fixed", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/presence[@type='error'][@to='{jid_one}/{resource_one}'][@from='invalid@{biboumi_host}/{nick_one}']/error[@type='cancel']/stanza:item-not-found", + "/presence/muc:x", + "/presence/error/stanza:text")), + ], conf='fixed_server'), ) -- cgit v1.2.3 From b5beb043325ca5625f4eb53cb9451daf499c586b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 7 Nov 2016 21:57:39 +0100 Subject: Remove a never reached (and non-sensical) error --- louloulibs/xmpp/xmpp_component.cpp | 25 ------------------------- louloulibs/xmpp/xmpp_component.hpp | 6 ------ src/xmpp/biboumi_component.cpp | 2 -- 3 files changed, 33 deletions(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 3ac617c..fa8b0a5 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -367,31 +367,6 @@ void XmppComponent::send_invalid_room_error(const std::string& muc_name, this->send_stanza(presence); } -void XmppComponent::send_invalid_user_error(const std::string& user_name, const std::string& to) -{ - Stanza message("message"); - message["from"] = user_name + "@" + this->served_hostname; - message["to"] = to; - message["type"] = "error"; - XmlNode x("x"); - x["xmlns"] = MUC_NS; - message.add_child(std::move(x)); - XmlNode error("error"); - error["type"] = "cancel"; - XmlNode item_not_found("item-not-found"); - item_not_found["xmlns"] = STANZA_NS; - error.add_child(std::move(item_not_found)); - XmlNode text("text"); - text["xmlns"] = STANZA_NS; - text["xml:lang"] = "en"; - text.set_inner(user_name + - " is not a valid IRC user name. A correct user jid is of the form: !@" + - this->served_hostname); - error.add_child(std::move(text)); - message.add_child(std::move(error)); - this->send_stanza(message); -} - void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to, const std::string& who) { XmlNode message("message"); diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 45a4038..5f5f937 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -127,12 +127,6 @@ public: 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 */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 010b415..d6782e2 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -269,8 +269,6 @@ void BiboumiComponent::handle_message(const Stanza& stanza) } } - else if (iid.type == Iid::Type::User) - this->send_invalid_user_error(to.local, from_str); } catch (const IRCNotConnected& ex) { this->send_stanza_error("message", from_str, to_str, id, -- cgit v1.2.3 From 009aff52ac735313329a6d74b9c9c425294bbbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 9 Nov 2016 17:11:55 +0100 Subject: Little update to the changelog --- CHANGELOG.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7470528..e52357d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,10 +3,12 @@ Version 4.0 - The separator between the IRC nickname and the IRC server is now '%' instead of '!'. This makes things simpler (only one separator to - remember). The distinction between a JID refering to a channel and a JID + remember). The distinction between a JID referring to a channel and a JID refering to a nickname is based on the first character (# or & by default, but this can be customized by the server with the ISUPPORT extension). + - Handle channel invitations in both directions. + - Add support for `JID escaping <.http://www.xmpp.org/extensions/xep-0106.html>`. - Save all channel messages into the database, with an ad-hoc option to disable this feature. - When joining a room, biboumi sends an history of the most recents messages -- cgit v1.2.3 From 9634cdaba2e5d2343fcbc1f07264d55609640273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 9 Nov 2016 17:50:09 +0100 Subject: Release version 4.0 --- CHANGELOG.rst | 4 ++-- CMakeLists.txt | 2 +- packaging/biboumi.spec.cmake | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e52357d..a896e68 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,5 @@ -Version 4.0 -=========== +Version 4.0 - 2016-11-09 +======================== - The separator between the IRC nickname and the IRC server is now '%' instead of '!'. This makes things simpler (only one separator to diff --git a/CMakeLists.txt b/CMakeLists.txt index 35f11ee..2301123 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 4) set(${PROJECT_NAME}_VERSION_MINOR 0) -set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") +set(${PROJECT_NAME}_VERSION_SUFFIX "") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index ec1ac5e..0747d34 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -59,8 +59,8 @@ make check %{?_smp_mflags} %changelog -* ${RPM_DATE} Le Coz Florent - ${RPM_VERSION}-1 -- Build latest git revision +* Wed Nov 9 2016 Le Coz Florent - 4.0-1 +- Update to 4.0 sources * Thu Aug 4 2016 Le Coz Florent - 3.0-1 - Update to 3.0 sources -- cgit v1.2.3