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