~singpolyma/biboumi

7536a1b3f38fbf093c1629b0db209754ada0c906 — louiz’ 8 years ago b59fc2a
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.
M docker/biboumi-test/debian/Dockerfile => docker/biboumi-test/debian/Dockerfile +1 -1
@@ 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


M docker/biboumi-test/fedora/Dockerfile => docker/biboumi-test/fedora/Dockerfile +1 -1
@@ 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


A louloulibs/utils/time.cpp => louloulibs/utils/time.cpp +12 -0
@@ 0,0 1,12 @@
#include <utils/time.hpp>

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(&timestamp));
    return {std::begin(date_buf), std::end(date_buf)};
}
}
\ No newline at end of file

A louloulibs/utils/time.hpp => louloulibs/utils/time.hpp +9 -0
@@ 0,0 1,9 @@
#pragma once

#include <ctime>
#include <string>

namespace utils
{
std::string to_string(const std::time_t& timestamp);
}
\ No newline at end of file

M louloulibs/xmpp/jid.hpp => louloulibs/xmpp/jid.hpp +6 -1
@@ 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;
  }
};


M louloulibs/xmpp/xmpp_component.cpp => louloulibs/xmpp/xmpp_component.cpp +3 -18
@@ 7,22 7,10 @@
#include <config/config.hpp>
#include <xmpp/jid.hpp>
#include <utils/sha1.hpp>

#include <stdexcept>
#include <iostream>
#include <set>

#include <stdio.h>
#include <utils/time.hpp>

#include <uuid.h>

#include <cstdlib>

#include <louloulibs.h>
#ifdef SYSTEMD_FOUND
# include <systemd/sd-daemon.h>
#endif

using namespace std::string_literals;

static std::set<std::string> 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(&timestamp));
  delay["stamp"] = date_buf;
  delay["stamp"] = utils::to_string(timestamp);

  message.add_child(std::move(delay));
  this->send_stanza(message);

M louloulibs/xmpp/xmpp_component.hpp => louloulibs/xmpp/xmpp_component.hpp +4 -0
@@ 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

M src/database/database.cpp => src/database/database.cpp +5 -5
@@ 138,13 138,13 @@ void Database::store_muc_message(const std::string& owner, const Iid& iid,

std::vector<db::MucLogLine> 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<db::MucLogLine>(*Database::db,
  if (limit == -1)
    limit = 1024;
  const auto& res = litesql::select<db::MucLogLine>(*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()

M src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +78 -1
@@ 8,8 8,9 @@
#include <xmpp/biboumi_adhoc_commands.hpp>
#include <bridge/list_element.hpp>
#include <config/config.hpp>
#include <xmpp/jid.hpp>
#include <utils/sha1.hpp>
#include <utils/time.hpp>
#include <xmpp/jid.hpp>

#include <stdexcept>
#include <iostream>


@@ 25,6 26,8 @@
# include <systemd/sd-daemon.h>
#endif

#include <database/database.hpp>

using namespace std::string_literals;

static std::set<std::string> 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();

M src/xmpp/biboumi_component.hpp => src/xmpp/biboumi_component.hpp +10 -0
@@ 9,6 9,10 @@
#include <string>
#include <map>

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

M tests/end_to_end/__main__.py => tests/end_to_end/__main__.py +47 -3
@@ 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,
                            "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
                    connection_sequence("irc.localhost", '{jid_one}/{resource_one}'),
                    partial(expect_stanza,
                            "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
                    partial(expect_stanza,
                            ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@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, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou</body></message>"),
                    # 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, "<message from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}' type='groupchat'><body>coucou 2</body></message>"),
                    # 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, "<iq to='#foo%{irc_server_one}' from='{jid_one}/{resource_one}' type='set' id='id1'><query xmlns='urn:xmpp:mam:1' queryid='qid1' /></iq>"),

                    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