From 07683df5b9989dfac51f87d4c1ec6824c3c54041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 2 Aug 2017 15:15:06 +0200 Subject: Allow failures of packaging jobs --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 02f34e9..6849cb4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -266,6 +266,7 @@ packaging:rpm: - master@louiz/biboumi tags: - docker + allow_failure: true image: docker.louiz.org/louiz/biboumi/test-fedora:latest script: - cd build/ @@ -286,6 +287,7 @@ packaging:deb: - debian@louiz/biboumi tags: - docker + allow_failure: true image: docker.louiz.org/louiz/biboumi/packaging-debian:latest before_script: [] script: @@ -309,6 +311,7 @@ packaging:archlinux: - master@louiz/biboumi tags: - docker + allow_failure: true image: docker.louiz.org/louiz/biboumi/test-archlinux:latest before_script: [] script: -- cgit v1.2.3 From cd70181cb05ce668f475173ace73a1716a1edd65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 2 Aug 2017 15:50:13 +0200 Subject: Fix the hostname of test3.biboumi.louiz.org --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6849cb4..f63b8c6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -347,7 +347,7 @@ deploy:docker: - docker push docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME - docker service create --with-registry-auth --detach=false --name $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-1 -e BIBOUMI_PASSWORD=password -e BIBOUMI_XMPP_SERVER_IP=prosody -e BIBOUMI_HOSTNAME=test.biboumi.louiz.org --network xmpp docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME || docker service update $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-1 - docker service create --with-registry-auth --detach=false --name $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-2 -e BIBOUMI_PASSWORD=password -e BIBOUMI_XMPP_SERVER_IP=prosody -e BIBOUMI_HOSTNAME=test2.biboumi.louiz.org --network xmpp docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME || docker service update $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-2 - - docker service create --with-registry-auth --detach=false --name $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-3 -e BIBOUMI_PASSWORD=password -e BIBOUMI_XMPP_SERVER_IP=prosody -e BIBOUMI_HOSTNAME=test2.biboumi.louiz.org --network xmpp docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME || docker service update $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-3 + - docker service create --with-registry-auth --detach=false --name $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-3 -e BIBOUMI_PASSWORD=password -e BIBOUMI_XMPP_SERVER_IP=prosody -e BIBOUMI_HOSTNAME=test3.biboumi.louiz.org --network xmpp docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME || docker service update $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-3 environment: name: master url: https://biboumi.louiz.org -- cgit v1.2.3 From cad70ba453a7877b64c0f23a4b4f34db9d1a0957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 2 Aug 2017 21:47:21 +0200 Subject: Re-add a removed (by mistake) pointer null check --- src/xmpp/biboumi_adhoc_commands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 60af506..65f9966 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -409,7 +409,7 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com else if (field->get_tag("var") == "pass" && value) options.col() = value->get_inner(); - else if (field->get_tag("var") == "after_connect_command") + else if (field->get_tag("var") == "after_connect_command" && value) options.col() = value->get_inner(); else if (field->get_tag("var") == "username" && value) -- cgit v1.2.3 From 97b52526c5a661ea9fc5c5e1ae5ea300c54e19d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 2 Aug 2017 22:06:45 +0200 Subject: ci: debian branch, pull the debian branch before trying to merge with master --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f63b8c6..eeb4375 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -292,6 +292,7 @@ packaging:deb: before_script: [] script: - git checkout debian + - git pull - git merge --no-commit --no-ff master - mk-build-deps - apt update -y -- cgit v1.2.3 From 5b0b41f40a3ef41657971e1538dfb508fd7e6be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 4 Aug 2017 15:29:58 +0200 Subject: Build the docker image with --no-cache, --pull --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index eeb4375..9b70297 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -344,7 +344,7 @@ deploy:docker: - echo "$TLSKEY" > ~/.docker/key.pem - docker version - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN docker.louiz.org - - docker build -t docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME docker/biboumi/alpine + - docker build --pull --no-cache -t docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME docker/biboumi/alpine - docker push docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME - docker service create --with-registry-auth --detach=false --name $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-1 -e BIBOUMI_PASSWORD=password -e BIBOUMI_XMPP_SERVER_IP=prosody -e BIBOUMI_HOSTNAME=test.biboumi.louiz.org --network xmpp docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME || docker service update $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-1 - docker service create --with-registry-auth --detach=false --name $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-2 -e BIBOUMI_PASSWORD=password -e BIBOUMI_XMPP_SERVER_IP=prosody -e BIBOUMI_HOSTNAME=test2.biboumi.louiz.org --network xmpp docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME || docker service update $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-2 -- cgit v1.2.3 From 9648fd286c16dc1b9154a7940820a5c7043b4836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 19 Aug 2017 13:00:19 +0200 Subject: Display the correct error message when we fail to open the sqlite3 db fix #3290 --- 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 85c675e..0068b24 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -25,7 +25,7 @@ void Database::open(const std::string& filename) auto res = sqlite3_open_v2(filename.data(), &new_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); if (res != SQLITE_OK) { - log_error("Failed to open database file ", filename, ": ", sqlite3_errmsg(Database::db)); + log_error("Failed to open database file ", filename, ": ", sqlite3_errmsg(new_db)); throw std::runtime_error(""); } Database::close(); -- cgit v1.2.3 From 2a62b8b0823435e14675ae9bbe281ddd37206fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 19 Aug 2017 13:00:56 +0200 Subject: Fix a sqlite3 leak when the database fails to open --- src/database/database.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/database/database.cpp b/src/database/database.cpp index 0068b24..266b17e 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -23,12 +23,13 @@ void Database::open(const std::string& filename) // not, just leave things untouched sqlite3* new_db; auto res = sqlite3_open_v2(filename.data(), &new_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); + Database::close(); if (res != SQLITE_OK) { log_error("Failed to open database file ", filename, ": ", sqlite3_errmsg(new_db)); + sqlite3_close_v2(new_db); throw std::runtime_error(""); } - Database::close(); Database::db = new_db; Database::muc_log_lines.create(Database::db); Database::muc_log_lines.upgrade(Database::db); -- cgit v1.2.3 From 49c86398a557856fb202c449790d0b4afffb1182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 19 Aug 2017 13:18:32 +0200 Subject: Change docker documentation regarding the database permissions fix #3290 --- docker/biboumi/alpine/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/biboumi/alpine/README.md b/docker/biboumi/alpine/README.md index 4b9e1e5..806bdc5 100644 --- a/docker/biboumi/alpine/README.md +++ b/docker/biboumi/alpine/README.md @@ -61,8 +61,9 @@ Volumes The database is stored in the /var/lib/biboumi/ directory. If you don’t bind a local directory to it, the database will be lost when the container is stopped. If you want to keep your database between each run, bind it with the -v option, like this: **-v /srv/biboumi/:/var/lib/biboumi**. -Note: Due to a limitation in Docker, to be able to read and write into this database, make sure this mounted directory is owned by UID and GID 1001:1001, on the host. +Note: Due to a limitation in Docker, to be able to read and write into this database, make sure this mounted directory has the proper read and write permissions on the host: it can be owned by UID and GID 1000:1000, or use chmod to give permissions to everyone, for example. ``` -chown -R 1001:1001 database/ +chown -R 1000:1000 database/ +chmod 777 database/ ``` -- cgit v1.2.3 From fe4ceb72d09968a16105c4c0864705f474d9a863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 24 Aug 2017 23:53:29 +0200 Subject: Add an e2e test to demonstrate bug #3291 --- tests/end_to_end/__main__.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index c6607d0..5317540 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1480,6 +1480,24 @@ if __name__ == '__main__': ("/message/subject",), ]), + # demonstrate bug https://lab.louiz.org/louiz/biboumi/issues/3291 + # First user joins an other channel + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message[@type='groupchat']/subject"), + + # Second user joins + partial(send_stanza, + ""), + 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(send_stanza, "reported"), @@ -1495,6 +1513,14 @@ if __name__ == '__main__': ), ("/iq[@id='kick1'][@type='result']",), ]), + + # Bug 3291, suite. We must not receive any presence from #foo, here + partial(send_stanza, "QUIT bye bye"), + partial(expect_unordered, + [("/presence[@from='#bar%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}']",), + ("/presence[@from='#bar%{irc_server_one}/{nick_two}'][@to='{jid_two}/{resource_one}']",), + ("/message",), + ("/message",)]) ]), Scenario("mode_change", [ -- cgit v1.2.3 From c23c99dac109b3cfd53c72dbcbaf8b483bd6f4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 24 Aug 2017 23:55:32 +0200 Subject: =?UTF-8?q?Don=E2=80=99t=20forget=20to=20remove=20the=20user=20fro?= =?UTF-8?q?m=20the=20channel,=20when=20kicked?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #3291 --- src/irc/irc_client.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 46dbdbe..ba593c9 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -1073,12 +1073,18 @@ void IrcClient::on_nick(const IrcMessage& message) void IrcClient::on_kick(const IrcMessage& message) { const std::string chan_name = utils::tolower(message.arguments[0]); - const std::string target = message.arguments[1]; + const std::string target_nick = message.arguments[1]; const std::string reason = message.arguments[2]; IrcChannel* channel = this->get_channel(chan_name); if (!channel->joined) return ; - const bool self = channel->get_self()->nick == target; + const IrcUser* target = channel->find_user(target_nick); + if (!target) + { + log_warning("Received a KICK command from a nick absent from the channel."); + return; + } + const bool self = channel->get_self() == target; if (self) channel->joined = false; IrcUser author(message.prefix); @@ -1086,7 +1092,8 @@ void IrcClient::on_kick(const IrcMessage& message) 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, self); + this->bridge.kick_muc_user(std::move(iid), target_nick, reason, author.nick, self); + channel->remove_user(target); } void IrcClient::on_invite(const IrcMessage& message) -- cgit v1.2.3 From 1997fb5c6a5f791960575a31bd34dfd24cf96a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 24 Aug 2017 23:56:00 +0200 Subject: Small refactor in on_quit() --- src/irc/irc_client.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index ba593c9..7776c8d 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -1017,19 +1017,17 @@ void IrcClient::on_quit(const IrcMessage& message) const std::string& chan_name = pair.first; IrcChannel* channel = pair.second.get(); const IrcUser* user = channel->find_user(message.prefix); + if (!user) + continue; bool self = false; if (user == channel->get_self()) self = true; - if (user) - { - std::string nick = user->nick; - channel->remove_user(user); - Iid iid; - iid.set_local(chan_name); - iid.set_server(this->hostname); - iid.type = Iid::Type::Channel; - this->bridge.send_muc_leave(iid, std::move(nick), txt, self, false); - } + Iid iid; + iid.set_local(chan_name); + iid.set_server(this->hostname); + iid.type = Iid::Type::Channel; + this->bridge.send_muc_leave(iid, user->nick, txt, self, false); + channel->remove_user(user); } } -- cgit v1.2.3 From 655151d88a6ab948949b73682c3a76a0274eb10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 26 Aug 2017 13:51:15 +0200 Subject: Cache the encoding_in database value, to avoid doing a query for each message --- src/bridge/bridge.cpp | 7 ++----- src/database/database.cpp | 1 + src/database/database.hpp | 35 +++++++++++++++++++++++++++++++++++ src/xmpp/biboumi_adhoc_commands.cpp | 4 ++-- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index e0cb36d..9fd0a5b 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -22,15 +22,12 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid) { #ifdef USE_DATABASE const auto jid = bridge.get_bare_jid(); - auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local()); - auto result = options.col(); - if (!result.empty()) - return result; + return Database::get_encoding_in(jid, iid.get_server(), iid.get_local()); #else (void)bridge; (void)iid; -#endif return {"ISO-8859-1"}; +#endif } Bridge::Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr& poller): diff --git a/src/database/database.cpp b/src/database/database.cpp index 266b17e..0f2349d 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -14,6 +14,7 @@ Database::GlobalOptionsTable Database::global_options("GlobalOptions_"); Database::IrcServerOptionsTable Database::irc_server_options("IrcServerOptions_"); Database::IrcChannelOptionsTable Database::irc_channel_options("IrcChannelOptions_"); Database::RosterTable Database::roster("roster"); +std::map Database::encoding_in_cache{}; void Database::open(const std::string& filename) diff --git a/src/database/database.hpp b/src/database/database.hpp index c00c938..f4b2ecd 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -13,6 +13,7 @@ #include #include +#include class Database @@ -140,7 +141,41 @@ class Database static RosterTable roster; static sqlite3* db; + /** + * Some caches, to avoid doing very frequent query requests for a few options. + */ + using CacheKey = std::tuple; + + static EncodingIn::real_type get_encoding_in(const std::string& owner, + const std::string& server, + const std::string& channel) + { + CacheKey channel_key{owner, server, channel}; + auto it = Database::encoding_in_cache.find(channel_key); + if (it == Database::encoding_in_cache.end()) + { + auto options = Database::get_irc_channel_options_with_server_default(owner, server, channel); + EncodingIn::real_type result = options.col(); + if (result.empty()) + result = "ISO-8859-1"; + it = Database::encoding_in_cache.insert(std::make_pair(channel_key, result)).first; + } + return it->second; + } + static void invalidate_encoding_in_cache(const std::string& owner, + const std::string& server, + const std::string& channel) + { + CacheKey channel_key{owner, server, channel}; + Database::encoding_in_cache.erase(channel_key); + } + static void invalidate_encoding_in_cache() + { + Database::encoding_in_cache.clear(); + } + private: static std::string gen_uuid(); + static std::map encoding_in_cache; }; #endif /* USE_DATABASE */ diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 65f9966..d78dc98 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -430,7 +430,7 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com options.col() = value->get_inner(); } - + Database::invalidate_encoding_in_cache(); options.save(Database::db); command_node.delete_all_children(); @@ -599,7 +599,7 @@ bool handle_irc_channel_configuration_form(XmppComponent& xmpp_component, const } } - + Database::invalidate_encoding_in_cache(requester.bare(), iid.get_server(), iid.get_local()); options.save(Database::db); } return true; -- cgit v1.2.3 From 2c717d347d796a2b007331c42d78146e156eaea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 26 Aug 2017 17:40:54 +0200 Subject: Add an index for the muc_log_line table This immensely speeds up the archive select queries. fix #3292 --- src/database/database.cpp | 3 +++ src/database/index.hpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/database/index.hpp diff --git a/src/database/database.cpp b/src/database/database.cpp index 0f2349d..f706528 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include sqlite3* Database::db; @@ -42,6 +44,7 @@ void Database::open(const std::string& filename) Database::irc_channel_options.upgrade(Database::db); Database::roster.create(Database::db); Database::roster.upgrade(Database::db); + create_index(Database::db, "archive_index", Database::muc_log_lines.get_name()); } diff --git a/src/database/index.hpp b/src/database/index.hpp new file mode 100644 index 0000000..5924779 --- /dev/null +++ b/src/database/index.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include +#include + +namespace +{ +template +typename std::enable_if::type +add_column_name(std::string&) +{ } + +template +typename std::enable_if::type +add_column_name(std::string& out) +{ + using ColumnType = typename std::remove_reference(std::declval>()))>::type; + out += ColumnType::name; + if (N != sizeof...(T) - 1) + out += ","; + add_column_name(out); +} +} + +template +void create_index(sqlite3* db, const std::string& name, const std::string& table) +{ + std::string res{"CREATE INDEX IF NOT EXISTS "}; + res += name + " ON " + table + "("; + add_column_name<0, Columns...>(res); + res += ")"; + + char* error; + const auto result = sqlite3_exec(db, res.data(), nullptr, nullptr, &error); + if (result != SQLITE_OK) + { + log_error("Error executing query: ", error); + sqlite3_free(error); + } +} -- cgit v1.2.3 From 25243f53c2479e2fda0f1a05d1589c8214b70b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 27 Aug 2017 14:32:29 +0200 Subject: =?UTF-8?q?In=20fixed=20mode,=20server=20messages=20come=20from=20?= =?UTF-8?q?biboumi=E2=80=99s=20hostname=20directly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of irc.example.com@biboumi, because that’s actually user named “irc.example.com”, in that case. And that fixes the raw messages in fixed mode. fix #3286 --- src/bridge/bridge.cpp | 7 ++-- src/xmpp/biboumi_component.cpp | 5 ++- src/xmpp/xmpp_component.cpp | 7 +++- tests/end_to_end/__main__.py | 77 ++++++++++++++++++++++++++++-------------- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 9fd0a5b..b1685e0 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -932,7 +932,10 @@ 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", false, false); + if (Config::get("fixed_irc_server", "").empty()) + this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, false); + else + this->xmpp.send_message("", this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, false); } } @@ -947,7 +950,7 @@ void Bridge::send_user_join(const std::string& hostname, const std::string& chan const Iid iid(chan_name, hostname, Iid::Type::Channel); this->send_xmpp_invitation(iid, ""); } - else + else { for (const auto& resource: resources) this->send_user_join(hostname, chan_name, user, user_mode, self, resource); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 0e1d270..6cddeb4 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -281,6 +281,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) { if (body && !body->get_inner().empty()) { + const auto fixed_irc_server = Config::get("fixed_irc_server", ""); // a message for nick!server if (iid.type == Iid::Type::User && !iid.get_local().empty()) { @@ -296,9 +297,11 @@ void BiboumiComponent::handle_message(const Stanza& stanza) bridge->set_preferred_from_jid(user_iid.get_local(), to_str); } else if (iid.type == Iid::Type::Server) + bridge->send_raw_message(iid.get_server(), body->get_inner()); + else if (iid.type == Iid::Type::None && !fixed_irc_server.empty()) { // 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()); + bridge->send_raw_message(fixed_irc_server, body->get_inner()); } } } diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 42a5392..24a85d7 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -277,7 +277,12 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con if (fulljid) message["from"] = from; else - message["from"] = from + "@" + this->served_hostname; + { + if (!from.empty()) + message["from"] = from + "@" + this->served_hostname; + else + message["from"] = this->served_hostname; + } if (!type.empty()) message["type"] = type; XmlSubNode body_node(message, "body"); diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 5317540..f590c1c 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -403,10 +403,14 @@ def handshake_sequence(): partial(send_stanza, "")) -def connection_begin_sequence(irc_host, jid, expected_irc_presence=False): +def connection_begin_sequence(irc_host, jid, expected_irc_presence=False, fixed_irc_server=False): jid = jid.format_map(common_replacements) - xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" + if fixed_irc_server: + xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]" + else: + xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" result = ( partial(expect_stanza, xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), @@ -440,10 +444,14 @@ def connection_begin_sequence(irc_host, jid, expected_irc_presence=False): return result -def connection_tls_begin_sequence(irc_host, jid): +def connection_tls_begin_sequence(irc_host, jid, fixed_irc_server): jid = jid.format_map(common_replacements) - xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" + if fixed_irc_server: + xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]" + else: + xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" irc_host = 'irc.localhost' return ( partial(expect_stanza, @@ -463,10 +471,14 @@ def connection_tls_begin_sequence(irc_host, jid): xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % irc_host)), ) -def connection_end_sequence(irc_host, jid): +def connection_end_sequence(irc_host, jid, fixed_irc_server=False): jid = jid.format_map(common_replacements) - xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" - xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" + if fixed_irc_server: + xpath = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]" + else: + xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" irc_host = 'irc.localhost' return ( partial(expect_stanza, @@ -493,23 +505,26 @@ def connection_end_sequence(irc_host, jid): xpath_re % r'^User mode for \w+ is \[\+Z?i\]$'), ) -def connection_middle_sequence(irc_host, jid): - xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" +def connection_middle_sequence(irc_host, jid, fixed_irc_server=False): + if fixed_irc_server: + xpath_re = "/message[@to='" + jid + "'][@from='biboumi.localhost']/body[re:test(text(), '%s')]" + else: + xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" irc_host = 'irc.localhost' return ( partial(expect_stanza, xpath_re % (r'^%s: \*\*\* You are exempt from flood limits$' % irc_host)), ) -def connection_sequence(irc_host, jid, expected_irc_presence=False): - return connection_begin_sequence(irc_host, jid, expected_irc_presence) +\ - connection_middle_sequence(irc_host, jid) +\ - connection_end_sequence(irc_host, jid) +def connection_sequence(irc_host, jid, expected_irc_presence=False, fixed_irc_server=False): + return connection_begin_sequence(irc_host, jid, expected_irc_presence, fixed_irc_server=fixed_irc_server) +\ + connection_middle_sequence(irc_host, jid, fixed_irc_server=fixed_irc_server) +\ + connection_end_sequence(irc_host, jid, fixed_irc_server=fixed_irc_server) -def connection_tls_sequence(irc_host, jid): - return connection_tls_begin_sequence(irc_host, jid) + \ - connection_middle_sequence(irc_host, jid) +\ - connection_end_sequence(irc_host, jid) +def connection_tls_sequence(irc_host, jid, fixed_irc_server=False): + return connection_tls_begin_sequence(irc_host, jid, fixed_irc_server) + \ + connection_middle_sequence(irc_host, jid, fixed_irc_server) +\ + connection_end_sequence(irc_host, jid, fixed_irc_server) def extract_attribute(xpath, name, stanza): @@ -824,7 +839,7 @@ if __name__ == '__main__': handshake_sequence(), partial(send_stanza, ""), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), partial(expect_stanza, "/message/body[text()='Mode #zgeg [+nt] by {irc_host_one}']"), partial(expect_stanza, @@ -1437,7 +1452,7 @@ if __name__ == '__main__': handshake_sequence(), partial(send_stanza, ""), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), partial(expect_stanza, "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), partial(expect_stanza, @@ -1933,7 +1948,7 @@ if __name__ == '__main__': partial(send_stanza, ""), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), partial(expect_stanza, "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), partial(expect_stanza, @@ -2011,7 +2026,7 @@ if __name__ == '__main__': # First user join partial(send_stanza, ""), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), partial(expect_stanza, "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), partial(expect_stanza, @@ -2402,6 +2417,18 @@ 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("raw_message_fixed_irc_server", + [ + handshake_sequence(), + partial(send_stanza, ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence"), + partial(expect_stanza, "/message"), + + partial(send_stanza, "WHOIS {nick_one}"), + partial(expect_stanza, "/message[@from='{biboumi_host}'][@type='chat']/body[text()='irc.localhost: {nick_one} ~{nick_one} localhost * {nick_one}']"), + ], conf='fixed_server'), Scenario("self_disco_info", [ handshake_sequence(), @@ -2752,7 +2779,7 @@ if __name__ == '__main__': partial(send_stanza, ""), - connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}', fixed_irc_server=True), partial(expect_stanza, "/message"), partial(expect_stanza, "/presence"), partial(expect_stanza, "/message"), @@ -2827,7 +2854,7 @@ if __name__ == '__main__': ""), # We must receive the IRC server presence, in the connection sequence - connection_sequence("irc.localhost", '{jid_one}/{resource_one}', True), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}', expected_irc_presence=True), partial(expect_stanza, "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), partial(expect_stanza, -- cgit v1.2.3 From fcaffb9e778ad5962e69dc23c1fc91eb59a27945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 27 Aug 2017 22:30:44 +0200 Subject: Add support for the "history" node on MUC join Supports the "seconds", "maxstanzas", "since" and "maxchars" (but only =0) attributes. fix #3270 --- src/bridge/bridge.cpp | 16 +++--- src/bridge/bridge.hpp | 7 +-- src/bridge/history_limit.hpp | 8 +++ src/irc/irc_client.cpp | 2 +- src/irc/irc_client.hpp | 7 +++ src/xmpp/biboumi_component.cpp | 28 +++++++++- tests/end_to_end/__main__.py | 118 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 175 insertions(+), 11 deletions(-) create mode 100644 src/bridge/history_limit.hpp diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index b1685e0..3bc618f 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -167,10 +167,11 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname) const } bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password, - const std::string& resource) + const std::string& resource, HistoryLimit history_limit) { const auto& hostname = iid.get_server(); IrcClient* irc = this->make_irc_client(hostname, nickname); + irc->history_limit = history_limit; this->add_resource_to_server(hostname, resource); auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource); if (!res_in_chan) @@ -993,17 +994,20 @@ 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) +void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name, const HistoryLimit& history_limit) { for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) - this->send_room_history(hostname, chan_name, resource); + this->send_room_history(hostname, chan_name, resource, history_limit); } -void Bridge::send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource) +void Bridge::send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource, const HistoryLimit& history_limit) { #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.col()); + auto limit = coptions.col(); + if (history_limit.stanzas >= 0 && history_limit.stanzas < limit) + limit = history_limit.stanzas; + const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, limit, history_limit.since); chan_name.append(utils::empty_if_fixed_server("%" + hostname)); for (const auto& line: lines) { @@ -1257,7 +1261,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_room_history(iid.get_server(), iid.get_local(), resource, irc->history_limit); this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index c10631b..c2f0233 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -74,7 +75,7 @@ public: * Try to join an irc_channel, does nothing and return true if the channel * was already joined. */ - bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password, const std::string& resource); + bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password, const std::string& resource, HistoryLimit history_limit); 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"); @@ -156,8 +157,8 @@ public: /** * 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, std::string chan_name, const std::string& resource); + void send_room_history(const std::string& hostname, const std::string& chan_name, const HistoryLimit& history_limit); + void send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource, const HistoryLimit& history_limit); /** * Send a MUC message from some participant */ diff --git a/src/bridge/history_limit.hpp b/src/bridge/history_limit.hpp new file mode 100644 index 0000000..9c75256 --- /dev/null +++ b/src/bridge/history_limit.hpp @@ -0,0 +1,8 @@ +#pragma once + +// Default values means no limit +struct HistoryLimit +{ + int stanzas{-1}; + std::string since{}; +}; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 7776c8d..5f26bf0 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -784,7 +784,7 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) 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_room_history(this->hostname, chan_name); + this->bridge.send_room_history(this->hostname, chan_name, this->history_limit); this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author); } diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index aec6cd9..de5c520 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include @@ -296,6 +298,11 @@ public: const std::vector& get_sorted_user_modes() const { return this->sorted_user_modes; } std::set get_chantypes() const { return this->chantypes; } + + /** + * Store the history limit that the client asked when joining this room. + */ + HistoryLimit history_limit; private: /** * The hostname of the server we are connected to. diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 6cddeb4..7c5b059 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -24,6 +24,7 @@ #include #include +#include using namespace std::string_literals; @@ -155,8 +156,33 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) bridge->send_irc_nick_change(iid, to.resource, from.resource); const XmlNode* x = stanza.get_child("x", MUC_NS); const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; + const XmlNode* history = x ? x->get_child("history", MUC_NS): nullptr; + HistoryLimit history_limit; + if (history) + { + // TODO implement the "seconds" + const auto seconds = history->get_tag("seconds"); + if (!seconds.empty()) + { + const auto now = std::chrono::system_clock::now(); + std::time_t timestamp = std::chrono::system_clock::to_time_t(now); + int int_seconds = std::atoi(seconds.data()); + timestamp -= int_seconds; + history_limit.since = utils::to_string(timestamp); + } + const auto since = history->get_tag("since"); + if (!since.empty()) + history_limit.since = since; + const auto maxstanzas = history->get_tag("maxstanzas"); + if (!maxstanzas.empty()) + history_limit.stanzas = std::atoi(maxstanzas.data()); + // Ignore any other value, because this is too complex to implement, + // so I won’t do it. + if (history->get_tag("maxchars") == "0") + history_limit.stanzas = 0; + } bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "", - from.resource); + from.resource, history_limit); } else if (type == "unavailable") { diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index f590c1c..859904f 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1942,6 +1942,124 @@ if __name__ == '__main__': partial(expect_stanza, "/iq[@type='result'][@id='id8'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), ]), + + + Scenario("join_history_limits", + [ + 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())]"), + + # Send two channel messages + 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']", + "/message/stable_id:stanza-id[@by='#foo%{irc_server_one}'][@id]",) + ), + + partial(send_stanza, "coucou 2"), + # Record the current time + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou 2']", + after = partial(save_current_timestamp_plus_delta, "first_timestamp", datetime.timedelta(seconds=1))), + + # Wait two seconds before sending two new messages + partial(sleep_for, 2), + partial(send_stanza, "coucou 3"), + partial(send_stanza, "coucou 4"), + partial(expect_stanza, "/message[@type='groupchat']/body[text()='coucou 3']"), + partial(expect_stanza, "/message[@type='groupchat']/body[text()='coucou 4']", + after = partial(save_current_timestamp_plus_delta, "second_timestamp", datetime.timedelta(seconds=1))), + + # join the virtual channel, to stay connected to the server even after leaving #foo + partial(send_stanza, + ""), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message/subject"), + + # Leave #foo + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable']"), + + # Rejoin #foo, with some history limit + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message/subject"), + + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable']"), + + # Rejoin #foo, with some history limit + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"), + partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + partial(expect_stanza, "/message/subject"), + + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable']"), + + + # Rejoin #foo, with some history limit + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + partial(expect_stanza, "/message/subject"), + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable']"), + + # Rejoin #foo, with some history limit + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + partial(expect_stanza, "/message/subject"), + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable']"), + + # Rejoin #foo, with some history limit + partial(send_stanza, + ""), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou']"), partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 2']"), + partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 3']"), + partial(expect_stanza, + "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/body[text()='coucou 4']"), + partial(expect_stanza, "/message/subject"), + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable']"), + + ]), + + Scenario("mam_on_fixed_server", [ handshake_sequence(), -- cgit v1.2.3 From e0d019696e934ec68b847cda2c73d5f90b86a404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 27 Aug 2017 22:37:11 +0200 Subject: Update the Archive documentation, regarding the previous commit --- doc/biboumi.1.rst | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 3de4160..3c5ec8e 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -383,14 +383,23 @@ History Public channel messages are saved into archives, inside the database, unless the `record_history` option is set to false by that user (see `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. +channel) are never stored in the database. 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. +When a channel is joined, if the client doesn’t specify any limit, biboumi +sends the `max_history_length` last messages found in the database as the +MUC history. If a client wants to only use MAM for the archives (because +it’s more convenient and powerful), it should request to receive no +history by using an attribute maxchars='0' or maxstanzas='0' as defined in +XEP 0045, and do a proper MAM request instead. + +Note: the maxchars attribute is ignored unless its value is exactly 0. +Supporting it properly would be very hard and would introduce a lot of +complexity for almost no benefit. + 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. -- cgit v1.2.3 From 98227db6ad005c2e73445ce10e13484cb0568d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 27 Aug 2017 22:41:05 +0200 Subject: Remove a forgotten and useless comment [skip-ci] --- src/xmpp/biboumi_component.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 7c5b059..0b2bba0 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -160,7 +160,6 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) HistoryLimit history_limit; if (history) { - // TODO implement the "seconds" const auto seconds = history->get_tag("seconds"); if (!seconds.empty()) { -- cgit v1.2.3 From dabc48b79b6189c99c246ae01af27fa170fd86a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 30 Aug 2017 16:14:35 +0200 Subject: Mark messages from the IRC server as private and no-copy fix #3284 --- src/bridge/bridge.cpp | 4 ++-- tests/end_to_end/__main__.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 3bc618f..02ba565 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -934,9 +934,9 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho for (const auto& resource: this->resources_in_server[from]) { if (Config::get("fixed_irc_server", "").empty()) - this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, false); + this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, true); else - this->xmpp.send_message("", this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, false); + this->xmpp.send_message("", this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, true); } } diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 859904f..93474ac 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -413,7 +413,11 @@ def connection_begin_sequence(irc_host, jid, expected_irc_presence=False, fixed_ xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" result = ( partial(expect_stanza, - xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), + (xpath % ('Connecting to %s:6697 (encrypted)' % irc_host), + "/message/hints:no-copy", + "/message/carbon:private" + ) + ), partial(expect_stanza, xpath % 'Connection failed: Connection refused'), partial(expect_stanza, @@ -455,7 +459,11 @@ def connection_tls_begin_sequence(irc_host, jid, fixed_irc_server): irc_host = 'irc.localhost' return ( partial(expect_stanza, - xpath % ('Connecting to %s:7778 (encrypted)' % irc_host)), + (xpath % ('Connecting to %s:7778 (encrypted)' % irc_host), + "/message/hints:no-copy", + "/message/carbon:private", + ) + ), partial(expect_stanza, xpath % 'Connected to IRC server (encrypted).'), # These five messages can be receive in any order -- cgit v1.2.3 From 8d99374f1f02a4d229b49f6697247eb1e1f4f940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 6 Sep 2017 21:30:44 +0200 Subject: When biboumi is logging into journald, use sd_journal_send This makes sure that multi-lines messages are properly parsed as a single message by journald. fix #3268 --- src/logger/logger.cpp | 4 +++ src/logger/logger.hpp | 76 ++++++++++++++++++++++++++------------------------- tests/logger.cpp | 5 ---- 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp index 92a3d9b..4287794 100644 --- a/src/logger/logger.cpp +++ b/src/logger/logger.cpp @@ -7,6 +7,10 @@ Logger::Logger(const int log_level): null_buffer{}, null_stream{&null_buffer} { +#ifdef SYSTEMD_FOUND + if (::getenv("JOURNAL_STREAM") != nullptr && this->use_stdout()) + this->use_systemd = true; +#endif } Logger::Logger(const int log_level, const std::string& log_file): diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp index ff6a82b..176726e 100644 --- a/src/logger/logger.hpp +++ b/src/logger/logger.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #define debug_lvl 0 #define info_lvl 1 @@ -19,7 +20,9 @@ #include "biboumi.h" #ifdef SYSTEMD_FOUND +#define SD_JOURNAL_SUPPRESS_LOCATION # include +# include #else # define SD_DEBUG "[DEBUG]: " # define SD_INFO "[INFO]: " @@ -57,6 +60,15 @@ public: Logger(Logger&&) = delete; Logger& operator=(Logger&&) = delete; +#ifdef SYSTEMD_FOUND + bool use_stdout() const + { + return this->stream.rdbuf() == std::cout.rdbuf(); + } + + bool use_systemd{false}; +#endif + private: const int log_level; std::ofstream ofstream{}; @@ -66,8 +78,6 @@ private: std::ostream null_stream; }; -#define WHERE __FILENAME__, ":", __LINE__, ":\t" - namespace logging_details { template @@ -84,45 +94,37 @@ namespace logging_details } template - void log_debug(U&&... args) - { - auto& os = Logger::instance()->get_stream(debug_lvl); - os << SD_DEBUG; - log(os, std::forward(args)...); - } - - template - void log_info(U&&... args) - { - auto& os = Logger::instance()->get_stream(info_lvl); - os << SD_INFO; - log(os, std::forward(args)...); - } - - template - void log_warning(U&&... args) - { - auto& os = Logger::instance()->get_stream(warning_lvl); - os << SD_WARNING; - log(os, std::forward(args)...); - } - - template - void log_error(U&&... args) + void do_logging(const int level, int syslog_level, const char* src_file, int line, U&&... args) { - auto& os = Logger::instance()->get_stream(error_lvl); - os << SD_ERR; - log(os, std::forward(args)...); + #ifdef SYSTEMD_FOUND + if (Logger::instance()->use_systemd) + { + (void)level; + std::ostringstream os; + log(os, std::forward(args)...); + sd_journal_send("MESSAGE=%s", os.str().data(), + "PRIORITY=%i", syslog_level, + "CODE_FILE=%s", src_file, + "CODE_LINE=%i", line, + nullptr); + } + else + { + #endif + static const char* priority_names[] = {"DEBUG", "INFO", "WARNING", "ERROR"}; + auto& os = Logger::instance()->get_stream(level); + os << '[' << priority_names[level] << "]: " << src_file << ':' << line << ":\t"; + log(os, std::forward(args)...); +#ifdef SYSTEMD_FOUND + } +#endif } } -#define log_info(...) logging_details::log_info(WHERE, __VA_ARGS__) - -#define log_warning(...) logging_details::log_warning(WHERE, __VA_ARGS__) - -#define log_error(...) logging_details::log_error(WHERE, __VA_ARGS__) - -#define log_debug(...) logging_details::log_debug(WHERE, __VA_ARGS__) +#define log_debug(...) logging_details::do_logging(debug_lvl, LOG_DEBUG, __FILENAME__, __LINE__, __VA_ARGS__) +#define log_info(...) logging_details::do_logging(info_lvl, LOG_INFO, __FILENAME__, __LINE__, __VA_ARGS__) +#define log_warning(...) logging_details::do_logging(warning_lvl, LOG_WARNING, __FILENAME__, __LINE__, __VA_ARGS__) +#define log_error(...) logging_details::do_logging(error_lvl, LOG_ERR, __FILENAME__, __LINE__, __VA_ARGS__) diff --git a/tests/logger.cpp b/tests/logger.cpp index 1d59a22..1e3392a 100644 --- a/tests/logger.cpp +++ b/tests/logger.cpp @@ -10,13 +10,8 @@ using namespace std::string_literals; TEST_CASE("Basic logging") { -#ifdef SYSTEMD_FOUND - const std::string debug_header = "<7>"; - const std::string error_header = "<3>"; -#else const std::string debug_header = "[DEBUG]: "; const std::string error_header = "[ERROR]: "; -#endif Logger::instance().reset(); GIVEN("A logger with log_level 0") { -- cgit v1.2.3 From bfcf29451787d10c747ad79cb3fd177ac86e9cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 9 Sep 2017 17:09:17 +0200 Subject: Add the persistent_by_default configuration option fix #3293 --- CHANGELOG.rst | 3 +++ doc/biboumi.1.rst | 26 ++++++++++++++++++++++---- src/bridge/bridge.cpp | 2 +- src/config/config.cpp | 8 ++++++++ src/config/config.hpp | 1 + src/database/database.cpp | 5 +++++ src/database/database.hpp | 5 ++++- src/xmpp/biboumi_adhoc_commands.cpp | 4 ++-- tests/end_to_end/__main__.py | 25 ++++++++++++++++++++++++- 9 files changed, 70 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f327fb1..528e63f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,9 @@ Version 6.0 supported. - Invitations can now be sent to any JID, not only JIDs served by the biboumi instance itself. + - The persistent_by_default configuration option has been added, this + lets the administrator decide whether or not the rooms should be + persistent or not by default, for all users. Version 5.0 - 2017-05-24 ======================== diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 3c5ec8e..9227ff6 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -100,6 +100,21 @@ 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. +persistent_by_default +--------------------- + +If this option is set to `true`, all rooms will be persistent by default: +the value of the “persistent” option in the global configuration of each +user will be “true”, but the value of each individual room will still +default to false. This means that a user just needs to change the global +“persistent” configuration option to false in order to override this. + +If it is set to false (the default value), all rooms are not persistent by +default. + +Each room can be configured individually by each user, to override this +default value. See `Ad-hoc commands`_. + realname_customization ---------------------- @@ -595,10 +610,13 @@ On the gateway itself (e.g on the JID biboumi.example.com): the database. * Max history length: The maximum number of lines in the history that the server is allowed to send when joining a channel. - * Persistent: Overrides the value specified in each individual channel, - all channels are persistent, whether or not their specific value is - true or false. See below for more details on what a persistent - channel is. + + * Persistent: Overrides the value specified in each individual channel. + If this option is set to true, all channels are persistent, whether + or not their specific value is true or false. This option is true by + default for everyone if the `persistent_by_default` configuration + option is true, otherwise it’s false. See below for more details on + what a persistent channel is. This value is On a server JID (e.g on the JID chat.freenode.org@biboumi.example.com) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 02ba565..8587264 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -435,7 +435,7 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con bool persistent = false; #ifdef USE_DATABASE const auto goptions = Database::get_global_options(this->user_jid); - if (goptions.col()) + if (goptions.col()) persistent = true; else { diff --git a/src/config/config.cpp b/src/config/config.cpp index 0f3d639..412b170 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -23,6 +23,14 @@ std::string Config::get(const std::string& option, const std::string& def) return it->second; } +bool Config::get_bool(const std::string& option, const bool def) +{ + auto res = Config::get(option, ""); + if (res.empty()) + return def; + return res == "true"; +} + int Config::get_int(const std::string& option, const int& def) { std::string res = Config::get(option, ""); diff --git a/src/config/config.hpp b/src/config/config.hpp index 2ba38cc..c5ef15d 100644 --- a/src/config/config.hpp +++ b/src/config/config.hpp @@ -44,6 +44,7 @@ public: * the second argument as the default. */ static int get_int(const std::string&, const int&); + static bool get_bool(const std::string&, const bool); /** * Set a value for the given option. And write all the config * in the file from which it was read if save is true. diff --git a/src/database/database.cpp b/src/database/database.cpp index f706528..a2b88e2 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include #include @@ -18,6 +20,9 @@ Database::IrcChannelOptionsTable Database::irc_channel_options("IrcChannelOption Database::RosterTable Database::roster("roster"); std::map Database::encoding_in_cache{}; +Database::GlobalPersistent::GlobalPersistent(): + Column{Config::get_bool("persistent_by_default", false)} +{} void Database::open(const std::string& filename) { diff --git a/src/database/database.hpp b/src/database/database.hpp index f4b2ecd..f9695d3 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -73,6 +73,9 @@ class Database struct Persistent: Column { static constexpr auto name = "persistent_"; Persistent(): Column(false) {} }; + struct GlobalPersistent: Column { static constexpr auto name = "persistent_"; + GlobalPersistent(); }; + struct LocalJid: Column { static constexpr auto name = "local"; }; struct RemoteJid: Column { static constexpr auto name = "remote"; }; @@ -81,7 +84,7 @@ class Database using MucLogLineTable = Table; using MucLogLine = MucLogLineTable::RowType; - using GlobalOptionsTable = Table; + using GlobalOptionsTable = Table; using GlobalOptions = GlobalOptionsTable::RowType; using IrcServerOptionsTable = Table; diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index d78dc98..bcdac39 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -159,7 +159,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman { XmlSubNode value(persistent, "value"); value.set_name("value"); - if (options.col()) + if (options.col()) value.set_inner("true"); else value.set_inner("false"); @@ -193,7 +193,7 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, } else if (field->get_tag("var") == "persistent" && value) - options.col() = to_bool(value->get_inner()); + options.col() = to_bool(value->get_inner()); } options.save(Database::db); diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 93474ac..dfb6ef7 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -377,7 +377,15 @@ port=8811 fixed_irc_server=irc.localhost admin=admin@example.com identd_port=1113 -"""} +""", + +'persistent_by_default': +"""hostname=biboumi.localhost +password=coucou +db_name=e2e_test.sqlite +port=8811 +persistent_by_default=true +""",} common_replacements = { 'irc_server_one': 'irc.localhost@biboumi.localhost', @@ -2650,6 +2658,7 @@ if __name__ == '__main__': "/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/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/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")) @@ -2671,6 +2680,20 @@ if __name__ == '__main__': partial(send_stanza, ""), partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='canceled']"), ]), + Scenario("global_configure_persistent_by_default", + [ + 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/dataform:x[@type='form']/dataform:field[@type='boolean'][@var='persistent']/dataform:value[text()='true']", + "/iq/commands:command/commands:actions/commands:next", + ), + ), + ],conf='persistent_by_default'), Scenario("irc_server_configure", [ handshake_sequence(), -- cgit v1.2.3 From e217a1978451ca0153fee36618ff4129ffcd2804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 9 Sep 2017 17:21:51 +0200 Subject: Fix the build without systemd, by adding a few define --- src/logger/logger.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp index 176726e..a99648c 100644 --- a/src/logger/logger.hpp +++ b/src/logger/logger.hpp @@ -28,6 +28,10 @@ # define SD_INFO "[INFO]: " # define SD_WARNING "[WARNING]: " # define SD_ERR "[ERROR]: " +# define LOG_ERR 3 +# define LOG_WARNING 4 +# define LOG_INFO 6 +# define LOG_DEBUG 7 #endif // Macro defined to get the filename instead of the full path. But if it is @@ -111,6 +115,7 @@ namespace logging_details else { #endif + (void)syslog_level; static const char* priority_names[] = {"DEBUG", "INFO", "WARNING", "ERROR"}; auto& os = Logger::instance()->get_stream(level); os << '[' << priority_names[level] << "]: " << src_file << ':' << line << ":\t"; -- cgit v1.2.3 From fe2221121828a66029913c9863e5c7bbf7a1a50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 9 Sep 2017 17:22:16 +0200 Subject: Fix the internal links in the documentation --- doc/biboumi.1.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 9227ff6..354080a 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -29,7 +29,7 @@ Available command line options: config_filename --------------- -Specify the file to read for configuration. See *CONFIG* section for more +Specify the file to read for configuration. See the `Configuration`_ section for more details on its content. Configuration @@ -283,7 +283,7 @@ ISUPPORT extension) then it’s a channel name, otherwise this is a nickname. 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. +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 @@ -377,7 +377,7 @@ IRC server JID -------------- These presence will appear online in the user’s roster whenever they are -connected to that IRC server (see *Connect to an IRC server* for more +connected to that IRC server (see `Connect to an IRC server`_ for more details). This is useful to keep track of which server an user is connected to: this is sometimes hard to remember, when they have many clients, or if they are using persistent channels. @@ -396,7 +396,7 @@ History ------- Public channel messages are saved into archives, inside the database, unless -the `record_history` option is set to false by that user (see `Ad-hoc commands`). +the `record_history` option is set to false by that user (see `Ad-hoc commands`_). Private messages (messages that are sent directly to a nickname, not a channel) are never stored in the database. @@ -702,7 +702,7 @@ Raw IRC messages Biboumi tries to support as many IRC features as possible, but doesn’t handle everything yet (or ever). In order to let the user send any arbitrary IRC message, biboumi forwards any XMPP message received on an IRC -Server JID (see *ADDRESSING*) as a raw command to that IRC server. +Server JID (see `Addressing`_) as a raw command to that IRC server. For example, to WHOIS the user Foo on the server irc.example.com, a user can send the message “WHOIS Foo” to ``irc.example.com@biboumi.example.com``. -- cgit v1.2.3 From c20978fc473c4bbcfbe0cbff3271bb22a8c8c430 Mon Sep 17 00:00:00 2001 From: Link Mauve Date: Sun, 10 Sep 2017 03:09:19 +0200 Subject: CMakeLists: Set symbol visibility to hidden. This reduces the size of the final binary by 12% by not including symbols, and thus function bodies that have been inlined for example. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 596d277..f3a79b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ find_library(LIBUBSAN NAMES ubsan libubsan.so.0) # ## Set various debug flags (instrumentation libs, coverage, …) # -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra -Wconversion") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra -Wconversion -fvisibility=hidden -fvisibility-inlines-hidden") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage --coverage") endif() -- cgit v1.2.3 From ad2ddafc5423d2111b75315852518e7788473404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 16 Sep 2017 14:09:03 +0200 Subject: Add a missing changelog entry --- CHANGELOG.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 528e63f..95a8ab5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,15 +9,17 @@ Version 6.0 - The RecordHistory option can now also be configured for each IRC channel, individually. - Add a global option to make all channels persistent. - - Status code='332' is sent with the unavailable presences when biboumi is + - The persistent_by_default configuration option has been added, this + lets the administrator decide whether or not the rooms should be + persistent or not by default, for all users. +- Status code='332' is sent with the unavailable presences when biboumi is being shutdown or the connection to the IRC server is cut unexpectedly. - Support for botan version 1.11.x has been dropped, only version 2.x is supported. - Invitations can now be sent to any JID, not only JIDs served by the biboumi instance itself. - - The persistent_by_default configuration option has been added, this - lets the administrator decide whether or not the rooms should be - persistent or not by default, for all users. + - The history limits sent by the client when they request to join a + channel is now supported. Version 5.0 - 2017-05-24 ======================== -- cgit v1.2.3 From 2dd38ec87793d17811e367fbe95740b48b19ce9f Mon Sep 17 00:00:00 2001 From: Link Mauve Date: Mon, 18 Sep 2017 15:03:32 +0200 Subject: Stop allowing the 1024-bit for irc.ppirc.net --- conf/irc.ppirc.net.policy.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/conf/irc.ppirc.net.policy.txt b/conf/irc.ppirc.net.policy.txt index 557d129..2357a53 100644 --- a/conf/irc.ppirc.net.policy.txt +++ b/conf/irc.ppirc.net.policy.txt @@ -1,2 +1 @@ key_exchange_methods = RSA -minimum_rsa_bits = 1024 -- cgit v1.2.3 From c1984733c4c50d7a4a7d5ae767ce87bcb3bea1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 19 Sep 2017 21:38:38 +0200 Subject: Release version 6.0 --- CHANGELOG.rst | 4 ++-- CMakeLists.txt | 2 +- packaging/biboumi.spec.cmake | 7 ++----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 95a8ab5..ea1d4f1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,5 @@ -Version 6.0 -=========== +Version 6.0 - 2017-09-17 +======================== - The LiteSQL dependency was removed. Only libsqlite3 is now necessary to work with the database. diff --git a/CMakeLists.txt b/CMakeLists.txt index f3a79b2..c9967ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 6) set(${PROJECT_NAME}_VERSION_MINOR 0) -set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") +set(${PROJECT_NAME}_VERSION_SUFFIX "") if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index d28c840..5c01b80 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -61,11 +61,8 @@ make check %{?_smp_mflags} %changelog -* ${RPM_DATE} Le Coz Florent - ${RPM_VERSION}-1 -- Build latest git revision - -* Wed Jun 14 2017 Le Coz Florent - 6.0-1 - Enable database support by building with sqlite3 +* Tue Sep 19 2017 Le Coz Florent - 6.0-1 + Update to version 6.0 * Wed May 24 2017 Le Coz Florent - 5.0-1 - Update to version 5.0 -- cgit v1.2.3 From 103e508f3b1c623f687c092790ea0f73d92e65f3 Mon Sep 17 00:00:00 2001 From: "Romain DEP." Date: Thu, 21 Sep 2017 08:38:04 +0200 Subject: compat: revert to using sqlite's close() function for compat with older distros. close_v2(), in use before this commit, was introduced as part of sqlite 3.7.14 (2012-09-03), and is as such incompatible with debian wheezy (3.7.13) and centos6 (3.6.20). FTR, Wheezy will be supported until May 2018, and centos6, until November 2020. --- src/database/database.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/database.cpp b/src/database/database.cpp index a2b88e2..8afe6f1 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -35,7 +35,7 @@ void Database::open(const std::string& filename) if (res != SQLITE_OK) { log_error("Failed to open database file ", filename, ": ", sqlite3_errmsg(new_db)); - sqlite3_close_v2(new_db); + sqlite3_close(new_db); throw std::runtime_error(""); } Database::db = new_db; @@ -238,7 +238,7 @@ std::vector Database::get_full_roster() void Database::close() { - sqlite3_close_v2(Database::db); + sqlite3_close(Database::db); Database::db = nullptr; } -- cgit v1.2.3 From 95ae49ef4dfdeffec0895e617e557b2ef1f13888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 21 Sep 2017 21:54:12 +0200 Subject: Fix a little formatting issue in the changelog --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ea1d4f1..f598c86 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,7 +12,7 @@ Version 6.0 - 2017-09-17 - The persistent_by_default configuration option has been added, this lets the administrator decide whether or not the rooms should be persistent or not by default, for all users. -- Status code='332' is sent with the unavailable presences when biboumi is + - Status code='332' is sent with the unavailable presences when biboumi is being shutdown or the connection to the IRC server is cut unexpectedly. - Support for botan version 1.11.x has been dropped, only version 2.x is supported. -- cgit v1.2.3 From 1c3d4f6f5fdf5829e931770523251abe9522914b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 21 Sep 2017 21:57:01 +0200 Subject: Remove a redundant Body definition --- src/bridge/colors.hpp | 12 ++---------- src/xmpp/body.hpp | 4 ++++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/bridge/colors.hpp b/src/bridge/colors.hpp index dceed74..25b085a 100644 --- a/src/bridge/colors.hpp +++ b/src/bridge/colors.hpp @@ -6,20 +6,12 @@ * vice versa. */ +#include + #include #include #include -class XmlNode; - -namespace Xmpp -{ -// Contains: -// - an XMPP-valid UTF-8 body -// - an XML node representing the XHTML-IM body, or null - using body = std::tuple>; -} - #define IRC_FORMAT_BOLD_CHAR '\x02' // done #define IRC_FORMAT_COLOR_CHAR '\x03' // done #define IRC_FORMAT_RESET_CHAR '\x0F' // done diff --git a/src/xmpp/body.hpp b/src/xmpp/body.hpp index 068d1a4..f693cdd 100644 --- a/src/xmpp/body.hpp +++ b/src/xmpp/body.hpp @@ -1,5 +1,9 @@ #pragma once +#include +#include + +class XmlNode; namespace Xmpp { -- cgit v1.2.3 From c5a02685361b95042d5c2ada58cba851bc1cc37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 4 Oct 2017 21:25:27 +0200 Subject: Explicitely include all needed botan headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most importantely, include parsing.h, since read_cfg is not implicitely included anymore in botan 2.3, and that does not compile. Also do not included botan.h anymore, since it’s deprecated in botan 2.3 fix #3296 --- src/network/credentials_manager.cpp | 1 + src/network/credentials_manager.hpp | 3 ++- src/network/tcp_socket_handler.cpp | 1 + src/network/tcp_socket_handler.hpp | 1 - src/network/tls_policy.cpp | 2 ++ 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/network/credentials_manager.cpp b/src/network/credentials_manager.cpp index 7f07cef..b25f442 100644 --- a/src/network/credentials_manager.cpp +++ b/src/network/credentials_manager.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include /** diff --git a/src/network/credentials_manager.hpp b/src/network/credentials_manager.hpp index aa4732a..3a37bdc 100644 --- a/src/network/credentials_manager.hpp +++ b/src/network/credentials_manager.hpp @@ -4,7 +4,8 @@ #ifdef BOTAN_FOUND -#include +#include +#include #include class TCPSocketHandler; diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 6239162..343ec56 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -13,6 +13,7 @@ #ifdef BOTAN_FOUND # include +# include # include # include # include diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp index 5cef739..c598641 100644 --- a/src/network/tcp_socket_handler.hpp +++ b/src/network/tcp_socket_handler.hpp @@ -21,7 +21,6 @@ #ifdef BOTAN_FOUND # include -# include # include # include diff --git a/src/network/tls_policy.cpp b/src/network/tls_policy.cpp index 5439397..b88eb88 100644 --- a/src/network/tls_policy.cpp +++ b/src/network/tls_policy.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include bool BiboumiTLSPolicy::load(const std::string& filename) { -- cgit v1.2.3 From 83aef94a2c0241f7f8146c67a98e02c8f2ecaaf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 4 Oct 2017 21:48:19 +0200 Subject: Update changelog for version 6.1 --- CHANGELOG.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f598c86..cab48dd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,10 @@ +Version 6.1 +=========== + + - Fix compilation with botan 2.3 + - Fix compilation with very old distributions (such as debian wheezy or + centos 6) that ship antique softwares (sqlite3 < 3.7.14) + Version 6.0 - 2017-09-17 ======================== -- cgit v1.2.3 From aa65cfd1064a158ea0ba3482db5e66316495ac24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 4 Oct 2017 22:10:07 +0200 Subject: Release version 6.1 --- CHANGELOG.rst | 4 ++-- CMakeLists.txt | 2 +- packaging/biboumi.spec.cmake | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cab48dd..4c3f5ad 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,5 @@ -Version 6.1 -=========== +Version 6.1 - 2017-10-04 +======================== - Fix compilation with botan 2.3 - Fix compilation with very old distributions (such as debian wheezy or diff --git a/CMakeLists.txt b/CMakeLists.txt index c9967ff..7752382 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.0) project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 6) -set(${PROJECT_NAME}_VERSION_MINOR 0) +set(${PROJECT_NAME}_VERSION_MINOR 1) set(${PROJECT_NAME}_VERSION_SUFFIX "") if(NOT CMAKE_BUILD_TYPE) diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index 5c01b80..a3b94ce 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -61,6 +61,9 @@ make check %{?_smp_mflags} %changelog +* Wed Oct 4 2017 Le Coz Florent - 6.1-1 + Update to version 6.1 + * Tue Sep 19 2017 Le Coz Florent - 6.0-1 Update to version 6.0 -- cgit v1.2.3 From f3b1e39cdd7c55c914659f5ac103c3a0855f8f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 26 Oct 2017 19:13:23 +0200 Subject: Add a workaround for https://github.com/randombit/botan/issues/1276 ref #3278 --- src/network/tcp_socket_handler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 343ec56..602cf94 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -28,6 +28,8 @@ namespace Botan::TLS::Session_Manager_In_Memory& get_session_manager() { static Botan::TLS::Session_Manager_In_Memory session_manager{get_rng()}; + // workaround for https://github.com/randombit/botan/issues/1276 + session_manager.remove_all(); return session_manager; } } -- cgit v1.2.3 From cb6b50dc0109ee3a201a4012b63f3914cc5ea6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 7 Nov 2017 23:05:05 +0100 Subject: Change how we count the number of connected resources to a server --- src/bridge/bridge.cpp | 20 ++++++++++++++++++-- tests/end_to_end/__main__.py | 14 -------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 8587264..eb1d553 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -894,7 +894,19 @@ void Bridge::send_muc_leave(const Iid& iid, const std::string& nick, this->xmpp.send_muc_leave(std::to_string(iid), nick, this->make_xmpp_body(message), this->user_jid + "/" + res, self, user_requested); if (self) - this->remove_all_resources_from_chan(iid.to_tuple()); + { + // Copy the resources currently in that channel + const auto resources_in_chan = this->resources_in_chan[iid.to_tuple()]; + + this->remove_all_resources_from_chan(iid.to_tuple()); + + // Now, for each resource that was in that channel, remove it from the server if it’s + // not in any other channel + for (const auto& r: resources_in_chan) + if (this->number_of_channels_the_resource_is_in(iid.get_server(), r) == 0) + this->remove_resource_from_server(iid.get_server(), r); + + } } IrcClient* irc = this->find_irc_client(iid.get_server()); @@ -1236,9 +1248,13 @@ std::size_t Bridge::number_of_channels_the_resource_is_in(const std::string& irc std::size_t res = 0; for (auto pair: this->resources_in_chan) { - if (std::get<0>(pair.first) == irc_hostname && pair.second.count(resource) != 0) + if (std::get<1>(pair.first) == irc_hostname && pair.second.count(resource) != 0) res++; } + + IrcClient* irc = this->find_irc_client(irc_hostname); + if (irc && (irc->get_dummy_channel().joined || irc->get_dummy_channel().joining)) + res++; return res; } diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index dfb6ef7..d10820d 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -623,8 +623,6 @@ if __name__ == '__main__': partial(send_stanza, "QUIT bye bye"), partial(expect_stanza, ("/presence[@from='#foo%{irc_server_one}/{nick_one}'][@type='unavailable']/muc_user:x/muc_user:status[@code='110']", "/presence[@from='#foo%{irc_server_one}/{nick_one}'][@type='unavailable']/muc_user:x/muc_user:status[@code='110']",)), - 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("multiple_channels_join", [ @@ -678,8 +676,6 @@ if __name__ == '__main__': 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("not_connected_error", [ @@ -724,8 +720,6 @@ if __name__ == '__main__': 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", [ @@ -1030,7 +1024,6 @@ if __name__ == '__main__': partial(send_stanza, "localhostDisconnected by e2e"), partial(expect_unordered, [("/presence[@type='unavailable'][@to='{jid_one}/{resource_one}'][@from='#bon%{irc_server_two}/{nick_three}']",), ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@status='completed']/commands:note[@type='info'][text()='{jid_one} was disconnected from 1 IRC server.']",), - ("/message[@to='{jid_one}/{resource_one}']/body[text()='ERROR: Disconnected by e2e']",), ]), @@ -1048,7 +1041,6 @@ if __name__ == '__main__': partial(send_stanza, "irc.localhostDisconnected by e2e"), partial(expect_unordered, [("/presence[@type='unavailable'][@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']",), ("/iq[@type='result']/commands:command[@node='disconnect-from-irc-server'][@status='completed']/commands:note[@type='info'][text()='{jid_one}/{resource_one} was disconnected from 1 IRC server.']",), - ("/message[@to='{jid_one}/{resource_one}']/body[text()='ERROR: Disconnected by e2e']",), ]), ]), Scenario("multisessionnick", @@ -1203,8 +1195,6 @@ if __name__ == '__main__': # Second user leaves the channel partial(send_stanza, ""), partial(expect_stanza, "/presence[@type='unavailable'][@from='#foo%{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_different_nick", [ @@ -2646,8 +2636,6 @@ if __name__ == '__main__': 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("global_configure", [ @@ -3015,8 +3003,6 @@ if __name__ == '__main__': # Leave the channel, and thus the IRC server partial(send_stanza, ""), partial(expect_stanza, "/presence[@type='unavailable'][@from='#foo%{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.']"), partial(expect_stanza, "/presence[@from='{irc_server_one}'][@to='{jid_one}'][@type='unavailable']"), ]) ) -- cgit v1.2.3 From 90dd8e8b422efdb1fc6c1fb6bfcbc423a4f700a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 7 Nov 2017 23:06:04 +0100 Subject: Fix #3304 --- src/bridge/bridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index eb1d553..925b226 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -468,9 +468,9 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con "Biboumi note: " + std::to_string(resources - 1) + " resources are still in this channel.", true, true, resource); this->remove_resource_from_chan(key, resource); + } if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0) this->remove_resource_from_server(iid.get_server(), resource); - } } -- cgit v1.2.3 From b82d662082f95e5cab8b486fe26b2e263d4a66cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 7 Nov 2017 23:16:42 +0100 Subject: Add an e2e test to demonstrate #3304 --- tests/end_to_end/__main__.py | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d10820d..e024fc5 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -2975,6 +2975,60 @@ if __name__ == '__main__': partial(send_stanza, ""), partial(send_stanza, ""), ]), + Scenario("resource_is_removed_from_server_when_last_chan_is_left", + [ + # Join 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'][@to='{jid_one}/{resource_one}']/subject[not(text())]"), + + # Make it persistent + partial(send_stanza, ""), + partial(expect_stanza, "/iq[@type='result']/muc_owner:query/dataform:x/dataform:field[@var='persistent'][@type='boolean']/dataform:value[text()='false']"), + partial(send_stanza, "true"), + partial(expect_stanza, "/iq[@type='result']"), + + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"), + + # Join the same channel, with the same JID, but a different resource + 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())]"), + + # Join some other channel with someone else + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #bar [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_one}'][@from='#bar%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#bar%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_one}']/subject[not(text())]"), + + # Send two messages from the second user to the first one + partial(send_stanza, "kikoo"), + partial(send_stanza, "second kikoo"), + + # We must receive each message only once, no duplicate + partial(expect_stanza, "/message/body[text()='kikoo']"), + partial(expect_stanza, "/message/body[text()='second kikoo']"), + ] + ), Scenario("irc_server_presence_in_roster", [ handshake_sequence(), -- cgit v1.2.3 From 5b27cee97272d4ae6ff30f03dc57221fa3e3183f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 27 Nov 2017 00:48:40 +0100 Subject: Send \r\n at the end of the identd responses fix #3315 --- src/identd/identd_socket.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/identd/identd_socket.cpp b/src/identd/identd_socket.cpp index b85257c..92cd80b 100644 --- a/src/identd/identd_socket.cpp +++ b/src/identd/identd_socket.cpp @@ -50,14 +50,14 @@ std::string IdentdSocket::generate_answer(const BiboumiComponent& biboumi, uint1 if (pair.second->match_port_pairt(local, remote)) { std::ostringstream os; - os << local << " , " << remote << " : USERID : OTHER : " << hash_jid(bridge->get_bare_jid()); + os << local << " , " << remote << " : USERID : OTHER : " << hash_jid(bridge->get_bare_jid()) << "\r\n"; log_debug("Identd, sending: ", os.str()); return os.str(); } } } std::ostringstream os; - os << local << " , " << remote << " ERROR : NO-USER"; + os << local << " , " << remote << " ERROR : NO-USER" << "\r\n"; log_debug("Identd, sending: ", os.str()); return os.str(); } -- cgit v1.2.3 From 0168b96b79db2627fdba77a8712956408aa081d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 4 Oct 2017 21:28:18 +0200 Subject: Add postgresql support --- CMakeLists.txt | 9 +++ INSTALL.rst | 11 +-- cmake/Modules/FindPQ.cmake | 43 ++++++++++++ src/database/column.hpp | 9 ++- src/database/count_query.hpp | 12 ++-- src/database/database.cpp | 75 ++++++++++---------- src/database/database.hpp | 33 +++++---- src/database/engine.hpp | 40 +++++++++++ src/database/index.hpp | 22 +++--- src/database/insert_query.hpp | 103 ++++++++++++++------------- src/database/postgresql_engine.cpp | 77 ++++++++++++++++++++ src/database/postgresql_engine.hpp | 31 ++++++++ src/database/postgresql_statement.hpp | 128 ++++++++++++++++++++++++++++++++++ src/database/query.cpp | 29 ++++++-- src/database/query.hpp | 39 ++--------- src/database/row.hpp | 105 ++++++++++++++-------------- src/database/select_query.hpp | 24 ++++--- src/database/sqlite3_engine.cpp | 93 ++++++++++++++++++++++++ src/database/sqlite3_engine.hpp | 30 ++++++++ src/database/sqlite3_statement.hpp | 93 ++++++++++++++++++++++++ src/database/statement.hpp | 45 +++++------- src/database/table.cpp | 23 ------ src/database/table.hpp | 89 ++++++++++++----------- src/database/type_to_sql.cpp | 9 --- src/database/type_to_sql.hpp | 16 ----- src/database/update_query.hpp | 100 ++++++++++++++++++++++++++ src/main.cpp | 2 + src/utils/is_one_of.hpp | 17 +++++ src/utils/optional_bool.cpp | 8 +++ src/utils/optional_bool.hpp | 4 +- tests/database.cpp | 13 +++- tests/utils.cpp | 12 ++++ 32 files changed, 1005 insertions(+), 339 deletions(-) create mode 100644 cmake/Modules/FindPQ.cmake create mode 100644 src/database/engine.hpp create mode 100644 src/database/postgresql_engine.cpp create mode 100644 src/database/postgresql_engine.hpp create mode 100644 src/database/postgresql_statement.hpp create mode 100644 src/database/sqlite3_engine.cpp create mode 100644 src/database/sqlite3_engine.hpp create mode 100644 src/database/sqlite3_statement.hpp delete mode 100644 src/database/table.cpp delete mode 100644 src/database/type_to_sql.cpp delete mode 100644 src/database/type_to_sql.hpp create mode 100644 src/database/update_query.hpp create mode 100644 src/utils/is_one_of.hpp create mode 100644 src/utils/optional_bool.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7752382..f7dd534 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,6 +130,12 @@ elseif(NOT WITHOUT_SQLITE3) find_package(SQLITE3) endif() +if(WITH_POSTGRESQL) + find_package(PQ REQUIRED) +elseif(NOT WITHOUT_POSTGRESQL) + find_package(PQ) +endif() + # ## Set all the include directories, depending on what libraries are used # @@ -193,6 +199,7 @@ if(SQLITE3_FOUND) add_library(database OBJECT ${source_database}) include_directories(database ${SQLITE3_INCLUDE_DIRS}) + include_directories(database ${PQ_INCLUDE_DIRS}) set(USE_DATABASE TRUE) else() add_library(database OBJECT "") @@ -261,7 +268,9 @@ if(LIBIDN_FOUND) endif() if(USE_DATABASE) target_link_libraries(${PROJECT_NAME} ${SQLITE3_LIBRARIES}) + target_link_libraries(${PROJECT_NAME} ${PQ_LIBRARIES}) target_link_libraries(test_suite ${SQLITE3_LIBRARIES}) + target_link_libraries(test_suite ${PQ_LIBRARIES}) endif() # Define a __FILENAME__ macro with the relative path (from the base project directory) diff --git a/INSTALL.rst b/INSTALL.rst index 3cf55a2..c98eb52 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -32,10 +32,12 @@ libiconv_ libuuid_ Generate unique IDs -sqlite3_ (option, but highly recommended) - Provides a way to store various options in a (sqlite3) database. Each user - of the gateway can store their own values (for example their prefered port, - or their IRC password). Without this dependency, many interesting features +sqlite3_ +or +libpq_ + Provides a way to store various options in a database. Each user of the + gateway can store their own values (for example their prefered port, or + their IRC password). Without this dependency, many interesting features are missing. libidn_ (optional, but recommended) @@ -165,3 +167,4 @@ to use biboumi. .. _systemd: https://www.freedesktop.org/wiki/Software/systemd/ .. _biboumi.1.rst: doc/biboumi.1.rst .. _gcrypt: https://www.gnu.org/software/libgcrypt/ +.. _libpq: https://www.postgresql.org/docs/current/static/libpq.html diff --git a/cmake/Modules/FindPQ.cmake b/cmake/Modules/FindPQ.cmake new file mode 100644 index 0000000..e268b8f --- /dev/null +++ b/cmake/Modules/FindPQ.cmake @@ -0,0 +1,43 @@ +# - Find libpq +# Find the postgresql front end library +# +# This module defines the following variables: +# PQ_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# PQ_INCLUDE_DIRS - The directory where to find the header file +# PQ_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# PQ_LIBRARY +# PQ_INCLUDE_DIR +# +# This file is in the public domain + +include(FindPkgConfig) + +if(NOT PQ_FOUND) + pkg_check_modules(PQ libpq) +endif() + +if(NOT PQ_FOUND) + find_path(PQ_INCLUDE_DIRS NAMES libpq-fe.h + DOC "The libpq include directory") + + find_library(PQ_LIBRARIES NAMES pq + DOC "The pq library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set PQ_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(PQ REQUIRED_VARS PQ_LIBRARIES PQ_INCLUDE_DIRS) + + if(PQ_FOUND) + set(PQ_LIBRARY ${PQ_LIBRARIES} CACHE INTERNAL "") + set(PQ_INCLUDE_DIR ${PQ_INCLUDE_DIRS} CACHE INTERNAL "") + set(PQ_FOUND ${PQ_FOUND} CACHE INTERNAL "") + endif() +endif() + +mark_as_advanced(PQ_INCLUDE_DIRS PQ_LIBRARIES) diff --git a/src/database/column.hpp b/src/database/column.hpp index 111f9ca..1f16bcf 100644 --- a/src/database/column.hpp +++ b/src/database/column.hpp @@ -13,5 +13,10 @@ struct Column T value{}; }; -struct Id: Column { static constexpr auto name = "id_"; - static constexpr auto options = "PRIMARY KEY AUTOINCREMENT"; }; +struct Id: Column { + static constexpr std::size_t unset_value = static_cast(-1); + static constexpr auto name = "id_"; + static constexpr auto options = "PRIMARY KEY"; + + Id(): Column(-1) {} +}; diff --git a/src/database/count_query.hpp b/src/database/count_query.hpp index 0dde63c..b462e0f 100644 --- a/src/database/count_query.hpp +++ b/src/database/count_query.hpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -15,20 +16,17 @@ struct CountQuery: public Query this->body += std::move(name); } - int64_t execute(sqlite3* db) + int64_t execute(DatabaseEngine& db) { - auto statement = this->prepare(db); + auto statement = db.prepare(this->body); int64_t res = 0; - if (sqlite3_step(statement.get()) == SQLITE_ROW) - res = sqlite3_column_int64(statement.get(), 0); + if (statement->step() != StepResult::Error) + res = statement->get_column_int64(0); else { log_error("Count request didn’t return a result"); return 0; } - if (sqlite3_step(statement.get()) != SQLITE_DONE) - log_warning("Count request returned more than one result."); - return res; } }; diff --git a/src/database/database.cpp b/src/database/database.cpp index 8afe6f1..b1a5ba5 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -7,16 +7,21 @@ #include #include +#include +#include +#include #include +#include + #include -sqlite3* Database::db; -Database::MucLogLineTable Database::muc_log_lines("MucLogLine_"); -Database::GlobalOptionsTable Database::global_options("GlobalOptions_"); -Database::IrcServerOptionsTable Database::irc_server_options("IrcServerOptions_"); -Database::IrcChannelOptionsTable Database::irc_channel_options("IrcChannelOptions_"); +std::unique_ptr Database::db; +Database::MucLogLineTable Database::muc_log_lines("muclogline_"); +Database::GlobalOptionsTable Database::global_options("globaloptions_"); +Database::IrcServerOptionsTable Database::irc_server_options("ircserveroptions_"); +Database::IrcChannelOptionsTable Database::irc_channel_options("ircchanneloptions_"); Database::RosterTable Database::roster("roster"); std::map Database::encoding_in_cache{}; @@ -29,27 +34,26 @@ void Database::open(const std::string& filename) // Try to open the specified database. // Close and replace the previous database pointer if it succeeded. If it did // not, just leave things untouched - sqlite3* new_db; - auto res = sqlite3_open_v2(filename.data(), &new_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); - Database::close(); - if (res != SQLITE_OK) - { - log_error("Failed to open database file ", filename, ": ", sqlite3_errmsg(new_db)); - sqlite3_close(new_db); - throw std::runtime_error(""); - } - Database::db = new_db; - Database::muc_log_lines.create(Database::db); - Database::muc_log_lines.upgrade(Database::db); - Database::global_options.create(Database::db); - Database::global_options.upgrade(Database::db); - Database::irc_server_options.create(Database::db); - Database::irc_server_options.upgrade(Database::db); - Database::irc_channel_options.create(Database::db); - Database::irc_channel_options.upgrade(Database::db); - Database::roster.create(Database::db); - Database::roster.upgrade(Database::db); - create_index(Database::db, "archive_index", Database::muc_log_lines.get_name()); + std::unique_ptr new_db; + static const auto psql_prefix = "postgresql://"s; + if (filename.substr(0, psql_prefix.size()) == psql_prefix) + new_db = PostgresqlEngine::open("dbname="s + filename.substr(psql_prefix.size())); + else + new_db = Sqlite3Engine::open(filename); + if (!new_db) + return; + Database::db = std::move(new_db); + Database::muc_log_lines.create(*Database::db); + Database::muc_log_lines.upgrade(*Database::db); + Database::global_options.create(*Database::db); + Database::global_options.upgrade(*Database::db); + Database::irc_server_options.create(*Database::db); + Database::irc_server_options.upgrade(*Database::db); + Database::irc_channel_options.create(*Database::db); + Database::irc_channel_options.upgrade(*Database::db); + Database::roster.create(*Database::db); + Database::roster.upgrade(*Database::db); + create_index(*Database::db, "archive_index", Database::muc_log_lines.get_name()); } @@ -59,7 +63,7 @@ Database::GlobalOptions Database::get_global_options(const std::string& owner) request.where() << Owner{} << "=" << owner; Database::GlobalOptions options{Database::global_options.get_name()}; - auto result = request.execute(Database::db); + auto result = request.execute(*Database::db); if (result.size() == 1) options = result.front(); else @@ -73,7 +77,7 @@ Database::IrcServerOptions Database::get_irc_server_options(const std::string& o request.where() << Owner{} << "=" << owner << " and " << Server{} << "=" << server; Database::IrcServerOptions options{Database::irc_server_options.get_name()}; - auto result = request.execute(Database::db); + auto result = request.execute(*Database::db); if (result.size() == 1) options = result.front(); else @@ -91,7 +95,7 @@ Database::IrcChannelOptions Database::get_irc_channel_options(const std::string& " and " << Server{} << "=" << server <<\ " and " << Channel{} << "=" << channel; Database::IrcChannelOptions options{Database::irc_channel_options.get_name()}; - auto result = request.execute(Database::db); + auto result = request.execute(*Database::db); if (result.size() == 1) options = result.front(); else @@ -186,7 +190,7 @@ std::vector Database::get_muc_logs(const std::string& owne if (limit >= 0) request.limit() << limit; - auto result = request.execute(Database::db); + auto result = request.execute(*Database::db); return {result.crbegin(), result.crend()}; } @@ -207,7 +211,7 @@ void Database::delete_roster_item(const std::string& local, const std::string& r query << " WHERE " << Database::RemoteJid{} << "=" << remote << \ " AND " << Database::LocalJid{} << "=" << local; - query.execute(Database::db); +// query.execute(*Database::db); } bool Database::has_roster_item(const std::string& local, const std::string& remote) @@ -216,7 +220,7 @@ bool Database::has_roster_item(const std::string& local, const std::string& remo query.where() << Database::LocalJid{} << "=" << local << \ " and " << Database::RemoteJid{} << "=" << remote; - auto res = query.execute(Database::db); + auto res = query.execute(*Database::db); return !res.empty(); } @@ -226,20 +230,19 @@ std::vector Database::get_contact_list(const std::string& auto query = Database::roster.select(); query.where() << Database::LocalJid{} << "=" << local; - return query.execute(Database::db); + return query.execute(*Database::db); } std::vector Database::get_full_roster() { auto query = Database::roster.select(); - return query.execute(Database::db); + return query.execute(*Database::db); } void Database::close() { - sqlite3_close(Database::db); - Database::db = nullptr; + Database::db.release(); } std::string Database::gen_uuid() diff --git a/src/database/database.hpp b/src/database/database.hpp index f9695d3..ec44543 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -7,6 +7,8 @@ #include #include +#include + #include #include @@ -25,11 +27,11 @@ class Database struct Owner: Column { static constexpr auto name = "owner_"; }; - struct IrcChanName: Column { static constexpr auto name = "ircChanName_"; }; + struct IrcChanName: Column { static constexpr auto name = "ircchanname_"; }; struct Channel: Column { static constexpr auto name = "channel_"; }; - struct IrcServerName: Column { static constexpr auto name = "ircServerName_"; }; + struct IrcServerName: Column { static constexpr auto name = "ircservername_"; }; struct Server: Column { static constexpr auto name = "server_"; }; @@ -44,30 +46,30 @@ class Database struct Ports: Column { static constexpr auto name = "ports_"; Ports(): Column("6667") {} }; - struct TlsPorts: Column { static constexpr auto name = "tlsPorts_"; + struct TlsPorts: Column { static constexpr auto name = "tlsports_"; TlsPorts(): Column("6697;6670") {} }; struct Username: Column { static constexpr auto name = "username_"; }; struct Realname: Column { static constexpr auto name = "realname_"; }; - struct AfterConnectionCommand: Column { static constexpr auto name = "afterConnectionCommand_"; }; + struct AfterConnectionCommand: Column { static constexpr auto name = "afterconnectioncommand_"; }; - struct TrustedFingerprint: Column { static constexpr auto name = "trustedFingerprint_"; }; + struct TrustedFingerprint: Column { static constexpr auto name = "trustedfingerprint_"; }; - struct EncodingOut: Column { static constexpr auto name = "encodingOut_"; }; + struct EncodingOut: Column { static constexpr auto name = "encodingout_"; }; - struct EncodingIn: Column { static constexpr auto name = "encodingIn_"; }; + struct EncodingIn: Column { static constexpr auto name = "encodingin_"; }; - struct MaxHistoryLength: Column { static constexpr auto name = "maxHistoryLength_"; + struct MaxHistoryLength: Column { static constexpr auto name = "maxhistorylength_"; MaxHistoryLength(): Column(20) {} }; - struct RecordHistory: Column { static constexpr auto name = "recordHistory_"; + struct RecordHistory: Column { static constexpr auto name = "recordhistory_"; RecordHistory(): Column(true) {}}; - struct RecordHistoryOptional: Column { static constexpr auto name = "recordHistory_"; }; + struct RecordHistoryOptional: Column { static constexpr auto name = "recordhistory_"; }; - struct VerifyCert: Column { static constexpr auto name = "verifyCert_"; + struct VerifyCert: Column { static constexpr auto name = "verifycert_"; VerifyCert(): Column(true) {} }; struct Persistent: Column { static constexpr auto name = "persistent_"; @@ -134,7 +136,7 @@ class Database static int64_t count(const TableType& table) { CountQuery query{table.get_name()}; - return query.execute(Database::db); + return query.execute(*Database::db); } static MucLogLineTable muc_log_lines; @@ -142,7 +144,7 @@ class Database static IrcServerOptionsTable irc_server_options; static IrcChannelOptionsTable irc_channel_options; static RosterTable roster; - static sqlite3* db; + static std::unique_ptr db; /** * Some caches, to avoid doing very frequent query requests for a few options. @@ -177,6 +179,11 @@ class Database Database::encoding_in_cache.clear(); } + static auto raw_exec(const std::string& query) + { + Database::db->raw_exec(query); + } + private: static std::string gen_uuid(); static std::map encoding_in_cache; diff --git a/src/database/engine.hpp b/src/database/engine.hpp new file mode 100644 index 0000000..2dd4c21 --- /dev/null +++ b/src/database/engine.hpp @@ -0,0 +1,40 @@ +#pragma once + +/** + * Interface to provide non-portable behaviour, specific to each + * database engine we want to support. + * + * Everything else (all portable stuf) should go outside of this class. + */ + +#include + +#include +#include +#include +#include +#include + +class DatabaseEngine +{ + public: + + DatabaseEngine() = default; + + DatabaseEngine(const DatabaseEngine&) = delete; + DatabaseEngine& operator=(const DatabaseEngine&) = delete; + DatabaseEngine(DatabaseEngine&&) = delete; + DatabaseEngine& operator=(DatabaseEngine&&) = delete; + + virtual std::set get_all_columns_from_table(const std::string& table_name) = 0; + virtual std::tuple raw_exec(const std::string& query) = 0; + virtual std::unique_ptr prepare(const std::string& query) = 0; + virtual void extract_last_insert_rowid(Statement& statement) = 0; + virtual std::string get_returning_id_sql_string(const std::string&) + { + return {}; + } + virtual std::string id_column_type() = 0; + + int64_t last_inserted_rowid{-1}; +}; diff --git a/src/database/index.hpp b/src/database/index.hpp index 5924779..30766ab 100644 --- a/src/database/index.hpp +++ b/src/database/index.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -25,18 +25,14 @@ add_column_name(std::string& out) } template -void create_index(sqlite3* db, const std::string& name, const std::string& table) +void create_index(DatabaseEngine& db, const std::string& name, const std::string& table) { - std::string res{"CREATE INDEX IF NOT EXISTS "}; - res += name + " ON " + table + "("; - add_column_name<0, Columns...>(res); - res += ")"; + std::string query{"CREATE INDEX IF NOT EXISTS "}; + query += name + " ON " + table + "("; + add_column_name<0, Columns...>(query); + query += ")"; - char* error; - const auto result = sqlite3_exec(db, res.data(), nullptr, nullptr, &error); - if (result != SQLITE_OK) - { - log_error("Error executing query: ", error); - sqlite3_free(error); - } + auto result = db.raw_exec(query); + if (std::get<0>(result) == false) + log_error("Error executing query: ", std::get<1>(result)); } diff --git a/src/database/insert_query.hpp b/src/database/insert_query.hpp index 2ece69d..2fc0512 100644 --- a/src/database/insert_query.hpp +++ b/src/database/insert_query.hpp @@ -12,62 +12,63 @@ #include -template -typename std::enable_if, Id>::value, void>::type -actual_bind(Statement& statement, std::vector& params, const std::tuple&) +template +typename std::enable_if::type +update_autoincrement_id(std::tuple& columns, Statement& statement) { - const auto value = params.front(); - params.erase(params.begin()); - if (sqlite3_bind_text(statement.get(), N + 1, value.data(), static_cast(value.size()), SQLITE_TRANSIENT) != SQLITE_OK) - log_error("Failed to bind ", value, " to param ", N); -} - -template -typename std::enable_if, Id>::value, void>::type -actual_bind(Statement& statement, std::vector&, const std::tuple& columns) -{ - auto&& column = std::get(columns); - if (column.value != 0) + using ColumnType = typename std::decay(columns))>::type; + if (std::is_same::value) { - if (sqlite3_bind_int64(statement.get(), N + 1, static_cast(column.value)) != SQLITE_OK) - log_error("Failed to bind ", column.value, " to id."); + log_debug("EXTRACTING LAST ID"); + auto&& column = std::get(columns); } - else if (sqlite3_bind_null(statement.get(), N + 1) != SQLITE_OK) - log_error("Failed to bind NULL to param ", N); + update_autoincrement_id(columns, statement); } +template +typename std::enable_if::type +update_autoincrement_id(std::tuple&, Statement& statement) +{} + struct InsertQuery: public Query { - InsertQuery(const std::string& name): - Query("INSERT OR REPLACE INTO ") + template + InsertQuery(const std::string& name, const std::tuple& columns): + Query("INSERT INTO ") { this->body += name; + this->insert_col_names(columns); + this->insert_values(columns); } template - void execute(const std::tuple& columns, sqlite3* db) + void execute(DatabaseEngine& db, std::tuple& columns) { - auto statement = this->prepare(db); - { - this->bind_param(columns, statement); - if (sqlite3_step(statement.get()) != SQLITE_DONE) - log_error("Failed to execute query: ", sqlite3_errmsg(db)); - } + auto statement = db.prepare(this->body); + this->bind_param(columns, *statement); + + if (statement->step() != StepResult::Error) + db.extract_last_insert_rowid(*statement); + else + log_error("Failed to extract the rowid from the last INSERT"); } template typename std::enable_if::type - bind_param(const std::tuple& columns, Statement& statement) + bind_param(const std::tuple& columns, Statement& statement, int index=1) { - using ColumnType = typename std::remove_reference(columns))>::type; + auto&& column = std::get(columns); + using ColumnType = std::decay_t; + + if (!std::is_same::value) + actual_bind(statement, column.value, index++); - actual_bind(statement, this->params, columns); - this->bind_param(columns, statement); + this->bind_param(columns, statement, index); } template typename std::enable_if::type - bind_param(const std::tuple&, Statement&) + bind_param(const std::tuple&, Statement&, int) {} template @@ -80,18 +81,21 @@ struct InsertQuery: public Query template typename std::enable_if::type - insert_value(const std::tuple& columns) + insert_value(const std::tuple& columns, int index=1) { - this->body += "?"; - if (N != sizeof...(T) - 1) - this->body += ","; - this->body += " "; - add_param(*this, std::get(columns)); - this->insert_value(columns); + using ColumnType = std::decay_t(columns))>; + + if (!std::is_same::value) + { + this->body += "$" + std::to_string(index++); + if (N != sizeof...(T) - 1) + this->body += ", "; + } + this->insert_value(columns, index); } template typename std::enable_if::type - insert_value(const std::tuple&) + insert_value(const std::tuple&, const int) { } template @@ -99,27 +103,28 @@ struct InsertQuery: public Query { this->body += " ("; this->insert_col_name(columns); - this->body += ")\n"; + this->body += ")"; } template typename std::enable_if::type insert_col_name(const std::tuple& columns) { - using ColumnType = typename std::remove_reference(columns))>::type; + using ColumnType = std::decay_t(columns))>; - this->body += ColumnType::name; + if (!std::is_same::value) + { + this->body += ColumnType::name; - if (N < (sizeof...(T) - 1)) - this->body += ", "; + if (N < (sizeof...(T) - 1)) + this->body += ", "; + } this->insert_col_name(columns); } + template typename std::enable_if::type insert_col_name(const std::tuple&) {} - - - private: }; diff --git a/src/database/postgresql_engine.cpp b/src/database/postgresql_engine.cpp new file mode 100644 index 0000000..9f603ab --- /dev/null +++ b/src/database/postgresql_engine.cpp @@ -0,0 +1,77 @@ +#include + +#include + +#include + +PostgresqlEngine::PostgresqlEngine(PGconn*const conn): + conn(conn) +{} + +PostgresqlEngine::~PostgresqlEngine() +{ + PQfinish(this->conn); +} + +std::unique_ptr PostgresqlEngine::open(const std::string& conninfo) +{ + log_debug("trying to open: ", conninfo); + PGconn* con = PQconnectdb(conninfo.data()); + + if (!con) + { + log_error("Failed to allocate a Postgresql connection"); + throw std::runtime_error(""); + } + const auto status = PQstatus(con); + if (status != CONNECTION_OK) + { + const char* errmsg = PQerrorMessage(con); + log_error("Postgresql connection failed: ", errmsg); + throw std::runtime_error("failed to open connection."); + } + return std::make_unique(con); +} + +std::set PostgresqlEngine::get_all_columns_from_table(const std::string& table_name) +{ + const auto query = "SELECT column_name from information_schema.columns where table_name='" + table_name + "'"; + auto statement = this->prepare(query); + std::set columns; + + while (statement->step() == StepResult::Row) + columns.insert(statement->get_column_text(0)); + + log_debug("found ", columns.size(), " columns."); + return columns; +} + +std::tuple PostgresqlEngine::raw_exec(const std::string& query) +{ + log_debug("raw_exec:", query); + PGresult* res = PQexec(this->conn, query.data()); + auto res_status = PQresultStatus(res); + if (res_status != PGRES_COMMAND_OK) + return std::make_tuple(false, PQresultErrorMessage(res)); + return std::make_tuple(true, std::string{}); +} + +std::unique_ptr PostgresqlEngine::prepare(const std::string& query) +{ + return std::make_unique(query, this->conn); +} + +void PostgresqlEngine::extract_last_insert_rowid(Statement& statement) +{ + this->last_inserted_rowid = statement.get_column_int64(0); +} + +std::string PostgresqlEngine::get_returning_id_sql_string(const std::string& col_name) +{ + return " RETURNING " + col_name; +} + +std::string PostgresqlEngine::id_column_type() +{ + return "SERIAL"; +} diff --git a/src/database/postgresql_engine.hpp b/src/database/postgresql_engine.hpp new file mode 100644 index 0000000..e6444d4 --- /dev/null +++ b/src/database/postgresql_engine.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +#include + +#include +#include +#include +#include + +class PostgresqlEngine: public DatabaseEngine +{ + public: + PostgresqlEngine(PGconn*const conn); + + ~PostgresqlEngine(); + + static std::unique_ptr open(const std::string& string); + + std::set get_all_columns_from_table(const std::string& table_name) override final; + std::tuple raw_exec(const std::string& query) override final; + std::unique_ptr prepare(const std::string& query) override; + void extract_last_insert_rowid(Statement& statement) override; + std::string get_returning_id_sql_string(const std::string& col_name) override; + std::string id_column_type() override; +private: + PGconn* const conn; +}; diff --git a/src/database/postgresql_statement.hpp b/src/database/postgresql_statement.hpp new file mode 100644 index 0000000..5016350 --- /dev/null +++ b/src/database/postgresql_statement.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include + +#include + +#include + +class PostgresqlStatement: public Statement +{ + public: + PostgresqlStatement(std::string body, PGconn*const conn): + body(std::move(body)), + conn(conn) + {} + ~PostgresqlStatement() + {} + PostgresqlStatement(const PostgresqlStatement&) = delete; + PostgresqlStatement& operator=(const PostgresqlStatement&) = delete; + PostgresqlStatement(PostgresqlStatement&& other) = delete; + PostgresqlStatement& operator=(PostgresqlStatement&& other) = delete; + + StepResult step() override final + { + if (!this->executed) + { + this->current_tuple = 0; + this->executed = true; + if (!this->execute()) + return StepResult::Error; + } + else + { + this->current_tuple++; + } + if (this->current_tuple < PQntuples(this->result)) + return StepResult::Row; + return StepResult::Done; + } + + int64_t get_column_int64(const int col) override + { + const char* result = PQgetvalue(this->result, this->current_tuple, col); + std::istringstream iss; + iss.str(result); + int64_t res; + iss >> res; + return res; + } + std::string get_column_text(const int col) override + { + const char* result = PQgetvalue(this->result, this->current_tuple, col); + return result; + } + int get_column_int(const int col) override + { + const char* result = PQgetvalue(this->result, this->current_tuple, col); + std::istringstream iss; + iss.str(result); + int res; + iss >> res; + return res; + } + + void bind(std::vector params) override + { + + this->params = std::move(params); + } + + bool bind_text(const int, const std::string& data) override + { + this->params.push_back(data); + return true; + } + bool bind_int64(const int pos, const std::int64_t value) override + { + this->params.push_back(std::to_string(value)); + return true; + } + bool bind_null(const int pos) override + { + this->params.push_back("NULL"); + return true; + } + + private: + +private: + bool execute() + { + std::vector params; + params.reserve(this->params.size()); + + for (const auto& param: this->params) + { + log_debug("param:", param); + params.push_back(param.data()); + } + + log_debug("body: ", body); + this->result = PQexecParams(this->conn, this->body.data(), + this->params.size(), + nullptr, + params.data(), + nullptr, + nullptr, + 0); + const auto status = PQresultStatus(this->result); + if (status == PGRES_TUPLES_OK) + { + log_debug("PGRES_TUPLES_OK"); + } + else if (status != PGRES_COMMAND_OK) + { + log_error("Failed to execute command: ", PQresultErrorMessage(this->result)); + return false; + } + return true; + } + + bool executed{false}; + std::string body; + PGconn*const conn; + std::vector params; + PGresult* result{nullptr}; + int current_tuple{0}; +}; diff --git a/src/database/query.cpp b/src/database/query.cpp index ba63a92..6f305b2 100644 --- a/src/database/query.cpp +++ b/src/database/query.cpp @@ -1,9 +1,29 @@ #include #include -template <> -void add_param(Query&, const Id&) -{} +void actual_bind(Statement& statement, const std::string& value, int index) +{ + log_debug("binding string:", value, " to col ", index); + statement.bind_text(index, value); +} + +void actual_bind(Statement& statement, const std::size_t value, int index) +{ + log_debug("binding size_t:", value); + statement.bind_int64(index, value); +} + +void actual_bind(Statement& statement, const OptionalBool& value, int index) +{ + log_debug("binding optional_t:", value.to_string()); + if (!value.is_set) + statement.bind_int64(index, 0); + else if (value.value) + statement.bind_int64(index, 1); + else + statement.bind_int64(index, -1); +} + void actual_add_param(Query& query, const std::string& val) { @@ -28,7 +48,8 @@ Query& operator<<(Query& query, const char* str) Query& operator<<(Query& query, const std::string& str) { - query.body += "?"; + query.body += "$" + std::to_string(query.current_param); + query.current_param++; actual_add_param(query, str); return query; } diff --git a/src/database/query.hpp b/src/database/query.hpp index 6e1db12..2467749 100644 --- a/src/database/query.hpp +++ b/src/database/query.hpp @@ -11,52 +11,27 @@ #include +void actual_bind(Statement& statement, const std::string& value, int index); +void actual_bind(Statement& statement, const std::size_t value, int index); +void actual_bind(Statement& statement, const OptionalBool& value, int index); + struct Query { std::string body; std::vector params; + int current_param{1}; Query(std::string str): body(std::move(str)) {} - - Statement prepare(sqlite3* db) - { - sqlite3_stmt* stmt; - auto res = sqlite3_prepare(db, this->body.data(), static_cast(this->body.size()) + 1, - &stmt, nullptr); - if (res != SQLITE_OK) - { - log_error("Error preparing statement: ", sqlite3_errmsg(db)); - return nullptr; - } - Statement statement(stmt); - int i = 1; - for (const std::string& param: this->params) - { - if (sqlite3_bind_text(statement.get(), i, param.data(), static_cast(param.size()), SQLITE_TRANSIENT) != SQLITE_OK) - log_error("Failed to bind ", param, " to param ", i); - i++; - } - - return statement; - } - - void execute(sqlite3* db) - { - auto statement = this->prepare(db); - while (sqlite3_step(statement.get()) != SQLITE_DONE) - ; - } }; template void add_param(Query& query, const ColumnType& column) { + std::cout << "add_param" << std::endl; actual_add_param(query, column.value); } -template <> -void add_param(Query& query, const Id& column); template void actual_add_param(Query& query, const T& val) @@ -81,7 +56,7 @@ template typename std::enable_if::value, Query&>::type operator<<(Query& query, const Integer& i) { - query.body += "?"; + query.body += "$" + std::to_string(query.current_param++); actual_add_param(query, i); return query; } diff --git a/src/database/row.hpp b/src/database/row.hpp index 2b50874..b69fac5 100644 --- a/src/database/row.hpp +++ b/src/database/row.hpp @@ -1,72 +1,73 @@ #pragma once #include +#include #include +#include + #include #include -template -typename std::enable_if, Id>::value, void>::type -update_id(std::tuple&, sqlite3*) -{} - -template -typename std::enable_if, Id>::value, void>::type -update_id(std::tuple& columns, sqlite3* db) +template +struct Row { - auto&& column = std::get(columns); - auto res = sqlite3_last_insert_rowid(db); - column.value = static_cast(res); -} + Row(std::string name): + table_name(std::move(name)) + {} -template -typename std::enable_if::type -update_autoincrement_id(std::tuple& columns, sqlite3* db) -{ - using ColumnType = typename std::remove_reference(columns))>::type; - update_id(columns, db); - update_autoincrement_id(columns, db); -} + template + typename Type::real_type& col() + { + auto&& col = std::get(this->columns); + return col.value; + } -template -typename std::enable_if::type -update_autoincrement_id(std::tuple&, sqlite3*) -{} + template + const auto& col() const + { + auto&& col = std::get(this->columns); + return col.value; + } -template -struct Row -{ - Row(std::string name): - table_name(std::move(name)) - {} + template + void save(std::unique_ptr& db, typename std::enable_if && Coucou>::type* = nullptr) + { + this->insert(*db); + } - template - typename Type::real_type& col() - { - auto&& col = std::get(this->columns); - return col.value; - } + template + void save(std::unique_ptr& db, typename std::enable_if && Coucou>::type* = nullptr) + { + const Id& id = std::get(this->columns); + if (id.value == Id::unset_value) + { + this->insert(*db); + std::get(this->columns).value = db->last_inserted_rowid; + } + else + this->update(*db); + } - template - const auto& col() const - { - auto&& col = std::get(this->columns); - return col.value; - } + private: + void insert(DatabaseEngine& db) + { + InsertQuery query(this->table_name, this->columns); + // Ugly workaround for non portable stuff + query.body += db.get_returning_id_sql_string(Id::name); + query.execute(db, this->columns); + } - void save(sqlite3* db) - { - InsertQuery query(this->table_name); - query.insert_col_names(this->columns); - query.insert_values(this->columns); + void update(DatabaseEngine& db) + { + UpdateQuery query(this->table_name, this->columns); - query.execute(this->columns, db); + query.execute(db, this->columns); + } - update_autoincrement_id(this->columns, db); - } +public: + std::tuple columns; + std::string table_name; - std::tuple columns; - std::string table_name; }; diff --git a/src/database/select_query.hpp b/src/database/select_query.hpp index 872001c..22b342e 100644 --- a/src/database/select_query.hpp +++ b/src/database/select_query.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -18,24 +20,21 @@ template typename std::enable_if::value, sqlite3_int64>::type extract_row_value(Statement& statement, const int i) { - return sqlite3_column_int64(statement.get(), i); + return statement.get_column_int64(i); } template typename std::enable_if::value, T>::type extract_row_value(Statement& statement, const int i) { - const auto size = sqlite3_column_bytes(statement.get(), i); - const unsigned char* str = sqlite3_column_text(statement.get(), i); - std::string result(reinterpret_cast(str), static_cast(size)); - return result; + return statement.get_column_text(i); } template typename std::enable_if::value, T>::type extract_row_value(Statement& statement, const int i) { - const auto integer = sqlite3_column_int(statement.get(), i); + const auto integer = statement.get_column_int(i); OptionalBool result; if (integer > 0) result.set_value(true); @@ -109,16 +108,21 @@ struct SelectQuery: public Query return *this; } - auto execute(sqlite3* db) + auto execute(DatabaseEngine& db) { - auto statement = this->prepare(db); std::vector> rows; - while (sqlite3_step(statement.get()) == SQLITE_ROW) + + auto statement = db.prepare(this->body); + statement->bind(std::move(this->params)); + + while (statement->step() == StepResult::Row) { + log_debug("one result."); Row row(this->table_name); - extract_row_values(row, statement); + extract_row_values(row, *statement); rows.push_back(row); } + return rows; } diff --git a/src/database/sqlite3_engine.cpp b/src/database/sqlite3_engine.cpp new file mode 100644 index 0000000..7e34f89 --- /dev/null +++ b/src/database/sqlite3_engine.cpp @@ -0,0 +1,93 @@ +#include + +#include + +#include +#include +#include + +Sqlite3Engine::Sqlite3Engine(sqlite3* db): + db(db) +{ +} + +Sqlite3Engine::~Sqlite3Engine() +{ + sqlite3_close(this->db); +} + +std::set Sqlite3Engine::get_all_columns_from_table(const std::string& table_name) +{ + std::set result; + char* errmsg; + std::string query{"PRAGMA table_info(" + table_name + ")"}; + int res = sqlite3_exec(this->db, query.data(), [](void* param, int columns_nb, char** columns, char**) -> int { + constexpr int name_column = 1; + std::set* result = static_cast*>(param); + if (name_column < columns_nb) + result->insert(utils::tolower(columns[name_column])); + return 0; + }, &result, &errmsg); + + if (res != SQLITE_OK) + { + log_error("Error executing ", query, ": ", errmsg); + sqlite3_free(errmsg); + } + + log_debug("List of columns in table ", table_name, ":"); + for (const auto& c: result) + log_debug(c); + return result; +} + +std::unique_ptr Sqlite3Engine::open(const std::string& filename) +{ + sqlite3* new_db; + auto res = sqlite3_open_v2(filename.data(), &new_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); + if (res != SQLITE_OK) + { + log_error("Failed to open database file ", filename, ": ", sqlite3_errmsg(new_db)); + sqlite3_close(new_db); + throw std::runtime_error(""); + } + return std::make_unique(new_db); +} + +std::tuple Sqlite3Engine::raw_exec(const std::string& query) +{ + char* error; + const auto result = sqlite3_exec(db, query.data(), nullptr, nullptr, &error); + if (result != SQLITE_OK) + { + std::string err_msg(error); + sqlite3_free(error); + return std::make_tuple(false, err_msg); + } + return std::make_tuple(true, std::string{}); +} + +std::unique_ptr Sqlite3Engine::prepare(const std::string& query) +{ + sqlite3_stmt* stmt; + log_debug("SQLITE3: ", query); + auto res = sqlite3_prepare(db, query.data(), static_cast(query.size()) + 1, + &stmt, nullptr); + if (res != SQLITE_OK) + { + log_error("Error preparing statement: ", sqlite3_errmsg(db)); + return nullptr; + } + return std::make_unique(stmt); +} + +void Sqlite3Engine::extract_last_insert_rowid(Statement& statement) +{ + this->last_inserted_rowid = sqlite3_last_insert_rowid(this->db); + log_debug("extracted inserted ID: ", this->last_inserted_rowid); +} + +std::string Sqlite3Engine::id_column_type() +{ + return "INTEGER PRIMARY KEY AUTOINCREMENT"; +} diff --git a/src/database/sqlite3_engine.hpp b/src/database/sqlite3_engine.hpp new file mode 100644 index 0000000..eb18d0c --- /dev/null +++ b/src/database/sqlite3_engine.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include + +class Sqlite3Engine: public DatabaseEngine +{ + public: + Sqlite3Engine(sqlite3* db); + + ~Sqlite3Engine(); + + static std::unique_ptr open(const std::string& string); + + std::set get_all_columns_from_table(const std::string& table_name) override final; + std::tuple raw_exec(const std::string& query) override final; + std::unique_ptr prepare(const std::string& query) override; + void extract_last_insert_rowid(Statement& statement) override; + std::string id_column_type() override; +private: + sqlite3* const db; +}; + diff --git a/src/database/sqlite3_statement.hpp b/src/database/sqlite3_statement.hpp new file mode 100644 index 0000000..42a5220 --- /dev/null +++ b/src/database/sqlite3_statement.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include + +#include + +#include + +class Sqlite3Statement: public Statement +{ + public: + Sqlite3Statement(sqlite3_stmt* stmt): + stmt(stmt) {} + ~Sqlite3Statement() + { + sqlite3_finalize(this->stmt); + } + + StepResult step() override final + { + auto res = sqlite3_step(this->get()); + log_debug("step: ", res); + if (res == SQLITE_ROW) + return StepResult::Row; + else if (res == SQLITE_DONE) + return StepResult::Done; + else + return StepResult::Error; + } + + void bind(std::vector params) override + { + int i = 1; + for (const std::string& param: params) + { + if (sqlite3_bind_text(this->get(), i, param.data(), static_cast(param.size()), SQLITE_TRANSIENT) != SQLITE_OK) + log_error("Failed to bind ", param, " to param ", i); + i++; + } + } + + int64_t get_column_int64(const int col) override + { + return sqlite3_column_int64(this->get(), col); + } + + std::string get_column_text(const int col) override + { + const auto size = sqlite3_column_bytes(this->get(), col); + const unsigned char* str = sqlite3_column_text(this->get(), col); + std::string result(reinterpret_cast(str), static_cast(size)); + return result; + } + + bool bind_text(const int pos, const std::string& data) override + { + return sqlite3_bind_text(this->get(), pos, data.data(), static_cast(data.size()), SQLITE_TRANSIENT) == SQLITE_OK; + } + bool bind_int64(const int pos, const std::int64_t value) override + { + return sqlite3_bind_int64(this->get(), pos, static_cast(value)) == SQLITE_OK; + } + bool bind_null(const int pos) override + { + return sqlite3_bind_null(this->get(), pos) == SQLITE_OK; + } + int get_column_int(const int col) override + { + return sqlite3_column_int(this->get(), col); + } + + Sqlite3Statement(const Sqlite3Statement&) = delete; + Sqlite3Statement& operator=(const Sqlite3Statement&) = delete; + Sqlite3Statement(Sqlite3Statement&& other): + stmt(other.stmt) + { + other.stmt = nullptr; + } + Sqlite3Statement& operator=(Sqlite3Statement&& other) + { + this->stmt = other.stmt; + other.stmt = nullptr; + return *this; + } + sqlite3_stmt* get() + { + return this->stmt; + } + + private: + sqlite3_stmt* stmt; + int last_step_result{SQLITE_OK}; +}; diff --git a/src/database/statement.hpp b/src/database/statement.hpp index 87cd70f..db5f31b 100644 --- a/src/database/statement.hpp +++ b/src/database/statement.hpp @@ -1,35 +1,28 @@ #pragma once -#include +#include +#include +#include + +enum class StepResult +{ + Row, + Done, + Error, +}; class Statement { public: - Statement(sqlite3_stmt* stmt): - stmt(stmt) {} - ~Statement() - { - sqlite3_finalize(this->stmt); - } + virtual StepResult step() = 0; + + virtual void bind(std::vector params) = 0; - Statement(const Statement&) = delete; - Statement& operator=(const Statement&) = delete; - Statement(Statement&& other): - stmt(other.stmt) - { - other.stmt = nullptr; - } - Statement& operator=(Statement&& other) - { - this->stmt = other.stmt; - other.stmt = nullptr; - return *this; - } - sqlite3_stmt* get() - { - return this->stmt; - } + virtual std::int64_t get_column_int64(const int col) = 0; + virtual std::string get_column_text(const int col) = 0; + virtual int get_column_int(const int col) = 0; - private: - sqlite3_stmt* stmt; + virtual bool bind_text(const int pos, const std::string& data) = 0; + virtual bool bind_int64(const int pos, const std::int64_t value) = 0; + virtual bool bind_null(const int pos) = 0; }; diff --git a/src/database/table.cpp b/src/database/table.cpp deleted file mode 100644 index 9224d79..0000000 --- a/src/database/table.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -std::set get_all_columns_from_table(sqlite3* db, const std::string& table_name) -{ - std::set result; - char* errmsg; - std::string query{"PRAGMA table_info(" + table_name + ")"}; - int res = sqlite3_exec(db, query.data(), [](void* param, int columns_nb, char** columns, char**) -> int { - constexpr int name_column = 1; - std::set* result = static_cast*>(param); - if (name_column < columns_nb) - result->insert(columns[name_column]); - return 0; - }, &result, &errmsg); - - if (res != SQLITE_OK) - { - log_error("Error executing ", query, ": ", errmsg); - sqlite3_free(errmsg); - } - - return result; -} diff --git a/src/database/table.hpp b/src/database/table.hpp index 0060211..5fbc301 100644 --- a/src/database/table.hpp +++ b/src/database/table.hpp @@ -1,7 +1,8 @@ #pragma once +#include + #include -#include #include #include @@ -10,23 +11,27 @@ using namespace std::string_literals; -std::set get_all_columns_from_table(sqlite3* db, const std::string& table_name); +template +std::string ToSQLType(DatabaseEngine& db) +{ + if (std::is_same::value) + return db.id_column_type(); + else if (std::is_same::value) + return "TEXT"; + else + return "INTEGER"; +} template -void add_column_to_table(sqlite3* db, const std::string& table_name) +void add_column_to_table(DatabaseEngine& db, const std::string& table_name) { const std::string name = ColumnType::name; - std::string query{"ALTER TABLE " + table_name + " ADD " + ColumnType::name + " " + TypeToSQLType::type}; - char* error; - const auto result = sqlite3_exec(db, query.data(), nullptr, nullptr, &error); - if (result != SQLITE_OK) - { - log_error("Error adding column ", name, " to table ", table_name, ": ", error); - sqlite3_free(error); - } + std::string query{"ALTER TABLE " + table_name + " ADD " + ColumnType::name + " " + ToSQLType(db)}; + auto res = db.raw_exec(query); + if (std::get<0>(res) == false) + log_error("Error adding column ", name, " to table ", table_name, ": ", std::get<1>(res)); } - template void append_option(std::string& s) { @@ -50,27 +55,24 @@ class Table name(std::move(name)) {} - void upgrade(sqlite3* db) + void upgrade(DatabaseEngine& db) { - const auto existing_columns = get_all_columns_from_table(db, this->name); + const auto existing_columns = db.get_all_columns_from_table(this->name); add_column_if_not_exists(db, existing_columns); } - void create(sqlite3* db) + void create(DatabaseEngine& db) { - std::string res{"CREATE TABLE IF NOT EXISTS "}; - res += this->name; - res += " (\n"; - this->add_column_create(res); - res += ")"; - - char* error; - const auto result = sqlite3_exec(db, res.data(), nullptr, nullptr, &error); - if (result != SQLITE_OK) - { - log_error("Error executing query: ", error); - sqlite3_free(error); - } + std::string query{"CREATE TABLE IF NOT EXISTS "}; + query += this->name; + query += " (\n"; + this->add_column_create(db, query); + query += ")"; + + log_debug("create:" , query); + auto result = db.raw_exec(query); + if (std::get<0>(result) == false) + log_error("Error executing query: ", std::get<1>(result)); } RowType row() @@ -78,7 +80,7 @@ class Table return {this->name}; } - SelectQuery select() + auto select() { SelectQuery select(this->name); return select; @@ -93,39 +95,44 @@ class Table template typename std::enable_if::type - add_column_if_not_exists(sqlite3* db, const std::set& existing_columns) + add_column_if_not_exists(DatabaseEngine& db, const std::set& existing_columns) { using ColumnType = typename std::remove_reference(std::declval()))>::type; - if (existing_columns.count(ColumnType::name) != 1) - { - add_column_to_table(db, this->name); - } + if (existing_columns.count(ColumnType::name) == 0) + add_column_to_table(db, this->name); add_column_if_not_exists(db, existing_columns); } template typename std::enable_if::type - add_column_if_not_exists(sqlite3*, const std::set&) + add_column_if_not_exists(DatabaseEngine&, const std::set&) {} template typename std::enable_if::type - add_column_create(std::string& str) + add_column_create(DatabaseEngine& db, std::string& str) { using ColumnType = typename std::remove_reference(std::declval()))>::type; - using RealType = typename ColumnType::real_type; +// using RealType = typename ColumnType::real_type; str += ColumnType::name; str += " "; - str += TypeToSQLType::type; - append_option(str); +// if (std::is_same::value) +// { +// str += "INTEGER PRIMARY KEY AUTOINCREMENT"; +// } +// else +// { + str += ToSQLType(db); +// append_option(str); +// } if (N != sizeof...(T) - 1) str += ","; str += "\n"; - add_column_create(str); + add_column_create(db, str); } template typename std::enable_if::type - add_column_create(std::string&) + add_column_create(DatabaseEngine&, std::string&) { } const std::string name; diff --git a/src/database/type_to_sql.cpp b/src/database/type_to_sql.cpp deleted file mode 100644 index bcd9daa..0000000 --- a/src/database/type_to_sql.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include - -template <> const std::string TypeToSQLType::type = "INTEGER"; -template <> const std::string TypeToSQLType::type = "INTEGER"; -template <> const std::string TypeToSQLType::type = "INTEGER"; -template <> const std::string TypeToSQLType::type = "INTEGER"; -template <> const std::string TypeToSQLType::type = "INTEGER"; -template <> const std::string TypeToSQLType::type = "TEXT"; -template <> const std::string TypeToSQLType::type = "INTEGER"; \ No newline at end of file diff --git a/src/database/type_to_sql.hpp b/src/database/type_to_sql.hpp deleted file mode 100644 index ba806ab..0000000 --- a/src/database/type_to_sql.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -#include - -template -struct TypeToSQLType { static const std::string type; }; - -template <> const std::string TypeToSQLType::type; -template <> const std::string TypeToSQLType::type; -template <> const std::string TypeToSQLType::type; -template <> const std::string TypeToSQLType::type; -template <> const std::string TypeToSQLType::type; -template <> const std::string TypeToSQLType::type; -template <> const std::string TypeToSQLType::type; \ No newline at end of file diff --git a/src/database/update_query.hpp b/src/database/update_query.hpp new file mode 100644 index 0000000..32befc0 --- /dev/null +++ b/src/database/update_query.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include +#include + +using namespace std::string_literals; + +template +struct Index; + +template +struct Index> +{ + static const std::size_t value = 0; +}; + +template +struct Index> +{ + static const std::size_t value = Index>::value + 1; +}; + +struct UpdateQuery: public Query +{ + template + UpdateQuery(const std::string& name, const std::tuple& columns): + Query("UPDATE ") + { + this->body += name; + this->insert_col_names_and_values(columns); + } + + template + void insert_col_names_and_values(const std::tuple& columns) + { + this->body += " SET "; + this->insert_col_name_and_value(columns); + this->body += " WHERE "s + Id::name + "=$" + std::to_string(this->current_param); + } + + template + typename std::enable_if::type + insert_col_name_and_value(const std::tuple& columns) + { + using ColumnType = std::decay_t(columns))>; + + if (!std::is_same::value) + { + this->body += ColumnType::name + "=$"s + std::to_string(this->current_param); + this->current_param++; + + if (N < (sizeof...(T) - 1)) + this->body += ", "; + } + + this->insert_col_name_and_value(columns); + } + template + typename std::enable_if::type + insert_col_name_and_value(const std::tuple&) + {} + + + template + void execute(DatabaseEngine& db, const std::tuple& columns) + { + auto statement = db.prepare(this->body); + this->bind_param(columns, *statement); + this->bind_id(columns, *statement); + + statement->step(); + } + + template + typename std::enable_if::type + bind_param(const std::tuple& columns, Statement& statement, int index=1) + { + auto&& column = std::get(columns); + using ColumnType = std::decay_t; + + if (!std::is_same::value) + actual_bind(statement, column.value, index++); + + this->bind_param(columns, statement, index); + } + + template + typename std::enable_if::type + bind_param(const std::tuple&, Statement&, int) + {} + + template + void bind_id(const std::tuple& columns, Statement& statement) + { + static constexpr auto index = Index>::value; + auto&& value = std::get(columns); + + actual_bind(statement, value.value, sizeof...(T)); + } +}; diff --git a/src/main.cpp b/src/main.cpp index 5725584..284d289 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,8 @@ #include #include +#include + #ifdef UDNS_FOUND # include #endif diff --git a/src/utils/is_one_of.hpp b/src/utils/is_one_of.hpp new file mode 100644 index 0000000..4d6770e --- /dev/null +++ b/src/utils/is_one_of.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +template +struct is_one_of_implem { + static constexpr bool value = false; +}; + +template +struct is_one_of_implem { + static constexpr bool value = + std::is_same::value || is_one_of_implem::value; +}; + +template +constexpr bool is_one_of = is_one_of_implem::value; diff --git a/src/utils/optional_bool.cpp b/src/utils/optional_bool.cpp new file mode 100644 index 0000000..56fdca2 --- /dev/null +++ b/src/utils/optional_bool.cpp @@ -0,0 +1,8 @@ +#include + + +std::ostream& operator<<(std::ostream& os, const OptionalBool& o) +{ + os << o.to_string(); + return os; +} diff --git a/src/utils/optional_bool.hpp b/src/utils/optional_bool.hpp index 59bbbab..867aca2 100644 --- a/src/utils/optional_bool.hpp +++ b/src/utils/optional_bool.hpp @@ -20,7 +20,7 @@ struct OptionalBool this->is_set = false; } - std::string to_string() + std::string to_string() const { if (this->is_set == false) return "unset"; @@ -33,3 +33,5 @@ struct OptionalBool bool is_set{false}; bool value{false}; }; + +std::ostream& operator<<(std::ostream& os, const OptionalBool& o); diff --git a/tests/database.cpp b/tests/database.cpp index f49220a..aeddea3 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -7,13 +7,25 @@ TEST_CASE("Database") { #ifdef USE_DATABASE +// Database::open("postgresql://test"); Database::open(":memory:"); + Database::raw_exec("DELETE FROM " + Database::irc_server_options.get_name()); + Database::raw_exec("DELETE FROM " + Database::irc_channel_options.get_name()); SECTION("Basic retrieve and update") { auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + CHECK(Database::count(Database::irc_server_options) == 0); o.save(Database::db); + CHECK(Database::count(Database::irc_server_options) == 1); + o.col() = "Different realname"; + CHECK(o.col() == "Different realname"); + o.save(Database::db); + CHECK(o.col() == "Different realname"); + CHECK(Database::count(Database::irc_server_options) == 1); + auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + CHECK(a.col() == "Different realname"); auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); // b does not yet exist in the db, the object is created but not yet @@ -28,7 +40,6 @@ TEST_CASE("Database") SECTION("channel options") { - Config::set("db_name", ":memory:"); auto o = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo"); CHECK(o.col() == ""); diff --git a/tests/utils.cpp b/tests/utils.cpp index c5ef7e7..6de19f0 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -11,6 +11,7 @@ #include #include #include +#include using namespace std::string_literals; @@ -171,3 +172,14 @@ TEST_CASE("dirname") CHECK(utils::dirname(".") == "."); CHECK(utils::dirname("./") == "./"); } + +TEST_CASE("is_in") +{ + CHECK((is_one_of) == true); + CHECK((is_one_of) == false); + CHECK((is_one_of) == false); + CHECK((is_one_of) == true); + CHECK((is_one_of) == false); + CHECK((is_one_of) == true); + CHECK((is_one_of) == true); +} -- cgit v1.2.3 From 4f7ad3f9855cf62649c40d912e6c3a6a2b777913 Mon Sep 17 00:00:00 2001 From: Jonas Wielicki Date: Tue, 28 Nov 2017 18:52:56 +0100 Subject: Support for full postgresql URIs (cf. https://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-CONNSTRING) --- 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 b1a5ba5..19e9988 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -37,7 +37,7 @@ void Database::open(const std::string& filename) std::unique_ptr new_db; static const auto psql_prefix = "postgresql://"s; if (filename.substr(0, psql_prefix.size()) == psql_prefix) - new_db = PostgresqlEngine::open("dbname="s + filename.substr(psql_prefix.size())); + new_db = PostgresqlEngine::open(filename); else new_db = Sqlite3Engine::open(filename); if (!new_db) -- cgit v1.2.3 From 14dcc57a19229dfaf878e07229e8f3e66857a75d Mon Sep 17 00:00:00 2001 From: Jonas Wielicki Date: Tue, 28 Nov 2017 18:53:16 +0100 Subject: Make destructor of Statement virtual I got an ASAN error otherwise (type mismatch) --- src/database/statement.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/database/statement.hpp b/src/database/statement.hpp index db5f31b..4a61928 100644 --- a/src/database/statement.hpp +++ b/src/database/statement.hpp @@ -14,6 +14,7 @@ enum class StepResult class Statement { public: + virtual ~Statement() = default; virtual StepResult step() = 0; virtual void bind(std::vector params) = 0; -- cgit v1.2.3 From 58f78f31e65e8628bb14722eb8b4b42a558cd0e0 Mon Sep 17 00:00:00 2001 From: Jonas Wielicki Date: Tue, 28 Nov 2017 20:38:29 +0100 Subject: Make destructor of DatabaseEngine virtual --- src/database/engine.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/database/engine.hpp b/src/database/engine.hpp index 2dd4c21..41dccf5 100644 --- a/src/database/engine.hpp +++ b/src/database/engine.hpp @@ -20,6 +20,7 @@ class DatabaseEngine public: DatabaseEngine() = default; + virtual ~DatabaseEngine() = default; DatabaseEngine(const DatabaseEngine&) = delete; DatabaseEngine& operator=(const DatabaseEngine&) = delete; -- cgit v1.2.3 From e1c7a6518a1aeaac1786c321aee35bd5e20acf6f Mon Sep 17 00:00:00 2001 From: Jonas Wielicki Date: Wed, 29 Nov 2017 08:03:23 +0100 Subject: Actually free the database on Database::close() .release() returns the pointer and releases ownership *without* destruction. --- 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 19e9988..f9a365d 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -242,7 +242,7 @@ std::vector Database::get_full_roster() void Database::close() { - Database::db.release(); + Database::db = nullptr; } std::string Database::gen_uuid() -- cgit v1.2.3 From 414bbca0bc2bf20c4f424c2368997a46129b32cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 1 Dec 2017 14:59:22 +0100 Subject: Handle postgresql and sqlite3 libs properly Do not fail to compile when one of them is missing but the other one is not. Raise an error when trying to open a database with the missing library. see #3237 --- CMakeLists.txt | 22 +++++++++++++++------- src/biboumi.h.cmake | 2 ++ src/database/count_query.hpp | 2 -- src/database/database.cpp | 2 -- src/database/insert_query.hpp | 2 -- src/database/postgresql_engine.cpp | 5 +++++ src/database/postgresql_engine.hpp | 27 ++++++++++++++++++++++----- src/database/query.hpp | 2 -- src/database/row.hpp | 2 -- src/database/select_query.hpp | 4 +--- src/database/sqlite3_engine.cpp | 6 ++++++ src/database/sqlite3_engine.hpp | 19 ++++++++++++++++++- src/main.cpp | 5 ++--- 13 files changed, 71 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7dd534..ba11aa6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -193,13 +193,17 @@ file(GLOB source_network src/network/*.[hc]pp) add_library(network OBJECT ${source_network}) -if(SQLITE3_FOUND) +if(SQLITE3_FOUND OR PQ_FOUND) file(GLOB source_database src/database/*.[hc]pp) add_library(database OBJECT ${source_database}) - include_directories(database ${SQLITE3_INCLUDE_DIRS}) - include_directories(database ${PQ_INCLUDE_DIRS}) + if(SQLITE3_FOUND) + include_directories(database ${SQLITE3_INCLUDE_DIRS}) + endif() + if(PQ_FOUND) + include_directories(database ${PQ_INCLUDE_DIRS}) + endif() set(USE_DATABASE TRUE) else() add_library(database OBJECT "") @@ -267,10 +271,14 @@ if(LIBIDN_FOUND) target_link_libraries(test_suite ${LIBIDN_LIBRARIES}) endif() if(USE_DATABASE) - target_link_libraries(${PROJECT_NAME} ${SQLITE3_LIBRARIES}) - target_link_libraries(${PROJECT_NAME} ${PQ_LIBRARIES}) - target_link_libraries(test_suite ${SQLITE3_LIBRARIES}) - target_link_libraries(test_suite ${PQ_LIBRARIES}) + if(SQLITE3_FOUND) + target_link_libraries(${PROJECT_NAME} ${SQLITE3_LIBRARIES}) + target_link_libraries(test_suite ${SQLITE3_LIBRARIES}) + endif() + if(PQ_FOUND) + target_link_libraries(${PROJECT_NAME} ${PQ_LIBRARIES}) + target_link_libraries(test_suite ${PQ_LIBRARIES}) +endif() endif() # Define a __FILENAME__ macro with the relative path (from the base project directory) diff --git a/src/biboumi.h.cmake b/src/biboumi.h.cmake index 1ad9a40..5bc1004 100644 --- a/src/biboumi.h.cmake +++ b/src/biboumi.h.cmake @@ -6,6 +6,8 @@ #cmakedefine BOTAN_FOUND #cmakedefine GCRYPT_FOUND #cmakedefine UDNS_FOUND +#cmakedefine PQ_FOUND +#cmakedefine SQLITE3_FOUND #cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}" #cmakedefine PROJECT_NAME "${PROJECT_NAME}" #cmakedefine HAS_GET_TIME diff --git a/src/database/count_query.hpp b/src/database/count_query.hpp index b462e0f..e8d24ef 100644 --- a/src/database/count_query.hpp +++ b/src/database/count_query.hpp @@ -6,8 +6,6 @@ #include -#include - struct CountQuery: public Query { CountQuery(std::string name): diff --git a/src/database/database.cpp b/src/database/database.cpp index f9a365d..ae5654c 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -15,8 +15,6 @@ #include -#include - std::unique_ptr Database::db; Database::MucLogLineTable Database::muc_log_lines("muclogline_"); Database::GlobalOptionsTable Database::global_options("globaloptions_"); diff --git a/src/database/insert_query.hpp b/src/database/insert_query.hpp index 2fc0512..853b7f1 100644 --- a/src/database/insert_query.hpp +++ b/src/database/insert_query.hpp @@ -10,8 +10,6 @@ #include #include -#include - template typename std::enable_if::type update_autoincrement_id(std::tuple& columns, Statement& statement) diff --git a/src/database/postgresql_engine.cpp b/src/database/postgresql_engine.cpp index 9f603ab..1c01ed5 100644 --- a/src/database/postgresql_engine.cpp +++ b/src/database/postgresql_engine.cpp @@ -1,3 +1,6 @@ +#include +#ifdef PQ_FOUND + #include #include @@ -75,3 +78,5 @@ std::string PostgresqlEngine::id_column_type() { return "SERIAL"; } + +#endif diff --git a/src/database/postgresql_engine.hpp b/src/database/postgresql_engine.hpp index e6444d4..fe4fb53 100644 --- a/src/database/postgresql_engine.hpp +++ b/src/database/postgresql_engine.hpp @@ -1,16 +1,20 @@ #pragma once -#include +#include +#include +#include +#include #include +#include -#include - -#include -#include #include #include +#ifdef PQ_FOUND + +#include + class PostgresqlEngine: public DatabaseEngine { public: @@ -29,3 +33,16 @@ class PostgresqlEngine: public DatabaseEngine private: PGconn* const conn; }; + +#else + +class PostgresqlEngine +{ +public: + static std::unique_ptr open(const std::string& string) + { + throw std::runtime_error("Cannot open postgresql database "s + string + ": biboumi is not compiled with libpq."); + } +}; + +#endif diff --git a/src/database/query.hpp b/src/database/query.hpp index 2467749..7f8aecb 100644 --- a/src/database/query.hpp +++ b/src/database/query.hpp @@ -9,8 +9,6 @@ #include #include -#include - void actual_bind(Statement& statement, const std::string& value, int index); void actual_bind(Statement& statement, const std::size_t value, int index); void actual_bind(Statement& statement, const OptionalBool& value, int index); diff --git a/src/database/row.hpp b/src/database/row.hpp index b69fac5..1b50ff9 100644 --- a/src/database/row.hpp +++ b/src/database/row.hpp @@ -8,8 +8,6 @@ #include -#include - template struct Row { diff --git a/src/database/select_query.hpp b/src/database/select_query.hpp index 22b342e..837a866 100644 --- a/src/database/select_query.hpp +++ b/src/database/select_query.hpp @@ -12,12 +12,10 @@ #include #include -#include - using namespace std::string_literals; template -typename std::enable_if::value, sqlite3_int64>::type +typename std::enable_if::value, std::int64_t>::type extract_row_value(Statement& statement, const int i) { return statement.get_column_int64(i); diff --git a/src/database/sqlite3_engine.cpp b/src/database/sqlite3_engine.cpp index 7e34f89..a94a892 100644 --- a/src/database/sqlite3_engine.cpp +++ b/src/database/sqlite3_engine.cpp @@ -1,3 +1,7 @@ +#include + +#ifdef SQLITE3_FOUND + #include #include @@ -91,3 +95,5 @@ std::string Sqlite3Engine::id_column_type() { return "INTEGER PRIMARY KEY AUTOINCREMENT"; } + +#endif diff --git a/src/database/sqlite3_engine.hpp b/src/database/sqlite3_engine.hpp index eb18d0c..5b8176c 100644 --- a/src/database/sqlite3_engine.hpp +++ b/src/database/sqlite3_engine.hpp @@ -4,12 +4,17 @@ #include -#include #include #include #include #include +#include + +#ifdef SQLITE3_FOUND + +#include + class Sqlite3Engine: public DatabaseEngine { public: @@ -28,3 +33,15 @@ private: sqlite3* const db; }; +#else + +class Sqlite3Engine +{ +public: + static std::unique_ptr open(const std::string& string) + { + throw std::runtime_error("Cannot open sqlite3 database "s + string + ": biboumi is not compiled with sqlite3 lib."); + } +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index 284d289..c877e43 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,8 +6,6 @@ #include #include -#include - #ifdef UDNS_FOUND # include #endif @@ -90,7 +88,8 @@ int main(int ac, char** av) #ifdef USE_DATABASE try { open_database(); - } catch (...) { + } catch (const std::exception& e) { + log_error(e.what()); return 1; } #endif -- cgit v1.2.3 From 6dc49f32844b846bd9675ed6a9d669e266122276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 1 Dec 2017 15:06:23 +0100 Subject: Fix a few warnings --- src/database/postgresql_statement.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/database/postgresql_statement.hpp b/src/database/postgresql_statement.hpp index 5016350..6e5dec8 100644 --- a/src/database/postgresql_statement.hpp +++ b/src/database/postgresql_statement.hpp @@ -73,12 +73,12 @@ class PostgresqlStatement: public Statement this->params.push_back(data); return true; } - bool bind_int64(const int pos, const std::int64_t value) override + bool bind_int64(const int, const std::int64_t value) override { this->params.push_back(std::to_string(value)); return true; } - bool bind_null(const int pos) override + bool bind_null(const int) override { this->params.push_back("NULL"); return true; @@ -99,8 +99,9 @@ private: } log_debug("body: ", body); + const int param_size = static_cast(this->params.size()); this->result = PQexecParams(this->conn, this->body.data(), - this->params.size(), + param_size, nullptr, params.data(), nullptr, -- cgit v1.2.3 From 24dc05dd979264143223e166faa032e75f986b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 3 Dec 2017 16:28:38 +0100 Subject: Run some of the ci tests against a postgresql docker container --- .gitlab-ci.yml | 21 +++++++++++++++++++-- CMakeLists.txt | 2 +- src/bridge/bridge.cpp | 1 + src/xmpp/biboumi_component.cpp | 6 ++++++ tests/database.cpp | 19 +++++++++++++++++-- 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b70297..cd6b307 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,6 +17,7 @@ variables: SYSTEMD: "-DWITH_SYSTEMD=1" LIBIDN: "-DWITH_LIBIDN=1" SQLITE3: "-DWITH_SQLITE3=1" + POSTGRESQL: "-WITH_POSTGRESQL=1" # ## Build jobs @@ -27,10 +28,10 @@ variables: tags: - docker script: - - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3}" + - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3} ${POSTGRESQL}" - mkdir build/ - cd build/ - - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3} + - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3} ${POSTGRESQL} - make everything -j$(nproc || echo 1) - make coverage_check -j$(nproc || echo 1) artifacts: @@ -64,21 +65,27 @@ build:alpine: build:1: variables: BOTAN: "-DWITHOUT_BOTAN=1" + POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" <<: *fedora_build build:2: variables: UDNS: "-DWITHOUT_UDNS=1" + POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" <<: *fedora_build build:3: variables: SQLITE3: "-DWITHOUT_SQLITE3=1" + TEST_POSTGRES_URI: "postgres@postgres/postgres" + services: + - postgres:latest <<: *fedora_build build:4: variables: SQLITE3: "-DWITHOUT_SQLITE3=1" + POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" BOTAN: "-DWITHOUT_BOTAN=1" LIBIDN: "-DWITHOUT_LIBIDN=1" <<: *fedora_build @@ -87,17 +94,27 @@ build:5: variables: SQLITE3: "-DWITHOUT_SQLITE3=1" UDNS: "-DWITHOUT_UDNS=1" + TEST_POSTGRES_URI: "postgres@postgres/postgres" + services: + - postgres:latest <<: *fedora_build build:6: variables: BOTAN: "-DWITHOUT_BOTAN=1" UDNS: "-DWITHOUT_UDNS=1" + POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" <<: *fedora_build build:7: variables: UDNS: "-DWITHOUT_UDNS=1" + POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" + <<: *fedora_build + +build:8: + variables: + POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" <<: *fedora_build diff --git a/CMakeLists.txt b/CMakeLists.txt index ba11aa6..155b73a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ endif() # ## Find optional instrumentation libraries that will be used in debug only # -find_library(LIBASAN NAMES asan libasan.so.3 libasan.so.2 libasan.so.1) +find_library(LIBASAN NAMES asan libasan.so.4 libasan.so.3 libasan.so.2 libasan.so.1) find_library(LIBUBSAN NAMES ubsan libubsan.so.0) # diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 925b226..57f0628 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1031,6 +1031,7 @@ void Bridge::send_room_history(const std::string& hostname, std::string chan_nam (void)hostname; (void)chan_name; (void)resource; + (void)history_limit; #endif } diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 0b2bba0..51ca78d 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -1080,6 +1080,9 @@ void BiboumiComponent::on_irc_client_connected(const std::string& irc_hostname, const auto local_jid = irc_hostname + "@" + this->served_hostname; if (Database::has_roster_item(local_jid, jid)) this->send_presence_to_contact(local_jid, jid, ""); +#else + (void)irc_hostname; + (void)jid; #endif } @@ -1089,6 +1092,9 @@ void BiboumiComponent::on_irc_client_disconnected(const std::string& irc_hostnam const auto local_jid = irc_hostname + "@" + this->served_hostname; if (Database::has_roster_item(local_jid, jid)) this->send_presence_to_contact(irc_hostname + "@" + this->served_hostname, jid, "unavailable"); +#else + (void)irc_hostname; + (void)jid; #endif } diff --git a/tests/database.cpp b/tests/database.cpp index aeddea3..20a446b 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -1,14 +1,29 @@ #include "catch.hpp" +#include + +#ifdef USE_DATABASE + +#include + #include #include TEST_CASE("Database") { -#ifdef USE_DATABASE -// Database::open("postgresql://test"); +#ifdef PQ_FOUND + std::string postgresql_uri{"postgresql://"}; + const char* env_value = ::getenv("TEST_POSTGRES_URI"); + if (env_value != nullptr) + postgresql_uri += env_value; + else + postgresql_uri += "/test"; + Database::open(postgresql_uri); +#else Database::open(":memory:"); +#endif + Database::raw_exec("DELETE FROM " + Database::irc_server_options.get_name()); Database::raw_exec("DELETE FROM " + Database::irc_channel_options.get_name()); -- cgit v1.2.3 From c3313d0d418cb2aaaf2226eb0a7729ef567b6afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 5 Dec 2017 01:05:45 +0100 Subject: Always free the PGresult pointer returned by PQexec Fix a somewhat big memory leak --- src/database/postgresql_engine.cpp | 6 ++++++ src/database/postgresql_statement.hpp | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/database/postgresql_engine.cpp b/src/database/postgresql_engine.cpp index 1c01ed5..4ee4223 100644 --- a/src/database/postgresql_engine.cpp +++ b/src/database/postgresql_engine.cpp @@ -1,6 +1,8 @@ #include #ifdef PQ_FOUND +#include + #include #include @@ -53,6 +55,10 @@ std::tuple PostgresqlEngine::raw_exec(const std::string& quer { log_debug("raw_exec:", query); PGresult* res = PQexec(this->conn, query.data()); + auto sg = utils::make_scope_guard([res](){ + PQclear(res); + }); + auto res_status = PQresultStatus(res); if (res_status != PGRES_COMMAND_OK) return std::make_tuple(false, PQresultErrorMessage(res)); diff --git a/src/database/postgresql_statement.hpp b/src/database/postgresql_statement.hpp index 6e5dec8..30c3ec2 100644 --- a/src/database/postgresql_statement.hpp +++ b/src/database/postgresql_statement.hpp @@ -14,7 +14,10 @@ class PostgresqlStatement: public Statement conn(conn) {} ~PostgresqlStatement() - {} + { + PQclear(this->result); + this->result = nullptr; + } PostgresqlStatement(const PostgresqlStatement&) = delete; PostgresqlStatement& operator=(const PostgresqlStatement&) = delete; PostgresqlStatement(PostgresqlStatement&& other) = delete; -- cgit v1.2.3 From 53fc926dc847af55a13a6ac9ea852658b0b0ba4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 12 Dec 2017 23:23:18 +0100 Subject: Only run the unit tests with postgresql if TEST_POSTGRES_URI env var is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Even if we built with postgresql’s support --- .gitlab-ci.yml | 11 ----------- tests/database.cpp | 9 +++------ 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cd6b307..5a3c7fd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -65,13 +65,11 @@ build:alpine: build:1: variables: BOTAN: "-DWITHOUT_BOTAN=1" - POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" <<: *fedora_build build:2: variables: UDNS: "-DWITHOUT_UDNS=1" - POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" <<: *fedora_build build:3: @@ -92,7 +90,6 @@ build:4: build:5: variables: - SQLITE3: "-DWITHOUT_SQLITE3=1" UDNS: "-DWITHOUT_UDNS=1" TEST_POSTGRES_URI: "postgres@postgres/postgres" services: @@ -103,21 +100,13 @@ build:6: variables: BOTAN: "-DWITHOUT_BOTAN=1" UDNS: "-DWITHOUT_UDNS=1" - POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" <<: *fedora_build build:7: variables: UDNS: "-DWITHOUT_UDNS=1" - POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" <<: *fedora_build -build:8: - variables: - POSTGRESQL: "-DWITHOUT_POSTGRESQL=1" - <<: *fedora_build - - # ## Test jobs # diff --git a/tests/database.cpp b/tests/database.cpp index 20a446b..7ab6da8 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -16,13 +16,10 @@ TEST_CASE("Database") std::string postgresql_uri{"postgresql://"}; const char* env_value = ::getenv("TEST_POSTGRES_URI"); if (env_value != nullptr) - postgresql_uri += env_value; + Database::open("postgresql://"s + env_value); else - postgresql_uri += "/test"; - Database::open(postgresql_uri); -#else - Database::open(":memory:"); #endif + Database::open(":memory:"); Database::raw_exec("DELETE FROM " + Database::irc_server_options.get_name()); Database::raw_exec("DELETE FROM " + Database::irc_channel_options.get_name()); @@ -121,5 +118,5 @@ TEST_CASE("Database") } Database::close(); -#endif } +#endif -- cgit v1.2.3 From 80168aef293308bc462092fbebb939fa58cd1c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20=E2=80=9Cpep=E2=80=9D=20Buquet?= Date: Fri, 15 Dec 2017 15:21:45 +0000 Subject: Add specific policy files for irc.gimp.org and irc.gnome.org --- conf/irc.gimp.org.policy.txt | 1 + conf/irc.gnome.org.policy.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 conf/irc.gimp.org.policy.txt create mode 100644 conf/irc.gnome.org.policy.txt diff --git a/conf/irc.gimp.org.policy.txt b/conf/irc.gimp.org.policy.txt new file mode 100644 index 0000000..2357a53 --- /dev/null +++ b/conf/irc.gimp.org.policy.txt @@ -0,0 +1 @@ +key_exchange_methods = RSA diff --git a/conf/irc.gnome.org.policy.txt b/conf/irc.gnome.org.policy.txt new file mode 100644 index 0000000..2357a53 --- /dev/null +++ b/conf/irc.gnome.org.policy.txt @@ -0,0 +1 @@ +key_exchange_methods = RSA -- cgit v1.2.3 From a557718bf2f16c440313e5d5409b23dcefa45ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 16 Dec 2017 15:34:54 +0100 Subject: db_name also accepts postgres:// scheme for PostgreSQL connections --- src/database/database.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/database/database.cpp b/src/database/database.cpp index ae5654c..3622963 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -34,7 +34,9 @@ void Database::open(const std::string& filename) // not, just leave things untouched std::unique_ptr new_db; static const auto psql_prefix = "postgresql://"s; - if (filename.substr(0, psql_prefix.size()) == psql_prefix) + static const auto psql_prefix2 = "postgres://"s; + if ((filename.substr(0, psql_prefix.size()) == psql_prefix) || + (filename.substr(0, psql_prefix2.size()) == psql_prefix2)) new_db = PostgresqlEngine::open(filename); else new_db = Sqlite3Engine::open(filename); -- cgit v1.2.3 From 8a12b547e8aed2c6880a7ac4b7a6031f5d077368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 16 Dec 2017 15:42:22 +0100 Subject: Document the db_name option (with postgresql support) --- doc/biboumi.1.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 354080a..89f8627 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -77,6 +77,20 @@ port The TCP port to use to connect to the local XMPP component. The default value is 5347. +db_name +------- + +The name of the database to use. This option can only be used if biboumi +has been compiled with a database support (Sqlite3 and/or PostgreSQL). If +the value begins with the postgresql scheme, “postgresql://” or +“postgres://”, then biboumi will try to connect to the PostgreSQL database +specified by the URI. See +https://www.postgresql.org/docs/current/static/libpq-connect.html#idm46428693970032 +for all possible values. For example the value could be +“postgresql://user:secret@localhost”. If the value does not start with the +postgresql scheme, then it specifies a filename that will be opened with +Sqlite3. For example the value could be “/var/lib/biboumi/biboumi.sqlite”. + admin ----- -- cgit v1.2.3 From b1f850b6395610c738a8e58abcdf2abfca3edd4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 16 Dec 2017 16:18:59 +0100 Subject: Update the docker images to enable postgresql, and document them --- docker/biboumi-test/alpine/Dockerfile | 3 ++- docker/biboumi-test/debian/Dockerfile | 3 ++- docker/biboumi-test/fedora/Dockerfile | 1 + docker/biboumi/alpine/Dockerfile | 2 ++ docker/biboumi/alpine/README.md | 25 ++++++++++++++++++++++++- 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/docker/biboumi-test/alpine/Dockerfile b/docker/biboumi-test/alpine/Dockerfile index f97c58c..e43f1b6 100644 --- a/docker/biboumi-test/alpine/Dockerfile +++ b/docker/biboumi-test/alpine/Dockerfile @@ -32,7 +32,8 @@ RUN apk add --no-cache g++\ openssl\ libressl-dev\ zlib-dev\ - curl + curl\ + postgresql-dev # Install botan RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan diff --git a/docker/biboumi-test/debian/Dockerfile b/docker/biboumi-test/debian/Dockerfile index 3a1c1a7..65c964e 100644 --- a/docker/biboumi-test/debian/Dockerfile +++ b/docker/biboumi-test/debian/Dockerfile @@ -39,7 +39,8 @@ RUN apt install -y g++\ openssl\ zlib1g-dev\ libssl-dev\ - curl + curl\ + libpq-dev # Install botan RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile index 8ff418c..12e13e5 100644 --- a/docker/biboumi-test/fedora/Dockerfile +++ b/docker/biboumi-test/fedora/Dockerfile @@ -39,6 +39,7 @@ RUN dnf --refresh install -y\ openssl-devel\ which\ java-1.8.0-openjdk\ + postgresql-devel\ && dnf clean all # Install botan diff --git a/docker/biboumi/alpine/Dockerfile b/docker/biboumi/alpine/Dockerfile index c1bf5fd..0b59eb7 100644 --- a/docker/biboumi/alpine/Dockerfile +++ b/docker/biboumi/alpine/Dockerfile @@ -13,6 +13,7 @@ RUN apk add --no-cache\ make\ udns-dev\ sqlite-dev\ + postgresql-dev\ libuuid\ util-linux-dev\ expat-dev\ @@ -30,6 +31,7 @@ RUN git clone git://git.louiz.org/biboumi && mkdir ./biboumi/build && cd ./bibou -DWITH_BOTAN=1\ -DWITH_SQLITE3=1\ -DWITH_LIBIDN=1\ + -DWITH_POSTGRESQL=1\ && make -j8 && make install && rm -rf /biboumi RUN adduser biboumi -D -h /home/biboumi diff --git a/docker/biboumi/alpine/README.md b/docker/biboumi/alpine/README.md index 806bdc5..6385e94 100644 --- a/docker/biboumi/alpine/README.md +++ b/docker/biboumi/alpine/README.md @@ -38,6 +38,7 @@ The configuration file inside the image contains only a few default values. To * BIBOUMI_PASSWORD: Sets the value of the *password* option. * BIBOUMI_ADMIN: Sets the value of the *admin* option. * BIBOUMI_XMPP_SERVER_IP: Sets the value of the *xmpp_server_ip* option. The default value is **xmpp**. +* BIBOUMI_DB_NAME: Sets the database name to be used by biboumi: a filesystem path pointing at a Sqlite3 file, or a postgresql URI (starting with “postgresql://”). See below to learn how to mount a host directory (to save your Sqlite3 database) or how to link with a postgresql docker container. You can also directly provide your own configuration file by mounting it inside the container using the -v option: @@ -59,7 +60,7 @@ If you want to connect to the XMPP server running on the host machine, use the * Volumes ------- -The database is stored in the /var/lib/biboumi/ directory. If you don’t bind a local directory to it, the database will be lost when the container is stopped. If you want to keep your database between each run, bind it with the -v option, like this: **-v /srv/biboumi/:/var/lib/biboumi**. +By default, a sqlite3 database is stored in the /var/lib/biboumi/ directory. If you don’t bind a local directory to it, the database will be lost when the container is stopped. If you want to keep your database between each run, bind it with the -v option, like this: **-v /srv/biboumi/:/var/lib/biboumi**. Note: Due to a limitation in Docker, to be able to read and write into this database, make sure this mounted directory has the proper read and write permissions on the host: it can be owned by UID and GID 1000:1000, or use chmod to give permissions to everyone, for example. @@ -67,3 +68,25 @@ Note: Due to a limitation in Docker, to be able to read and write into this data chown -R 1000:1000 database/ chmod 777 database/ ``` + +Linking with a PostgreSQL container +----------------------------------- + +If you want to use a PostgreSQL database, you need to either access the host database (run the biboumi container with --network=host), or link with a [postgresql docker image](https://hub.docker.com/_/postgres/). + +To do that, start the PostgreSQL container like this: + +``` +docker run --name postgres postgres:latest +``` + +This will run a postgresql instance with a configured superuser named “postgres”, with no password and a database named “postgres” as well. If you want different values, please refer to the PostgreSQL’s image documentation. + +Then start your biboumi container, by linking with this PostgreSQL container, and by specifying the correct db_name value (of course, also specify all the other options, like the XMPP hostname and password): + +``` +docker run --name biboumi \ + --link=postgres \ + -e BIBOUMI_DB_NAME=postgres://postgres@postgres/postgres \ + biboumi +``` -- cgit v1.2.3 From 8713f015b19a1b86a9fddb6f954ea24cd31169f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 16 Dec 2017 20:35:39 +0100 Subject: Remove the ci deploy stop --- .gitlab-ci.yml | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5a3c7fd..4e1545a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,6 @@ stages: - test # Use the build artifacts to run the tests - packaging # Publish some packages (rpm, deb…) - external # Interact with some external service (codecov, coverity…) - - deploy before_script: - uname -a @@ -328,34 +327,3 @@ packaging:archlinux: - makepkg -si --noconfirm - test -e /usr/bin/biboumi dependencies: [] - -# -## Deploy jobs -# - -deploy:docker: - stage: deploy - tags: - - docker-in-docker - only: - - master@louiz/biboumi - variables: - DOCKER_HOST: tcp://docker.louiz.org:2376 - DOCKER_TLS_VERIFY: 1 - SERVICE_NAME: biboumi - script: - - mkdir -p ~/.docker - - echo "$TLSCACERT" > ~/.docker/ca.pem - - echo "$TLSCERT" > ~/.docker/cert.pem - - echo "$TLSKEY" > ~/.docker/key.pem - - docker version - - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN docker.louiz.org - - docker build --pull --no-cache -t docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME docker/biboumi/alpine - - docker push docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME - - docker service create --with-registry-auth --detach=false --name $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-1 -e BIBOUMI_PASSWORD=password -e BIBOUMI_XMPP_SERVER_IP=prosody -e BIBOUMI_HOSTNAME=test.biboumi.louiz.org --network xmpp docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME || docker service update $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-1 - - docker service create --with-registry-auth --detach=false --name $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-2 -e BIBOUMI_PASSWORD=password -e BIBOUMI_XMPP_SERVER_IP=prosody -e BIBOUMI_HOSTNAME=test2.biboumi.louiz.org --network xmpp docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME || docker service update $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-2 - - docker service create --with-registry-auth --detach=false --name $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-3 -e BIBOUMI_PASSWORD=password -e BIBOUMI_XMPP_SERVER_IP=prosody -e BIBOUMI_HOSTNAME=test3.biboumi.louiz.org --network xmpp docker.louiz.org/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_COMMIT_REF_NAME || docker service update $CI_PROJECT_NAMESPACE-$CI_PROJECT_NAME-3 - environment: - name: master - url: https://biboumi.louiz.org - dependencies: [] -- cgit v1.2.3 From ac0f96a5f4efcefb6fe7f44df5dbea8bbfe61b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 16 Dec 2017 20:35:53 +0100 Subject: Little update on the INSTALL.rst database doc --- INSTALL.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index c98eb52..b030697 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -35,10 +35,10 @@ libuuid_ sqlite3_ or libpq_ - Provides a way to store various options in a database. Each user of the - gateway can store their own values (for example their prefered port, or - their IRC password). Without this dependency, many interesting features - are missing. + Provides a way to store various options and messages archives in a + database. Each user of the gateway can store their own values (for + example their prefered port, or their IRC password). Without this + dependency, many interesting features are missing. libidn_ (optional, but recommended) Provides the stringprep functionality. Without it, JIDs for IRC users are -- cgit v1.2.3 From 044a1a0c8c34fc463066a52528cd59db6d206c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 16 Dec 2017 20:36:15 +0100 Subject: Use a specific (0.4.2) pyasn1 version in the debian Dockerfile --- 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 65c964e..557face 100644 --- a/docker/biboumi-test/debian/Dockerfile +++ b/docker/biboumi-test/debian/Dockerfile @@ -46,7 +46,7 @@ RUN apt install -y g++\ RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan # Install slixmpp, for e2e tests -RUN git clone https://github.com/saghul/aiodns.git && cd aiodns && git checkout 7ee13f9bea25784322~ && python3 setup.py build && python3 setup.py install && git clone git://git.louiz.org/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install +RUN git clone https://github.com/saghul/aiodns.git && cd aiodns && git checkout 7ee13f9bea25784322~ && python3 setup.py build && python3 setup.py install && git clone git://git.louiz.org/slixmpp && pip3 install pyasn1==0.4.2 && cd slixmpp && python3 setup.py build && python3 setup.py install RUN useradd tester -m -- cgit v1.2.3 From 286aa58c7b42f78eb72d4f05170a8c25edeba504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 17 Dec 2017 15:21:40 +0100 Subject: Remove a warning (unused argument) --- src/database/sqlite3_engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/sqlite3_engine.cpp b/src/database/sqlite3_engine.cpp index a94a892..d159540 100644 --- a/src/database/sqlite3_engine.cpp +++ b/src/database/sqlite3_engine.cpp @@ -85,7 +85,7 @@ std::unique_ptr Sqlite3Engine::prepare(const std::string& query) return std::make_unique(stmt); } -void Sqlite3Engine::extract_last_insert_rowid(Statement& statement) +void Sqlite3Engine::extract_last_insert_rowid(Statement&) { this->last_inserted_rowid = sqlite3_last_insert_rowid(this->db); log_debug("extracted inserted ID: ", this->last_inserted_rowid); -- cgit v1.2.3 From 37340e593ffb61eaccc444a1efdb3aa6f784a14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 26 Dec 2017 19:52:41 +0100 Subject: Add a node on outgoing private MUC messages See https://xmpp.org/extensions/xep-0045.html#privatemessage fix #3321 --- src/bridge/bridge.cpp | 3 ++- src/xmpp/xmpp_component.cpp | 8 +++++++- src/xmpp/xmpp_component.hpp | 3 ++- tests/end_to_end/__main__.py | 6 ++++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 57f0628..54bee84 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -862,7 +862,8 @@ 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, true); + this->user_jid + "/" + + resource, "chat", true, true, true); } else { diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 24a85d7..c44b990 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -269,7 +269,8 @@ void* XmppComponent::get_receive_buffer(const size_t size) const } 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) + const std::string& type, const bool fulljid, const bool nocopy, + const bool muc_private) { Stanza message("message"); { @@ -301,6 +302,11 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con XmlSubNode nocopy(message, "no-copy"); nocopy["xmlns"] = "urn:xmpp:hints"; } + if (muc_private) + { + XmlSubNode x(message, "x"); + x["xmlns"] = MUC_USER_NS; + } } this->send_stanza(message); } diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 22d5c48..2bbbe3b 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -112,7 +112,8 @@ public: * 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, const bool nocopy=false); + const std::string& type, const bool fulljid, const bool nocopy=false, + const bool muc_private=false); /** * Send a join from a new participant */ diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index e024fc5..2becdef 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1088,7 +1088,8 @@ if __name__ == '__main__': # 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']", "/message/hints:no-copy", - "/message/carbon:private")), + "/message/carbon:private", + "!/message/muc_user:x")), partial(expect_stanza, "/message[@from='{lower_nick_two}%{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']"), @@ -1280,7 +1281,8 @@ if __name__ == '__main__': # Respond to the message, to the server-wide JID 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']"), + partial(expect_stanza, ("/message[@from='#foo%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='yes']", + "/message/muc_user:x")), ## Do the exact same thing, from a different chan, # to check if the response comes from the right JID -- cgit v1.2.3 From 131ef9946fff0f5cfd794203e819df931b72600f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 26 Dec 2017 20:21:18 +0100 Subject: Include the nodes in the MAM iq result fix #3322 --- src/xmpp/biboumi_component.cpp | 19 ++++++++++++++++++- src/xmpp/xmpp_component.cpp | 4 +++- src/xmpp/xmpp_component.hpp | 2 +- tests/end_to_end/__main__.py | 5 ++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 51ca78d..a998fbe 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -725,7 +725,24 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) if (!line.col().empty()) this->send_archived_message(line, to.full(), from.full(), query_id); } - this->send_iq_result_full_jid(id, from.full(), to.full()); + { + auto fin_ptr = std::make_unique("fin"); + { + XmlNode& fin = *(fin_ptr.get()); + fin["xmlns"] = MAM_NS; + XmlSubNode set(fin, "set"); + set["xmlns"] = RSM_NS; + if (!lines.empty()) + { + XmlSubNode first(set, "first"); + first["index"] = "0"; + first.set_inner(lines[0].col()); + XmlSubNode last(set, "last"); + last.set_inner(lines[lines.size() - 1].col()); + } + } + this->send_iq_result_full_jid(id, from.full(), to.full(), std::move(fin_ptr)); + } return true; } return false; diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index c44b990..8f6826e 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -640,13 +640,15 @@ void XmppComponent::send_iq_version_request(const std::string& from, this->send_stanza(iq); } -void XmppComponent::send_iq_result_full_jid(const std::string& id, const std::string& to_jid, const std::string& from_full_jid) +void XmppComponent::send_iq_result_full_jid(const std::string& id, const std::string& to_jid, const std::string& from_full_jid, std::unique_ptr inner) { Stanza iq("iq"); iq["from"] = from_full_jid; iq["to"] = to_jid; iq["id"] = id; iq["type"] = "result"; + if (inner) + iq.add_child(std::move(inner)); this->send_stanza(iq); } diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 2bbbe3b..3950863 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -203,7 +203,7 @@ public: */ void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from); void send_iq_result_full_jid(const std::string& id, const std::string& to_jid, - const std::string& from_full_jid); + const std::string& from_full_jid, std::unique_ptr inner=nullptr); void handle_handshake(const Stanza& stanza); void handle_error(const Stanza& stanza); diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 2becdef..271f87b 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -130,6 +130,7 @@ def match(stanza, xpath): 'dataform': 'jabber:x:data', 'version': 'jabber:iq:version', 'mam': 'urn:xmpp:mam:2', + 'rms': 'http://jabber.org/protocol/rsm', 'delay': 'urn:xmpp:delay', 'forward': 'urn:xmpp:forward:0', 'client': 'jabber:client', @@ -1852,7 +1853,9 @@ 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}']", + "/iq/mam:fin/rms:set/rsm:last", + "/iq/mam:fin/rsm:set/rsm:first")), # Retrieve an empty archive by specifying an early “end” date partial(send_stanza, """ -- cgit v1.2.3 From bb2dd2b69306517c85bfe59dee323cf94ef34292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 27 Dec 2017 18:04:12 +0100 Subject: Update the changelogs for 7.0 --- CHANGELOG.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4c3f5ad..8744858 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,16 @@ +Version 7.0 +=========== + + - Support PostgreSQL as a database backend. See below for migration tips. + - Add a workaround for a bug in botan < 2.4 where session resumption + would sometime result in a TLS decode error + - Add a node in our private MUC messages, to help + clients distinguish between MUC and non-MUC messages. + - Fix the identd outgoing responses: \r\n was missing, and some clients + would ignore our messages entirely. + - Fix the iq result sent at the end of a MAM response. Some clients (e.g. + gajim) would throw an error as a result. + Version 6.1 - 2017-10-04 ======================== -- cgit v1.2.3 From e4122880b5a9208aa97807ea517f70375a824a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 27 Dec 2017 18:53:08 +0100 Subject: Add a documentation to explain how to migrate from sqlite3 to postgresl fix #3319 --- CHANGELOG.rst | 29 +++++++++++++++++++++++++++++ scripts/dump_sqlite3.sh | 21 +++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100755 scripts/dump_sqlite3.sh diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8744858..c619c00 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,35 @@ Version 7.0 - Fix the iq result sent at the end of a MAM response. Some clients (e.g. gajim) would throw an error as a result. +Sqlite3 to PostgreSQL migration +------------------------------- + +If you used biboumi with the sqlite3 database backend and you want to +start using postgresql instead, follow these simple steps: + + - Make sure your Sqlite3 database has the correct format by running at + least biboumi version 6.0 against this database. Indeed: biboumi can + upgrade your database scheme by itself automatically when it starts, but + the migration process can only migrate from the latest known schema, + which is the one in version 6.x and 7.x. If you are migrating from + version 6.x or 7.x, you have nothing to do. + - Start biboumi (at least version 7.0) with db_name configured to use + your postgresql database: this will create an empty database, create all + the tables with all the rights columns, ready to be filled. + - Backup your database if you value it. The migration process will not + write anything into it, so it your data should theorically be kept + intact, but we never know. + - Run the dump script found in biboumi’s sources: + ``_. Its first and only argument must be the path + to your sqlite3 database. For example run `./scripts/dump_sqlite3.sh + /var/lib/biboumi/biboumi.sqlite`. This will create, in your current + directory, some sqlite files that contain instructions to be fed into + postgresql. + - Import all the ouput files thusly created into your PostgreSQL, with + something like this: `psql postgresql://user@password/biboumi < *.sql`. + This takes a few minutes if your database is huge (if it contains many + archived messages). + Version 6.1 - 2017-10-04 ======================== diff --git a/scripts/dump_sqlite3.sh b/scripts/dump_sqlite3.sh new file mode 100755 index 0000000..88f3097 --- /dev/null +++ b/scripts/dump_sqlite3.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +sqlite3_args=$@ + +function dump_table { + table=$1 + columns=$2 + echo ".mode insert $table +.output $table.sql +select $columns from $table;" | sqlite3 $sqlite3_args +} + +dump_table "roster" "local, remote" + +dump_table "ircserveroptions_" "id_, owner_, server_, pass_, afterconnectioncommand_, tlsports_, ports_, username_, realname_, verifycert_, trustedfingerprint_, encodingout_, encodingin_, maxhistorylength_" + +dump_table "ircchanneloptions_" "id_, owner_, server_, channel_, encodingout_, encodingin_, maxhistorylength_, persistent_, recordhistory_" + +dump_table "globaloptions_" "id_, owner_, maxhistorylength_, recordhistory_, persistent_" + +dump_table "muclogline_" "id_, uuid_, owner_, ircchanname_, ircservername_, date_, body_, nick_" -- cgit v1.2.3 From 2d9f516d1d36bbdd4b114dd3652bbeaebd2fa379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 27 Dec 2017 19:23:28 +0100 Subject: =?UTF-8?q?Don=E2=80=99t=20answer=20to=20some=20requests=20towards?= =?UTF-8?q?=20MUC=20participants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These requests are only meant to be received by the room itself. The participant must answer with not-implemented instead. fix #3323 --- src/xmpp/biboumi_component.cpp | 17 ++++++++++++----- tests/end_to_end/__main__.py | 8 +++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index a998fbe..e6eca3b 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -444,8 +444,13 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) { if (iid.type == Iid::Type::Server) adhoc_handler = &this->irc_server_adhoc_commands_handler; - else + else if (iid.type == Iid::Type::Channel && to.resource.empty()) adhoc_handler = &this->irc_channel_adhoc_commands_handler; + else + { + error_name = "feature-not-implemented"; + return; + } } // Execute the command, if any, and get a result XmlNode that we // insert in our response @@ -495,7 +500,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) stanza_error.disable(); } } - else if (iid.type == Iid::Type::Channel) + else if (iid.type == Iid::Type::Channel && to.resource.empty()) { if (node.empty()) { @@ -554,7 +559,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) this->irc_server_adhoc_commands_handler); stanza_error.disable(); } - else if (iid.type == Iid::Type::Channel) + else if (iid.type == Iid::Type::Channel && to.resource.empty()) { // Get the channel's adhoc commands this->send_adhoc_commands_list(id, from, to_str, (Config::get("admin", "") == @@ -562,6 +567,8 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) this->irc_channel_adhoc_commands_handler); stanza_error.disable(); } + else // “to” is a MUC user, not the room itself + error_name = "feature-not-implemented"; } else if (node.empty() && iid.type == Iid::Type::Server) { // Disco on an IRC server: get the list of channels @@ -784,7 +791,7 @@ bool BiboumiComponent::handle_room_configuration_form_request(const std::string& { Iid iid(to.local, {'#', '&'}); - if (iid.type != Iid::Type::Channel) + if (iid.type != Iid::Type::Channel || !to.resource.empty()) return false; Stanza iq("iq"); @@ -806,7 +813,7 @@ bool BiboumiComponent::handle_room_configuration_form(const XmlNode& query, cons { Iid iid(to.local, {'#', '&'}); - if (iid.type != Iid::Type::Channel) + if (iid.type != Iid::Type::Channel || !to.resource.empty()) return false; Jid requester(from); diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 271f87b..649e073 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -909,7 +909,13 @@ if __name__ == '__main__': partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", "/iq/disco_items:query/disco_items:item[6]")), ], conf='fixed_server'), - + Scenario("list_muc_user_adhoc", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, "/iq[@type='error']/error[@type='cancel']/stanza:feature-not-implemented"), + ] + ), Scenario("execute_hello_adhoc_command", [ handshake_sequence(), -- cgit v1.2.3 From 8409e50c1a28cc87799a8d9c67a90abf250ff6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 27 Dec 2017 19:36:36 +0100 Subject: Fix a subtle iid parsing error in the adhoc code --- 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 e6eca3b..8775869 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -436,7 +436,7 @@ 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()) adhoc_handler = &this->adhoc_commands_handler; -- cgit v1.2.3 From 3230866683b6cde34ed1f1c8bf768d74238ad056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 2 Jan 2018 03:13:16 +0100 Subject: Little fix to the rst formatting in INSTALL and CHANGELOG files --- CHANGELOG.rst | 306 +++++++++++++++++++++++++++++----------------------------- INSTALL.rst | 4 +- 2 files changed, 154 insertions(+), 156 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c619c00..34966c2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,15 +1,15 @@ Version 7.0 =========== - - Support PostgreSQL as a database backend. See below for migration tips. - - Add a workaround for a bug in botan < 2.4 where session resumption - would sometime result in a TLS decode error - - Add a node in our private MUC messages, to help - clients distinguish between MUC and non-MUC messages. - - Fix the identd outgoing responses: \r\n was missing, and some clients - would ignore our messages entirely. - - Fix the iq result sent at the end of a MAM response. Some clients (e.g. - gajim) would throw an error as a result. +- Support PostgreSQL as a database backend. See below for migration tips. +- Add a workaround for a bug in botan < 2.4 where session resumption + would sometime result in a TLS decode error +- Add a node in our private MUC messages, to help + clients distinguish between MUC and non-MUC messages. +- Fix the identd outgoing responses: `\\r\\n` was missing, and some clients + would ignore our messages entirely. +- Fix the iq result sent at the end of a MAM response. Some clients (e.g. + gajim) would throw an error as a result. Sqlite3 to PostgreSQL migration ------------------------------- @@ -17,192 +17,192 @@ Sqlite3 to PostgreSQL migration If you used biboumi with the sqlite3 database backend and you want to start using postgresql instead, follow these simple steps: - - Make sure your Sqlite3 database has the correct format by running at - least biboumi version 6.0 against this database. Indeed: biboumi can - upgrade your database scheme by itself automatically when it starts, but - the migration process can only migrate from the latest known schema, - which is the one in version 6.x and 7.x. If you are migrating from - version 6.x or 7.x, you have nothing to do. - - Start biboumi (at least version 7.0) with db_name configured to use - your postgresql database: this will create an empty database, create all - the tables with all the rights columns, ready to be filled. - - Backup your database if you value it. The migration process will not - write anything into it, so it your data should theorically be kept - intact, but we never know. - - Run the dump script found in biboumi’s sources: - ``_. Its first and only argument must be the path - to your sqlite3 database. For example run `./scripts/dump_sqlite3.sh - /var/lib/biboumi/biboumi.sqlite`. This will create, in your current - directory, some sqlite files that contain instructions to be fed into - postgresql. - - Import all the ouput files thusly created into your PostgreSQL, with - something like this: `psql postgresql://user@password/biboumi < *.sql`. - This takes a few minutes if your database is huge (if it contains many - archived messages). +- Make sure your Sqlite3 database has the correct format by running at + least biboumi version 6.0 against this database. Indeed: biboumi can + upgrade your database scheme by itself automatically when it starts, but + the migration process can only migrate from the latest known schema, + which is the one in version 6.x and 7.x. If you are migrating from + version 6.x or 7.x, you have nothing to do. +- Start biboumi (at least version 7.0) with db_name configured to use + your postgresql database: this will create an empty database, create all + the tables with all the rights columns, ready to be filled. +- Backup your database if you value it. The migration process will not + write anything into it, so it your data should theorically be kept + intact, but we never know. +- Run the dump script found in biboumi’s sources: + ``_. Its first and only argument must be the path + to your sqlite3 database. For example run `./scripts/dump_sqlite3.sh + /var/lib/biboumi/biboumi.sqlite`. This will create, in your current + directory, some sqlite files that contain instructions to be fed into + postgresql. +- Import all the ouput files thusly created into your PostgreSQL, with + something like this: `psql postgresql://user@password/biboumi < *.sql`. + This takes a few minutes if your database is huge (if it contains many + archived messages). Version 6.1 - 2017-10-04 ======================== - - Fix compilation with botan 2.3 - - Fix compilation with very old distributions (such as debian wheezy or - centos 6) that ship antique softwares (sqlite3 < 3.7.14) +- Fix compilation with botan 2.3 +- Fix compilation with very old distributions (such as debian wheezy or + centos 6) that ship antique softwares (sqlite3 < 3.7.14) Version 6.0 - 2017-09-17 ======================== - - The LiteSQL dependency was removed. Only libsqlite3 is now necessary - to work with the database. - - Some JIDs can be added into users’ rosters. The component JID tells if - biboumi is started or not, and the IRC-server JIDs tell if the user is - currently connected to that server. - - The RecordHistory option can now also be configured for each IRC channel, - individually. - - Add a global option to make all channels persistent. - - The persistent_by_default configuration option has been added, this - lets the administrator decide whether or not the rooms should be - persistent or not by default, for all users. - - Status code='332' is sent with the unavailable presences when biboumi is - being shutdown or the connection to the IRC server is cut unexpectedly. - - Support for botan version 1.11.x has been dropped, only version 2.x is - supported. - - Invitations can now be sent to any JID, not only JIDs served by the biboumi - instance itself. - - The history limits sent by the client when they request to join a - channel is now supported. +- The LiteSQL dependency was removed. Only libsqlite3 is now necessary + to work with the database. +- Some JIDs can be added into users’ rosters. The component JID tells if + biboumi is started or not, and the IRC-server JIDs tell if the user is + currently connected to that server. +- The RecordHistory option can now also be configured for each IRC channel, + individually. +- Add a global option to make all channels persistent. +- The persistent_by_default configuration option has been added, this + lets the administrator decide whether or not the rooms should be + persistent or not by default, for all users. +- Status code='332' is sent with the unavailable presences when biboumi is + being shutdown or the connection to the IRC server is cut unexpectedly. +- Support for botan version 1.11.x has been dropped, only version 2.x is + supported. +- Invitations can now be sent to any JID, not only JIDs served by the biboumi + instance itself. +- The history limits sent by the client when they request to join a + channel is now supported. Version 5.0 - 2017-05-24 ======================== - - An identd server has been added. - - Add a **persistent** option for channels. When a channel is configured - as persistent, when the user leaves the room, biboumi stays idle and keeps - saving the received messages in the archive, instead of leaving the channel - entirely. When the user re-joins the room later, biboumi sends the message - history to her/him. This feature can be used to make biboumi behave like - an IRC bouncer. - - Use the udns library instead of c-ares, for asynchronous DNS resolution. - It’s still fully optional. - - Update MAM implementation to version 6.0 (namespace mam:2) - - If the client doesn’t specify any limit in its MAM and channel list request, - the results returned by biboumi contain at most 100 messages, instead of - the potentially huge complete result. - - Multiline topics are now properly handled - - Configuration options can be overridden by values found in the process env. - - Botan’s TLS policies can be customized by the administrator, for each - IRC server, with simple text files. - - The IRC channel configuration form is now also available using the MUC - configuration, in addition to the ad-hoc command. - - Notices starting with [#channel] are considered as welcome messages coming - from that channel, instead of private messages. +- An identd server has been added. +- Add a **persistent** option for channels. When a channel is configured + as persistent, when the user leaves the room, biboumi stays idle and keeps + saving the received messages in the archive, instead of leaving the channel + entirely. When the user re-joins the room later, biboumi sends the message + history to her/him. This feature can be used to make biboumi behave like + an IRC bouncer. +- Use the udns library instead of c-ares, for asynchronous DNS resolution. + It’s still fully optional. +- Update MAM implementation to version 6.0 (namespace mam:2) +- If the client doesn’t specify any limit in its MAM and channel list request, + the results returned by biboumi contain at most 100 messages, instead of + the potentially huge complete result. +- Multiline topics are now properly handled +- Configuration options can be overridden by values found in the process env. +- Botan’s TLS policies can be customized by the administrator, for each + IRC server, with simple text files. +- The IRC channel configuration form is now also available using the MUC + configuration, in addition to the ad-hoc command. +- Notices starting with [#channel] are considered as welcome messages coming + from that channel, instead of private messages. Version 4.3 - 2017-05-02 ======================== - - Fix a segmentation fault that occured when trying to connect to an IRC - server without any port configured. + - Fix a segmentation fault that occured when trying to connect to an IRC + server without any port configured. Version 4.2 - 2017-04-26 ======================== - - Fix a build issue when LiteSQL is absent from the system +- Fix a build issue when LiteSQL is absent from the system Version 4.1 - 2017-03-21 ======================== - - Works with botan 2.x, as well as botan 1.11.x +- Works with botan 2.x, as well as botan 1.11.x 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 - 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 - 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. +- 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 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 + 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 ======================== - - Support multiple-nick sessions: a user can join an IRC channel behind - one single nick, using multiple different clients, at the same time (as - long as each client is using the same bare JID). - - Database support for persistant per-user per-server configuration. Add - `LiteSQL ` as an optional - dependency. - - Add ad-hoc commands that lets each user configure various things - - Support an after-connect command that will be sent to the server - just after the user gets connected to it. - - Support the sending of a PASS command. - - Lets the users configure their username and realname, if the - realname_customization is set to true. - - The remote TLS certificates are checked against the system’s trusted - CAs, unless the user used the configuration option that ignores these - checks. - - Lets the user set a sha-1 hash to identify a server certificate that - should always be trusted. - - Add an outgoing_bind option. - - Add an ad-hoc command to forcefully disconnect a user from one or - more servers. - - Let the user configure the incoming encoding of an IRC server (the - default behaviour remains unchanged: check if it’s valid utf-8 and if - not, decode as latin-1). - - Support `multi-prefix `. - - And of course, many bufixes. - - Run unit tests and a test suite, build the RPM and check many things - automatically using gitlab-ci. +- Support multiple-nick sessions: a user can join an IRC channel behind + one single nick, using multiple different clients, at the same time (as + long as each client is using the same bare JID). +- Database support for persistant per-user per-server configuration. Add + `LiteSQL ` as an optional + dependency. +- Add ad-hoc commands that lets each user configure various things +- Support an after-connect command that will be sent to the server + just after the user gets connected to it. +- Support the sending of a PASS command. +- Lets the users configure their username and realname, if the + realname_customization is set to true. +- The remote TLS certificates are checked against the system’s trusted + CAs, unless the user used the configuration option that ignores these + checks. +- Lets the user set a sha-1 hash to identify a server certificate that + should always be trusted. +- Add an outgoing_bind option. +- Add an ad-hoc command to forcefully disconnect a user from one or + more servers. +- Let the user configure the incoming encoding of an IRC server (the + default behaviour remains unchanged: check if it’s valid utf-8 and if + not, decode as latin-1). +- Support `multi-prefix `. +- And of course, many bufixes. +- Run unit tests and a test suite, build the RPM and check many things + automatically using gitlab-ci. Version 2.0 - 2015-05-29 ======================== - - List channels on an IRC server through an XMPP disco items request - - Let the user send any arbitrary raw IRC command by sending a - message to the IRC server’s JID. - - By default, look for the configuration file as per the XDG - basedir spec. - - Support PING requests in all directions. - - Improve the way we forward received NOTICEs by remembering to - which users we previously sent a private message. This improves the - user experience when talking to NickServ. - - Support joining key-protected channels - - Setting a participant's role/affiliation now results in a change of IRC - mode, instead of being ignored. Setting Toto's affiliation to admin is - now equivalent to “/mode +o Toto” - - Fix the reconnection to the XMPP server to try every 2 seconds - instead of immediately. This avoid hogging resources for nothing - - Asynchronously resolve domain names by optionally using the DNS - library c-ares. - - Add a reload add-hoc command, to reload biboumi's configuration - - Add a fixed_irc_server option. With this option enabled, - biboumi can only connect to the one single IRC server configured +- List channels on an IRC server through an XMPP disco items request +- Let the user send any arbitrary raw IRC command by sending a + message to the IRC server’s JID. +- By default, look for the configuration file as per the XDG + basedir spec. +- Support PING requests in all directions. +- Improve the way we forward received NOTICEs by remembering to + which users we previously sent a private message. This improves the + user experience when talking to NickServ. +- Support joining key-protected channels +- Setting a participant's role/affiliation now results in a change of IRC + mode, instead of being ignored. Setting Toto's affiliation to admin is + now equivalent to “/mode +o Toto” +- Fix the reconnection to the XMPP server to try every 2 seconds + instead of immediately. This avoid hogging resources for nothing +- Asynchronously resolve domain names by optionally using the DNS + library c-ares. +- Add a reload add-hoc command, to reload biboumi's configuration +- Add a fixed_irc_server option. With this option enabled, + biboumi can only connect to the one single IRC server configured Version 1.1 - 2014-07-16 ======================== - - Fix a segmentation fault when connecting to an IRC server using IPv6 +- Fix a segmentation fault when connecting to an IRC server using IPv6 Version 1.0 - 2014-07-12 ======================== - - First stable release. - - Mostly complete MUC to IRC, and IRC to MUC support - - Complete handling of private messages - - Full IRC modes support: setting any IRC mode, and receiving notifications - for every mode change - - Verbose connection status notifications - - Conversion from IRC formatting to XHTML-im - - Ad-hoc commands support - - Basic TLS support: auto-accepts all certificates, no cipher - configuration, no way to force usage of TLS (it is used only if - available, clear connection is automatically used as a fallback) - - IPv6 support +- First stable release. +- Mostly complete MUC to IRC, and IRC to MUC support +- Complete handling of private messages +- Full IRC modes support: setting any IRC mode, and receiving notifications + for every mode change +- Verbose connection status notifications +- Conversion from IRC formatting to XHTML-im +- Ad-hoc commands support +- Basic TLS support: auto-accepts all certificates, no cipher + configuration, no way to force usage of TLS (it is used only if + available, clear connection is automatically used as a fallback) +- IPv6 support diff --git a/INSTALL.rst b/INSTALL.rst index b030697..0d99d4c 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -32,9 +32,7 @@ libiconv_ libuuid_ Generate unique IDs -sqlite3_ -or -libpq_ +sqlite3_ or libpq_ Provides a way to store various options and messages archives in a database. Each user of the gateway can store their own values (for example their prefered port, or their IRC password). Without this -- cgit v1.2.3 From 70ecb0f0d43f75da89ecfa2857aac67aafda136f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 2 Jan 2018 03:17:05 +0100 Subject: Also tabs --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 34966c2..76296b6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -98,8 +98,8 @@ Version 5.0 - 2017-05-24 Version 4.3 - 2017-05-02 ======================== - - Fix a segmentation fault that occured when trying to connect to an IRC - server without any port configured. +- Fix a segmentation fault that occured when trying to connect to an IRC + server without any port configured. Version 4.2 - 2017-04-26 ======================== -- cgit v1.2.3 From 435a63a070e62a11b0a6f5a3c06c445e1a79b9f5 Mon Sep 17 00:00:00 2001 From: Ailin Nemui Date: Wed, 10 Jan 2018 13:36:53 +0100 Subject: Change max line length to more conservative constants --- src/irc/irc_client.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 5f26bf0..40078d9 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -483,12 +483,16 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st } // The max size is 512, taking into account the whole message, not just // the text we send. - // This includes our own nick, username and host (because this will be - // added by the server into our message), in addition to the basic - // components of the message we send (command name, chan name, \r\n et) + // This includes our own nick, constants for username and host (because these + // are notoriously hard to know what the server will use), in addition to the basic + // components of the message we send (command name, chan name, \r\n etc.) // : + NICK + ! + USER + @ + HOST + + PRIVMSG + + CHAN + + : + \r\n + // 63 is the maximum hostname length defined by the protocol. 10 seems to be + // the username limit. + constexpr auto max_username_size = 10; + constexpr auto max_hostname_size = 63; const auto line_size = 512 - - this->current_nick.size() - this->username.size() - this->own_host.size() - + this->current_nick.size() - max_username_size - max_hostname_size - ::strlen(":!@ PRIVMSG ") - chan_name.length() - ::strlen(" :\r\n"); const auto lines = cut(body, line_size); for (const auto& line: lines) -- cgit v1.2.3 From 7c8a7176d196d4bb3724cfafd41980c16be5f404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 12 Jan 2018 03:59:46 +0100 Subject: Only use sd_journal_* if we really are outputing to journald We check that the device and inode numbers are actually the same as the JOURNAL_STREAM value, instead of just checking that the value exists. This fixes the logger unit tests --- src/logger/logger.cpp | 21 ++++++++++++++++++++- src/logger/logger.hpp | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp index 4287794..482cb18 100644 --- a/src/logger/logger.cpp +++ b/src/logger/logger.cpp @@ -1,6 +1,10 @@ #include #include +#include +#include +#include + Logger::Logger(const int log_level): log_level(log_level), stream(std::cout.rdbuf()), @@ -8,7 +12,22 @@ Logger::Logger(const int log_level): null_stream{&null_buffer} { #ifdef SYSTEMD_FOUND - if (::getenv("JOURNAL_STREAM") != nullptr && this->use_stdout()) + if (!this->use_stdout()) + return; + + // See https://www.freedesktop.org/software/systemd/man/systemd.exec.html#%24JOURNAL_STREAM + const char* journal_stream = ::getenv("JOURNAL_STREAM"); + if (journal_stream == nullptr) + return; + + struct stat s{}; + const int res = ::fstat(STDOUT_FILENO, &s); + if (res == -1) + return; + + const auto stdout_stream = std::to_string(s.st_dev) + ":" + std::to_string(s.st_ino); + + if (stdout_stream == journal_stream) this->use_systemd = true; #endif } diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp index a99648c..1689866 100644 --- a/src/logger/logger.hpp +++ b/src/logger/logger.hpp @@ -9,6 +9,7 @@ */ #include +#include #include #include #include -- cgit v1.2.3 From 34d6c2bce1f9f3236992660e0c0458e5cc8eb74d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 12 Jan 2018 05:41:54 +0100 Subject: =?UTF-8?q?Don=E2=80=99t=20use=20codecov=20anymore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 72 +--------------------------------------------------------- README.rst | 3 --- 2 files changed, 1 insertion(+), 74 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e1545a..31fa61e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ stages: - build # Build in various conf, keeps the artifacts - test # Use the build artifacts to run the tests - packaging # Publish some packages (rpm, deb…) - - external # Interact with some external service (codecov, coverity…) + - external # Interact with some external service (coverity…) before_script: - uname -a @@ -172,76 +172,6 @@ test:freebsd: - make check - make e2e -# -## External jobs -# - -.template:codecov: &codecov - stage: external - tags: - - docker - image: docker.louiz.org/louiz/biboumi/test-fedora:latest - -.template:codecov_unittests: &codecov_unittests - <<: *codecov - script: - - bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -f ./coverage_test_suite.info -F $(echo $CI_JOB_NAME | sed s/:/_/g | sed s/codecov_//) - -.template:codecov_e2e: &codecov_e2e - <<: *codecov - script: - - bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -f ./coverage_e2e.info -F $(echo $CI_JOB_NAME | sed s/:/_/g | sed s/codecov_//) - -codecov:fedora: - <<: *codecov_e2e - dependencies: - - test:fedora - -codecov:without_udns: - <<: *codecov_e2e - dependencies: - - test:without_udns - -codecov:debian: - <<: *codecov_e2e - dependencies: - - test:debian - -codecov:build:1: - <<: *codecov_unittests - dependencies: - - build:1 - -codecov:build:2: - <<: *codecov_unittests - dependencies: - - build:2 - -codecov:build:3: - <<: *codecov_unittests - dependencies: - - build:3 - -codecov:build:4: - <<: *codecov_unittests - dependencies: - - build:4 - -codecov:build:5: - <<: *codecov_unittests - dependencies: - - build:5 - -codecov:build:6: - <<: *codecov_unittests - dependencies: - - build:6 - -codecov:build:7: - <<: *codecov_unittests - dependencies: - - build:7 - coverity: stage: external only: diff --git a/README.rst b/README.rst index 5ee9846..8a03701 100644 --- a/README.rst +++ b/README.rst @@ -4,9 +4,6 @@ Biboumi .. image:: https://lab.louiz.org/louiz/biboumi/badges/master/build.svg :target: https://lab.louiz.org/louiz/biboumi/pipelines -.. image:: https://codecov.proxy.louiz.org/gh/louiz/biboumi/branch/master/graph/badge.svg - :target: https://codecov.io/gh/louiz/biboumi - .. image:: https://coverity.proxy.louiz.org/projects/3726/badge.svg :target: https://scan.coverity.com/projects/louiz-biboumi -- cgit v1.2.3 From b4b9828de50c09fea5503b3b3ead2ae9d5144404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 12 Jan 2018 19:54:14 +0100 Subject: Follow log_level even when using journald output fix #3328 --- CHANGELOG.rst | 2 ++ src/logger/logger.hpp | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 76296b6..76be951 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,8 @@ Version 7.0 would ignore our messages entirely. - Fix the iq result sent at the end of a MAM response. Some clients (e.g. gajim) would throw an error as a result. +- log_level configuration option is no longer ignored if the logs are written + into journald Sqlite3 to PostgreSQL migration ------------------------------- diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp index 1689866..315fc11 100644 --- a/src/logger/logger.hpp +++ b/src/logger/logger.hpp @@ -74,8 +74,8 @@ public: bool use_systemd{false}; #endif -private: const int log_level; +private: std::ofstream ofstream{}; std::ostream stream; @@ -105,13 +105,16 @@ namespace logging_details if (Logger::instance()->use_systemd) { (void)level; - std::ostringstream os; - log(os, std::forward(args)...); - sd_journal_send("MESSAGE=%s", os.str().data(), - "PRIORITY=%i", syslog_level, - "CODE_FILE=%s", src_file, - "CODE_LINE=%i", line, - nullptr); + if (level >= Logger::instance()->log_level) + { + std::ostringstream os; + log(os, std::forward(args)...); + sd_journal_send("MESSAGE=%s", os.str().data(), + "PRIORITY=%i", syslog_level, + "CODE_FILE=%s", src_file, + "CODE_LINE=%i", line, + nullptr); + } } else { -- cgit v1.2.3 From 3cd649023680bd49f9865fd62474d4db6a9d7c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 14 Jan 2018 21:44:22 +0100 Subject: Remove all the ugly database debug --- src/database/insert_query.hpp | 5 +---- src/database/postgresql_engine.cpp | 3 --- src/database/postgresql_statement.hpp | 13 ++----------- src/database/query.cpp | 3 --- src/database/select_query.hpp | 1 - src/database/sqlite3_engine.cpp | 5 ----- src/database/sqlite3_statement.hpp | 1 - src/database/table.hpp | 15 ++------------- 8 files changed, 5 insertions(+), 41 deletions(-) diff --git a/src/database/insert_query.hpp b/src/database/insert_query.hpp index 853b7f1..17cc245 100644 --- a/src/database/insert_query.hpp +++ b/src/database/insert_query.hpp @@ -16,10 +16,7 @@ update_autoincrement_id(std::tuple& columns, Statement& statement) { using ColumnType = typename std::decay(columns))>::type; if (std::is_same::value) - { - log_debug("EXTRACTING LAST ID"); - auto&& column = std::get(columns); - } + auto&& column = std::get(columns); update_autoincrement_id(columns, statement); } diff --git a/src/database/postgresql_engine.cpp b/src/database/postgresql_engine.cpp index 4ee4223..150ca42 100644 --- a/src/database/postgresql_engine.cpp +++ b/src/database/postgresql_engine.cpp @@ -20,7 +20,6 @@ PostgresqlEngine::~PostgresqlEngine() std::unique_ptr PostgresqlEngine::open(const std::string& conninfo) { - log_debug("trying to open: ", conninfo); PGconn* con = PQconnectdb(conninfo.data()); if (!con) @@ -47,13 +46,11 @@ std::set PostgresqlEngine::get_all_columns_from_table(const std::st while (statement->step() == StepResult::Row) columns.insert(statement->get_column_text(0)); - log_debug("found ", columns.size(), " columns."); return columns; } std::tuple PostgresqlEngine::raw_exec(const std::string& query) { - log_debug("raw_exec:", query); PGresult* res = PQexec(this->conn, query.data()); auto sg = utils::make_scope_guard([res](){ PQclear(res); diff --git a/src/database/postgresql_statement.hpp b/src/database/postgresql_statement.hpp index 30c3ec2..571c8f1 100644 --- a/src/database/postgresql_statement.hpp +++ b/src/database/postgresql_statement.hpp @@ -96,12 +96,7 @@ private: params.reserve(this->params.size()); for (const auto& param: this->params) - { - log_debug("param:", param); - params.push_back(param.data()); - } - - log_debug("body: ", body); + params.push_back(param.data()); const int param_size = static_cast(this->params.size()); this->result = PQexecParams(this->conn, this->body.data(), param_size, @@ -111,11 +106,7 @@ private: nullptr, 0); const auto status = PQresultStatus(this->result); - if (status == PGRES_TUPLES_OK) - { - log_debug("PGRES_TUPLES_OK"); - } - else if (status != PGRES_COMMAND_OK) + if (status != PGRES_TUPLES_OK && status != PGRES_COMMAND_OK) { log_error("Failed to execute command: ", PQresultErrorMessage(this->result)); return false; diff --git a/src/database/query.cpp b/src/database/query.cpp index 6f305b2..4054007 100644 --- a/src/database/query.cpp +++ b/src/database/query.cpp @@ -3,19 +3,16 @@ void actual_bind(Statement& statement, const std::string& value, int index) { - log_debug("binding string:", value, " to col ", index); statement.bind_text(index, value); } void actual_bind(Statement& statement, const std::size_t value, int index) { - log_debug("binding size_t:", value); statement.bind_int64(index, value); } void actual_bind(Statement& statement, const OptionalBool& value, int index) { - log_debug("binding optional_t:", value.to_string()); if (!value.is_set) statement.bind_int64(index, 0); else if (value.value) diff --git a/src/database/select_query.hpp b/src/database/select_query.hpp index 837a866..af2ac1c 100644 --- a/src/database/select_query.hpp +++ b/src/database/select_query.hpp @@ -115,7 +115,6 @@ struct SelectQuery: public Query while (statement->step() == StepResult::Row) { - log_debug("one result."); Row row(this->table_name); extract_row_values(row, *statement); rows.push_back(row); diff --git a/src/database/sqlite3_engine.cpp b/src/database/sqlite3_engine.cpp index d159540..92d514b 100644 --- a/src/database/sqlite3_engine.cpp +++ b/src/database/sqlite3_engine.cpp @@ -39,9 +39,6 @@ std::set Sqlite3Engine::get_all_columns_from_table(const std::strin sqlite3_free(errmsg); } - log_debug("List of columns in table ", table_name, ":"); - for (const auto& c: result) - log_debug(c); return result; } @@ -74,7 +71,6 @@ std::tuple Sqlite3Engine::raw_exec(const std::string& query) std::unique_ptr Sqlite3Engine::prepare(const std::string& query) { sqlite3_stmt* stmt; - log_debug("SQLITE3: ", query); auto res = sqlite3_prepare(db, query.data(), static_cast(query.size()) + 1, &stmt, nullptr); if (res != SQLITE_OK) @@ -88,7 +84,6 @@ std::unique_ptr Sqlite3Engine::prepare(const std::string& query) void Sqlite3Engine::extract_last_insert_rowid(Statement&) { this->last_inserted_rowid = sqlite3_last_insert_rowid(this->db); - log_debug("extracted inserted ID: ", this->last_inserted_rowid); } std::string Sqlite3Engine::id_column_type() diff --git a/src/database/sqlite3_statement.hpp b/src/database/sqlite3_statement.hpp index 42a5220..7738fa6 100644 --- a/src/database/sqlite3_statement.hpp +++ b/src/database/sqlite3_statement.hpp @@ -19,7 +19,6 @@ class Sqlite3Statement: public Statement StepResult step() override final { auto res = sqlite3_step(this->get()); - log_debug("step: ", res); if (res == SQLITE_ROW) return StepResult::Row; else if (res == SQLITE_DONE) diff --git a/src/database/table.hpp b/src/database/table.hpp index 5fbc301..680e7cc 100644 --- a/src/database/table.hpp +++ b/src/database/table.hpp @@ -65,11 +65,10 @@ class Table { std::string query{"CREATE TABLE IF NOT EXISTS "}; query += this->name; - query += " (\n"; + query += " ("; this->add_column_create(db, query); query += ")"; - log_debug("create:" , query); auto result = db.raw_exec(query); if (std::get<0>(result) == false) log_error("Error executing query: ", std::get<1>(result)); @@ -112,21 +111,11 @@ class Table add_column_create(DatabaseEngine& db, std::string& str) { using ColumnType = typename std::remove_reference(std::declval()))>::type; -// using RealType = typename ColumnType::real_type; str += ColumnType::name; str += " "; -// if (std::is_same::value) -// { -// str += "INTEGER PRIMARY KEY AUTOINCREMENT"; -// } -// else -// { - str += ToSQLType(db); -// append_option(str); -// } + str += ToSQLType(db); if (N != sizeof...(T) - 1) str += ","; - str += "\n"; add_column_create(db, str); } -- cgit v1.2.3 From 7e64a2e361adcdbd2fce5ad76051a150b4de062d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 14 Jan 2018 21:46:49 +0100 Subject: Add a DEBUG_SQL_QUERIES to log info about the executed SQL queries fix #3324 --- CMakeLists.txt | 3 +++ INSTALL.rst | 3 +++ src/biboumi.h.cmake | 2 ++ src/database/count_query.hpp | 3 +++ src/database/insert_query.hpp | 4 ++++ src/database/postgresql_engine.cpp | 6 ++++++ src/database/query.hpp | 29 +++++++++++++++++++++++++++++ src/database/select_query.hpp | 4 ++++ src/database/sqlite3_engine.cpp | 7 +++++++ src/database/update_query.hpp | 4 ++++ src/utils/scopetimer.hpp | 17 +++++++++++++++++ 11 files changed, 82 insertions(+) create mode 100644 src/utils/scopetimer.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 155b73a..d19a84e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -193,6 +193,9 @@ file(GLOB source_network src/network/*.[hc]pp) add_library(network OBJECT ${source_network}) +option(DEBUG_SQL_QUERIES + "If set to true, every SQL statement executed will be logged and timed" + OFF) if(SQLITE3_FOUND OR PQ_FOUND) file(GLOB source_database src/database/*.[hc]pp) diff --git a/INSTALL.rst b/INSTALL.rst index 0d99d4c..b0bf4be 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -96,6 +96,9 @@ The list of available options: - POLL: use the standard poll(2). This is the default value on all non-Linux platforms. +- DEBUG_SQL_QUERIES: If set to ON, additional debug logging and timing will be + done for every SQL query that is executed. The default is OFF. + - WITH_BOTAN and WITHOUT_BOTAN: The first force the usage of the Botan library, if it is not found, the configuration process will fail. The second will make the build process ignore the Botan library, it will not be used even diff --git a/src/biboumi.h.cmake b/src/biboumi.h.cmake index 5bc1004..fa99cd4 100644 --- a/src/biboumi.h.cmake +++ b/src/biboumi.h.cmake @@ -12,3 +12,5 @@ #cmakedefine PROJECT_NAME "${PROJECT_NAME}" #cmakedefine HAS_GET_TIME #cmakedefine HAS_PUT_TIME +#cmakedefine DEBUG_SQL_QUERIES + diff --git a/src/database/count_query.hpp b/src/database/count_query.hpp index e8d24ef..118ce44 100644 --- a/src/database/count_query.hpp +++ b/src/database/count_query.hpp @@ -16,6 +16,9 @@ struct CountQuery: public Query int64_t execute(DatabaseEngine& db) { +#ifdef DEBUG_SQL_QUERIES + const auto timer = this->log_and_time(); +#endif auto statement = db.prepare(this->body); int64_t res = 0; if (statement->step() != StepResult::Error) diff --git a/src/database/insert_query.hpp b/src/database/insert_query.hpp index 17cc245..9726424 100644 --- a/src/database/insert_query.hpp +++ b/src/database/insert_query.hpp @@ -39,6 +39,10 @@ struct InsertQuery: public Query template void execute(DatabaseEngine& db, std::tuple& columns) { +#ifdef DEBUG_SQL_QUERIES + const auto timer = this->log_and_time(); +#endif + auto statement = db.prepare(this->body); this->bind_param(columns, *statement); diff --git a/src/database/postgresql_engine.cpp b/src/database/postgresql_engine.cpp index 150ca42..984a959 100644 --- a/src/database/postgresql_engine.cpp +++ b/src/database/postgresql_engine.cpp @@ -3,6 +3,8 @@ #include +#include + #include #include @@ -51,6 +53,10 @@ std::set PostgresqlEngine::get_all_columns_from_table(const std::st std::tuple PostgresqlEngine::raw_exec(const std::string& query) { +#ifdef DEBUG_SQL_QUERIES + log_debug("SQL QUERY: ", query); + const auto timer = make_sql_timer(); +#endif PGresult* res = PQexec(this->conn, query.data()); auto sg = utils::make_scope_guard([res](){ PQclear(res); diff --git a/src/database/query.hpp b/src/database/query.hpp index 7f8aecb..547138f 100644 --- a/src/database/query.hpp +++ b/src/database/query.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -13,6 +15,20 @@ void actual_bind(Statement& statement, const std::string& value, int index); void actual_bind(Statement& statement, const std::size_t value, int index); void actual_bind(Statement& statement, const OptionalBool& value, int index); +#ifdef DEBUG_SQL_QUERIES +#include + +inline auto make_sql_timer() +{ + return make_scope_timer([](const std::chrono::steady_clock::duration& elapsed) + { + const auto seconds = std::chrono::duration_cast(elapsed); + const auto rest = elapsed - seconds; + log_debug("Query executed in ", seconds.count(), ".", rest.count(), "s."); + }); +} +#endif + struct Query { std::string body; @@ -22,6 +38,18 @@ struct Query Query(std::string str): body(std::move(str)) {} + +#ifdef DEBUG_SQL_QUERIES + auto log_and_time() + { + std::ostringstream os; + os << this->body << "; "; + for (const auto& param: this->params) + os << "'" << param << "' "; + log_debug("SQL QUERY: ", os.str()); + return make_sql_timer(); + } +#endif }; template @@ -58,3 +86,4 @@ operator<<(Query& query, const Integer& i) actual_add_param(query, i); return query; } + diff --git a/src/database/select_query.hpp b/src/database/select_query.hpp index af2ac1c..5a17f38 100644 --- a/src/database/select_query.hpp +++ b/src/database/select_query.hpp @@ -110,6 +110,10 @@ struct SelectQuery: public Query { std::vector> rows; +#ifdef DEBUG_SQL_QUERIES + const auto timer = this->log_and_time(); +#endif + auto statement = db.prepare(this->body); statement->bind(std::move(this->params)); diff --git a/src/database/sqlite3_engine.cpp b/src/database/sqlite3_engine.cpp index 92d514b..ae4a146 100644 --- a/src/database/sqlite3_engine.cpp +++ b/src/database/sqlite3_engine.cpp @@ -6,6 +6,8 @@ #include +#include + #include #include #include @@ -57,6 +59,11 @@ std::unique_ptr Sqlite3Engine::open(const std::string& filename) std::tuple Sqlite3Engine::raw_exec(const std::string& query) { +#ifdef DEBUG_SQL_QUERIES + log_debug("SQL QUERY: ", query); + const auto timer = make_sql_timer(); +#endif + char* error; const auto result = sqlite3_exec(db, query.data(), nullptr, nullptr, &error); if (result != SQLITE_OK) diff --git a/src/database/update_query.hpp b/src/database/update_query.hpp index 32befc0..a29ac3f 100644 --- a/src/database/update_query.hpp +++ b/src/database/update_query.hpp @@ -64,6 +64,10 @@ struct UpdateQuery: public Query template void execute(DatabaseEngine& db, const std::tuple& columns) { +#ifdef DEBUG_SQL_QUERIES + const auto timer = this->log_and_time(); +#endif + auto statement = db.prepare(this->body); this->bind_param(columns, *statement); this->bind_id(columns, *statement); diff --git a/src/utils/scopetimer.hpp b/src/utils/scopetimer.hpp new file mode 100644 index 0000000..7d3db9b --- /dev/null +++ b/src/utils/scopetimer.hpp @@ -0,0 +1,17 @@ +#include + +#include + +#include + +template +auto make_scope_timer(Callback cb) +{ + const auto start_time = std::chrono::steady_clock::now(); + return utils::make_scope_guard([start_time, cb = std::move(cb)]() + { + const auto now = std::chrono::steady_clock::now(); + const auto elapsed = now - start_time; + cb(elapsed); + }); +} -- cgit v1.2.3 From 1bb3e2e7da6778814f4a89787d8ab54c6e2d1760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 17 Jan 2018 20:41:17 +0100 Subject: Clarify a few things about the dependencies --- INSTALL.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/INSTALL.rst b/INSTALL.rst index b0bf4be..4c02c7b 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -32,7 +32,7 @@ libiconv_ libuuid_ Generate unique IDs -sqlite3_ or libpq_ +sqlite3_ or libpq_ (optional, but recommented) Provides a way to store various options and messages archives in a database. Each user of the gateway can store their own values (for example their prefered port, or their IRC password). Without this @@ -47,12 +47,13 @@ udns_ (optional, but recommended) performances when connecting to a big number of IRC servers at the same time. -libbotan_ 2.x (optional) +libbotan_ 2.x (optional, but recommended) Provides TLS support. Without it, IRC connections are all made in plain-text mode. gcrypt_ (mandatory only if botan is absent) - Provides the SHA-1 hash function, for the case where Botan is absent. + Provides the SHA-1 hash function, for the case where Botan is absent. It + does NOT provide any TLS or encryption feature. systemd_ (optional) Provides the support for a systemd service of Type=notify. This is useful only -- cgit v1.2.3 From 520711023f82bc0828a7af9459eabb155bf42566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 17 Jan 2018 20:59:38 +0100 Subject: Use our botan gh#1276 workaround only for botan < 2.4 fix #3320 --- src/network/tcp_socket_handler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 602cf94..642cf03 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -12,6 +12,7 @@ #include #ifdef BOTAN_FOUND +# include # include # include # include @@ -28,8 +29,10 @@ namespace Botan::TLS::Session_Manager_In_Memory& get_session_manager() { static Botan::TLS::Session_Manager_In_Memory session_manager{get_rng()}; +#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(2,4,0) // workaround for https://github.com/randombit/botan/issues/1276 session_manager.remove_all(); +#endif return session_manager; } } -- cgit v1.2.3 From 5c75cd104f1fe69ac7a287044d5fc1a6cf8cdffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 17 Jan 2018 21:09:25 +0100 Subject: By the way, we were on version 7.0~dev --- CMakeLists.txt | 6 +++--- packaging/biboumi.spec.cmake | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d19a84e..5239b52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.0) project(biboumi) -set(${PROJECT_NAME}_VERSION_MAJOR 6) -set(${PROJECT_NAME}_VERSION_MINOR 1) -set(${PROJECT_NAME}_VERSION_SUFFIX "") +set(${PROJECT_NAME}_VERSION_MAJOR 7) +set(${PROJECT_NAME}_VERSION_MINOR 0) +set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index a3b94ce..4cd3aa5 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -12,6 +12,7 @@ BuildRequires: expat-devel BuildRequires: libuuid-devel BuildRequires: systemd-devel BuildRequires: sqlite-devel +BuildRequires: postgresql-devel BuildRequires: cmake BuildRequires: systemd BuildRequires: pandoc @@ -39,7 +40,8 @@ cmake . -DCMAKE_CXX_FLAGS="%{optflags}" \ -DWITHOUT_BOTAN=1 \ -DWITH_SYSTEMD=1 \ -DWITH_LIBIDN=1 \ - -DWITH_SQLITE3=1 + -DWITH_SQLITE3=1 \ + -DWITH_POSTGRESQL=1 make %{?_smp_mflags} @@ -61,6 +63,9 @@ make check %{?_smp_mflags} %changelog +* ${RPM_DATE} Le Coz Florent - ${RPM_VERSION}-1 +- Build latest git revision + * Wed Oct 4 2017 Le Coz Florent - 6.1-1 Update to version 6.1 -- cgit v1.2.3 From 6e1af8b082379a2cf0aafc8c7929bea465b9d8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 17 Jan 2018 21:14:44 +0100 Subject: Release version 7.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 76be951..b553283 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,5 @@ -Version 7.0 -=========== +Version 7.0 - 2018-01-17 +======================== - Support PostgreSQL as a database backend. See below for migration tips. - Add a workaround for a bug in botan < 2.4 where session resumption diff --git a/CMakeLists.txt b/CMakeLists.txt index 5239b52..4dc9d63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 7) set(${PROJECT_NAME}_VERSION_MINOR 0) -set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") +set(${PROJECT_NAME}_VERSION_SUFFIX "") if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index 4cd3aa5..95f19e1 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -63,8 +63,8 @@ make check %{?_smp_mflags} %changelog -* ${RPM_DATE} Le Coz Florent - ${RPM_VERSION}-1 -- Build latest git revision +* Wed Jan 17 2018 Le Coz Florent - 7.0-1 + Update to version 7.0 * Wed Oct 4 2017 Le Coz Florent - 6.1-1 Update to version 6.1 -- cgit v1.2.3 From f371d9ca46578a722d2ce0d4a88ea35f64dd1d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 18 Jan 2018 19:34:56 +0100 Subject: xep-0106 escape the JIDs listed in a disco#items server query fix #3325 --- src/xmpp/biboumi_component.cpp | 5 ++++- tests/end_to_end/__main__.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 8775869..a0e52e6 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -1010,7 +1011,9 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id, const std for (auto it = begin; it != end; ++it) { XmlSubNode item(query, "item"); - item["jid"] = it->channel + "@" + this->served_hostname; + std::string channel_name = it->channel; + xep0106::encode(channel_name); + item["jid"] = channel_name + "@" + this->served_hostname; } if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty())) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 649e073..e223ead 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -2254,6 +2254,21 @@ if __name__ == '__main__': "/iq/disco_items:query/disco_items:item[@jid='#bar%{irc_server_one}']" )) ]), + Scenario("channel_list_escaping", + [ + handshake_sequence(), + + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #true/false [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#true\\2ffalse%{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='#true\\2ffalse%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + ]), Scenario("channel_list_with_rsm", [ handshake_sequence(), -- cgit v1.2.3 From b6b4cc1b1a7c2ef9a4478cb869b1bacf6ba616ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 18 Jan 2018 19:37:53 +0100 Subject: Remove the now useless .codecov.yml file --- .codecov.yml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index 74ba41b..0000000 --- a/.codecov.yml +++ /dev/null @@ -1,3 +0,0 @@ -codecov: - ignore: - - "tests" -- cgit v1.2.3 From 24b4a09d47829994157305cd5ef68a44ac7af27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Jan 2018 18:05:57 +0100 Subject: By the way, start version 8.0 --- CMakeLists.txt | 4 ++-- packaging/biboumi.spec.cmake | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dc9d63..f30037c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.0) project(biboumi) -set(${PROJECT_NAME}_VERSION_MAJOR 7) +set(${PROJECT_NAME}_VERSION_MAJOR 8) set(${PROJECT_NAME}_VERSION_MINOR 0) -set(${PROJECT_NAME}_VERSION_SUFFIX "") +set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index 95f19e1..a61f4bc 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -63,6 +63,9 @@ make check %{?_smp_mflags} %changelog +* ${RPM_DATE} Le Coz Florent - ${RPM_VERSION}-1 +- Build latest git revision + * Wed Jan 17 2018 Le Coz Florent - 7.0-1 Update to version 7.0 -- cgit v1.2.3 From 06271729e33300cebbd7222f50a2b38905e33cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Jan 2018 20:57:34 +0100 Subject: Fix a crash happening when a user cancels a non-existing ad-hoc session --- src/xmpp/adhoc_commands_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp index e4dcd5c..bb48781 100644 --- a/src/xmpp/adhoc_commands_handler.cpp +++ b/src/xmpp/adhoc_commands_handler.cpp @@ -83,7 +83,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co XmlSubNode next(actions, "next"); } } - else if (action == "cancel") + else if (session_it != this->sessions.end() && action == "cancel") { this->sessions.erase(session_it); command_node["status"] = "canceled"; -- cgit v1.2.3 From 23e51e814f330ee06dc4dac29d1b2f328e8238b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 22 Jan 2018 21:01:50 +0100 Subject: Release version 7.1 --- CHANGELOG.rst | 5 +++++ CMakeLists.txt | 2 +- packaging/biboumi.spec.cmake | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b553283..0e0d207 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +Version 7.1 - 2018-01-22 +======================== + +- Fix a crash happening if a user cancels a non-existing ad-hoc session + Version 7.0 - 2018-01-17 ======================== diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dc9d63..b31d718 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.0) project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 7) -set(${PROJECT_NAME}_VERSION_MINOR 0) +set(${PROJECT_NAME}_VERSION_MINOR 1) set(${PROJECT_NAME}_VERSION_SUFFIX "") if(NOT CMAKE_BUILD_TYPE) diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index 95f19e1..0bb0db8 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -63,6 +63,9 @@ make check %{?_smp_mflags} %changelog +* Wed Jan 22 2018 Le Coz Florent - 7.1-1 + Update to version 7.1 + * Wed Jan 17 2018 Le Coz Florent - 7.0-1 Update to version 7.0 -- cgit v1.2.3 From 33a5f1355d1250bf77184459a8d40a790e42814d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 24 Jan 2018 21:42:26 +0100 Subject: Remove a variable template usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because it’s only supported in gcc>=5.0 --- src/database/row.hpp | 4 ++-- src/utils/is_one_of.hpp | 9 +++------ tests/utils.cpp | 14 +++++++------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/database/row.hpp b/src/database/row.hpp index 1b50ff9..2d55897 100644 --- a/src/database/row.hpp +++ b/src/database/row.hpp @@ -30,13 +30,13 @@ struct Row } template - void save(std::unique_ptr& db, typename std::enable_if && Coucou>::type* = nullptr) + void save(std::unique_ptr& db, typename std::enable_if::value && Coucou>::type* = nullptr) { this->insert(*db); } template - void save(std::unique_ptr& db, typename std::enable_if && Coucou>::type* = nullptr) + void save(std::unique_ptr& db, typename std::enable_if::value && Coucou>::type* = nullptr) { const Id& id = std::get(this->columns); if (id.value == Id::unset_value) diff --git a/src/utils/is_one_of.hpp b/src/utils/is_one_of.hpp index 4d6770e..c706421 100644 --- a/src/utils/is_one_of.hpp +++ b/src/utils/is_one_of.hpp @@ -3,15 +3,12 @@ #include template -struct is_one_of_implem { +struct is_one_of { static constexpr bool value = false; }; template -struct is_one_of_implem { +struct is_one_of { static constexpr bool value = - std::is_same::value || is_one_of_implem::value; + std::is_same::value || is_one_of::value; }; - -template -constexpr bool is_one_of = is_one_of_implem::value; diff --git a/tests/utils.cpp b/tests/utils.cpp index 6de19f0..99c7040 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -175,11 +175,11 @@ TEST_CASE("dirname") TEST_CASE("is_in") { - CHECK((is_one_of) == true); - CHECK((is_one_of) == false); - CHECK((is_one_of) == false); - CHECK((is_one_of) == true); - CHECK((is_one_of) == false); - CHECK((is_one_of) == true); - CHECK((is_one_of) == true); + CHECK((is_one_of::value) == true); + CHECK((is_one_of::value) == false); + CHECK((is_one_of::value) == false); + CHECK((is_one_of::value) == true); + CHECK((is_one_of::value) == false); + CHECK((is_one_of::value) == true); + CHECK((is_one_of::value) == true); } -- cgit v1.2.3 From 97c8e2fed3b510d8048a386230b480c3044153d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 25 Jan 2018 02:05:26 +0100 Subject: Release version 7.2 --- CHANGELOG.rst | 6 ++++++ CMakeLists.txt | 2 +- packaging/biboumi.spec.cmake | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0e0d207..8fdafe9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,9 @@ +Version 7.2 - 2018-01-24 +======================== + +- Fix compilation with gcc 4.9. This is the last version to support this + old version. + Version 7.1 - 2018-01-22 ======================== diff --git a/CMakeLists.txt b/CMakeLists.txt index b31d718..5d31900 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.0) project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 7) -set(${PROJECT_NAME}_VERSION_MINOR 1) +set(${PROJECT_NAME}_VERSION_MINOR 2) set(${PROJECT_NAME}_VERSION_SUFFIX "") if(NOT CMAKE_BUILD_TYPE) diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index 0bb0db8..8b9ad17 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -63,6 +63,9 @@ make check %{?_smp_mflags} %changelog +* Wed Jan 24 2018 Le Coz Florent - 7.2-1 + Update to version 7.2 + * Wed Jan 22 2018 Le Coz Florent - 7.1-1 Update to version 7.1 -- cgit v1.2.3 From e267512ad40c073bd5a5b37a4ee3378c80b9f523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 25 Jan 2018 02:17:20 +0100 Subject: Restore the is_one_of variable template --- src/database/row.hpp | 4 ++-- src/utils/is_one_of.hpp | 9 ++++++--- tests/utils.cpp | 14 +++++++------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/database/row.hpp b/src/database/row.hpp index 2d55897..130863a 100644 --- a/src/database/row.hpp +++ b/src/database/row.hpp @@ -30,13 +30,13 @@ struct Row } template - void save(std::unique_ptr& db, typename std::enable_if::value && Coucou>::type* = nullptr) + void save(std::unique_ptr& db, typename std::enable_if && Coucou>::type* = nullptr) { this->insert(*db); } template - void save(std::unique_ptr& db, typename std::enable_if::value && Coucou>::type* = nullptr) + void save(std::unique_ptr& db, typename std::enable_if && Coucou>::type* = nullptr) { const Id& id = std::get(this->columns); if (id.value == Id::unset_value) diff --git a/src/utils/is_one_of.hpp b/src/utils/is_one_of.hpp index c706421..4d6770e 100644 --- a/src/utils/is_one_of.hpp +++ b/src/utils/is_one_of.hpp @@ -3,12 +3,15 @@ #include template -struct is_one_of { +struct is_one_of_implem { static constexpr bool value = false; }; template -struct is_one_of { +struct is_one_of_implem { static constexpr bool value = - std::is_same::value || is_one_of::value; + std::is_same::value || is_one_of_implem::value; }; + +template +constexpr bool is_one_of = is_one_of_implem::value; diff --git a/tests/utils.cpp b/tests/utils.cpp index 99c7040..6de19f0 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -175,11 +175,11 @@ TEST_CASE("dirname") TEST_CASE("is_in") { - CHECK((is_one_of::value) == true); - CHECK((is_one_of::value) == false); - CHECK((is_one_of::value) == false); - CHECK((is_one_of::value) == true); - CHECK((is_one_of::value) == false); - CHECK((is_one_of::value) == true); - CHECK((is_one_of::value) == true); + CHECK((is_one_of) == true); + CHECK((is_one_of) == false); + CHECK((is_one_of) == false); + CHECK((is_one_of) == true); + CHECK((is_one_of) == false); + CHECK((is_one_of) == true); + CHECK((is_one_of) == true); } -- cgit v1.2.3 From 6dca27f08d426cdf966514fa3bf766be5f6566dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 25 Jan 2018 02:19:05 +0100 Subject: Only support gcc >= 5.0 from now on --- INSTALL.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.rst b/INSTALL.rst index 4c02c7b..45a860d 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -16,7 +16,7 @@ Build and runtime dependencies: Tools: ~~~~~~ -- A C++14 compiler (clang >= 3.4 or gcc >= 4.9 for example) +- A C++14 compiler (clang >= 3.4 or gcc >= 5.0 for example) - CMake - pandoc (optional) to build the man page -- cgit v1.2.3 From a209e77fc2c189c251fa90eebdce0f16ff4f6f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 26 Jan 2018 00:35:08 +0100 Subject: Rename a few CI things to a useful names --- .gitlab-ci.yml | 6 +++--- docker/biboumi-test/archlinux/Dockerfile | 13 ------------- docker/packaging/archlinux/Dockerfile | 13 +++++++++++++ 3 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 docker/biboumi-test/archlinux/Dockerfile create mode 100644 docker/packaging/archlinux/Dockerfile diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 31fa61e..8a0a04e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -101,7 +101,7 @@ build:6: UDNS: "-DWITHOUT_UDNS=1" <<: *fedora_build -build:7: +build:without_udns: variables: UDNS: "-DWITHOUT_UDNS=1" <<: *fedora_build @@ -144,7 +144,7 @@ test:without_udns: image: docker.louiz.org/louiz/biboumi/test-fedora:latest <<: *basic_test dependencies: - - build:7 + - build:without_udns test:alpine: image: docker.louiz.org/louiz/biboumi/test-alpine:latest @@ -248,7 +248,7 @@ packaging:archlinux: tags: - docker allow_failure: true - image: docker.louiz.org/louiz/biboumi/test-archlinux:latest + image: docker.louiz.org/louiz/biboumi/packaging-archlinux:latest before_script: [] script: - sudo pacman -Syuu --noconfirm diff --git a/docker/biboumi-test/archlinux/Dockerfile b/docker/biboumi-test/archlinux/Dockerfile deleted file mode 100644 index 20f0343..0000000 --- a/docker/biboumi-test/archlinux/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM docker.io/base/archlinux:latest - -RUN pacman -Syuuuu --noconfirm - -RUN pacman -Syu --noconfirm cmake base-devel git clang-tools-extra - -RUN useradd -m -G wheel -s /bin/bash builder - -RUN sed -i '/^# %wheel ALL=(ALL) NOPASSWD: ALL/s/^# //' /etc/sudoers - -WORKDIR /home/builder - -USER builder diff --git a/docker/packaging/archlinux/Dockerfile b/docker/packaging/archlinux/Dockerfile new file mode 100644 index 0000000..20f0343 --- /dev/null +++ b/docker/packaging/archlinux/Dockerfile @@ -0,0 +1,13 @@ +FROM docker.io/base/archlinux:latest + +RUN pacman -Syuuuu --noconfirm + +RUN pacman -Syu --noconfirm cmake base-devel git clang-tools-extra + +RUN useradd -m -G wheel -s /bin/bash builder + +RUN sed -i '/^# %wheel ALL=(ALL) NOPASSWD: ALL/s/^# //' /etc/sudoers + +WORKDIR /home/builder + +USER builder -- cgit v1.2.3 From e0265c81be7465b06803f67fd0d26ef4f571ca84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 26 Jan 2018 01:07:03 +0100 Subject: ci: remove some useless template, directly define the jobs --- .gitlab-ci.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a0a04e..0aeec84 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -42,25 +42,19 @@ variables: <<: *basic_build image: docker.louiz.org/louiz/biboumi/test-fedora:latest -.template:debian_build: &debian_build +build:fedora: + <<: *fedora_build + +build:debian: <<: *basic_build image: docker.louiz.org/louiz/biboumi/test-debian:latest -.template:alpine_build: &alpine_build +build:alpine: variables: SYSTEMD: "-DWITHOUT_SYSTEMD=1" <<: *basic_build image: docker.louiz.org/louiz/biboumi/test-alpine:latest -build:fedora: - <<: *fedora_build - -build:debian: - <<: *debian_build - -build:alpine: - <<: *alpine_build - build:1: variables: BOTAN: "-DWITHOUT_BOTAN=1" -- cgit v1.2.3 From ca95f89753858e0947ec89dfacc297cea950c02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 26 Jan 2018 01:14:52 +0100 Subject: Add a archlinux-armv7l build --- .gitlab-ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0aeec84..feabd5d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,6 +55,15 @@ build:alpine: <<: *basic_build image: docker.louiz.org/louiz/biboumi/test-alpine:latest +build:archlinux: + <<: *basic_build + only: + - branches@louiz/biboumi + tags: + - armv7l + artifacts: + paths: [] + build:1: variables: BOTAN: "-DWITHOUT_BOTAN=1" -- cgit v1.2.3 From 6e7ee320bc1256793bc11c4b00a8f4f89f407af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 26 Jan 2018 01:31:42 +0100 Subject: ci: Add Werror and Wno-psabi compiler flags --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index feabd5d..a6f7bd8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,7 +30,7 @@ variables: - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3} ${POSTGRESQL}" - mkdir build/ - cd build/ - - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3} ${POSTGRESQL} + - cmake .. -DCMAKE_CXX_FLAGS="-Werror -Wno-psabi" -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3} ${POSTGRESQL} - make everything -j$(nproc || echo 1) - make coverage_check -j$(nproc || echo 1) artifacts: -- cgit v1.2.3 From 9bf81a2c04eddabd0f09ea9157e6e7c97bea88f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 28 Jan 2018 14:10:14 +0100 Subject: This should fix the int conversion warning on 32bits arch --- src/database/query.cpp | 2 +- src/database/query.hpp | 2 +- src/database/row.hpp | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/database/query.cpp b/src/database/query.cpp index 4054007..d27dc59 100644 --- a/src/database/query.cpp +++ b/src/database/query.cpp @@ -6,7 +6,7 @@ void actual_bind(Statement& statement, const std::string& value, int index) statement.bind_text(index, value); } -void actual_bind(Statement& statement, const std::size_t value, int index) +void actual_bind(Statement& statement, const std::int64_t value, int index) { statement.bind_int64(index, value); } diff --git a/src/database/query.hpp b/src/database/query.hpp index 547138f..8434944 100644 --- a/src/database/query.hpp +++ b/src/database/query.hpp @@ -12,7 +12,7 @@ #include void actual_bind(Statement& statement, const std::string& value, int index); -void actual_bind(Statement& statement, const std::size_t value, int index); +void actual_bind(Statement& statement, const std::int64_t value, int index); void actual_bind(Statement& statement, const OptionalBool& value, int index); #ifdef DEBUG_SQL_QUERIES diff --git a/src/database/row.hpp b/src/database/row.hpp index 130863a..4dc98be 100644 --- a/src/database/row.hpp +++ b/src/database/row.hpp @@ -42,7 +42,8 @@ struct Row if (id.value == Id::unset_value) { this->insert(*db); - std::get(this->columns).value = db->last_inserted_rowid; + if (db->last_inserted_rowid >= 0) + std::get(this->columns).value = static_cast(db->last_inserted_rowid); } else this->update(*db); -- cgit v1.2.3 From 442f46c4acf728e7a189327598a0ea3d33010d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 28 Jan 2018 14:15:34 +0100 Subject: And an other conversion warning --- src/xmpp/xmpp_component.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 8f6826e..053fc3e 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -22,6 +22,7 @@ #include #ifdef SYSTEMD_FOUND # include +#include #endif using namespace std::string_literals; @@ -398,7 +399,7 @@ 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) +void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, Database::time_point::rep timestamp) { Stanza message("message"); message["to"] = jid_to; -- cgit v1.2.3 From 7403c397edb14ba93d014ab10e13a97c817ff0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 28 Jan 2018 14:20:21 +0100 Subject: Correctly include the database header --- src/xmpp/xmpp_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 053fc3e..88baf6e 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -22,7 +23,6 @@ #include #ifdef SYSTEMD_FOUND # include -#include #endif using namespace std::string_literals; -- cgit v1.2.3 From 17f2cb5c93e2c86f68cda97fed43fb617bd75ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 29 Jan 2018 21:11:20 +0100 Subject: =?UTF-8?q?Do=20not=20forget=20the=20complete=3D'true'=20attribute?= =?UTF-8?q?=20in=20MAM=E2=80=99s=20result=20iq?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/xmpp/biboumi_component.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index a0e52e6..ee8a502 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -738,6 +738,7 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) { XmlNode& fin = *(fin_ptr.get()); fin["xmlns"] = MAM_NS; + fin["complete"] = "true"; XmlSubNode set(fin, "set"); set["xmlns"] = RSM_NS; if (!lines.empty()) -- cgit v1.2.3 From cb831788942b49a28bd79fd62dbdc3d00f15b227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 30 Jan 2018 09:07:21 +0100 Subject: Add the complete='true' attribute only when appropriate --- CHANGELOG.rst | 5 +++++ src/xmpp/biboumi_component.cpp | 25 +++++++++++++++++++------ tests/end_to_end/__main__.py | 20 ++++++++++++++------ 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8fdafe9..bcddc11 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +Version 8.0 +=========== + +- Add a complete='true' in MAM’s iq result when appropriate + Version 7.2 - 2018-01-24 ======================== diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index ee8a502..dd66aca 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -721,13 +721,25 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) if (max) limit = std::atoi(max->get_inner().data()); } - // If the archive is really big, and the client didn’t specify any - // limit, we avoid flooding it: we set an arbitrary max limit. - if (limit == -1 && start.empty() && end.empty()) + // Do send more than 100 messages, even if the client asked for more, + // or if it didn’t specify any limit. + // 101 is just a trick to know if there are more available messages. + // If our query returns 101 message, we know it’s incomplete, but we + // still send only 100 + if ((limit == -1 && start.empty() && end.empty()) + || limit > 100) + limit = 101; + log_debug("limit: ", limit); + auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end); + bool complete = true; + if (lines.size() > 100) { - limit = 100; + log_debug("incomplete"); + complete = false; + log_debug("size of lines before erase: ", lines.size()); + lines.erase(lines.begin(), std::prev(lines.end(), 100)); + log_debug("size of lines after erase: ", lines.size()); } - const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end); for (const Database::MucLogLine& line: lines) { if (!line.col().empty()) @@ -738,7 +750,8 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) { XmlNode& fin = *(fin_ptr.get()); fin["xmlns"] = MAM_NS; - fin["complete"] = "true"; + if (complete) + fin["complete"] = "true"; XmlSubNode set(fin, "set"); set["xmlns"] = RSM_NS; if (!lines.empty()) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index e223ead..c4c149a 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1861,7 +1861,8 @@ if __name__ == '__main__': partial(expect_stanza, ("/iq[@type='result'][@id='id1'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", "/iq/mam:fin/rms:set/rsm:last", - "/iq/mam:fin/rsm:set/rsm:first")), + "/iq/mam:fin/rsm:set/rsm:first", + "/iq/mam:fin[@complete='true']")), # Retrieve an empty archive by specifying an early “end” date partial(send_stanza, """ @@ -1873,7 +1874,8 @@ if __name__ == '__main__': """), partial(expect_stanza, - "/iq[@type='result'][@id='id2'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), + ("/iq[@type='result'][@id='id2'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set",)), # Retrieve an empty archive by specifying a late “start” date # (note that this test will break in ~1000 years) @@ -1886,7 +1888,8 @@ if __name__ == '__main__': """), partial(expect_stanza, - "/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), + ("/iq[@type='result'][@id='id3'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set")), # Retrieve a limited archive partial(send_stanza, "1"), @@ -1897,7 +1900,8 @@ if __name__ == '__main__': ), partial(expect_stanza, - "/iq[@type='result'][@id='id4'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), + ("/iq[@type='result'][@id='id4'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set")), ]), Scenario("mam_with_timestamps", @@ -1957,7 +1961,8 @@ if __name__ == '__main__': ), partial(expect_stanza, - "/iq[@type='result'][@id='id8'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']"), + ("/iq[@type='result'][@id='id8'][@from='#foo%{irc_server_one}'][@to='{jid_one}/{resource_one}']", + "/iq/mam:fin[@complete='true']/rsm:set")), ]), @@ -2151,8 +2156,11 @@ if __name__ == '__main__': ("/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()='149']") ), + # And it should not be marked as complete 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}']", + "!/iq//mam:fin[@complete='true']", + "/iq//mam:fin")), ]), Scenario("channel_history_on_fixed_server", -- cgit v1.2.3 From 58fd68916636df2372f8187b375245ef5922833a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 30 Jan 2018 09:14:26 +0100 Subject: Add a ifndef USE_DATABASE guard around send_history_message --- src/xmpp/xmpp_component.cpp | 3 ++- src/xmpp/xmpp_component.hpp | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 88baf6e..9be9e34 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -399,6 +398,7 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str this->send_stanza(message); } +#ifdef USE_DATABASE void XmppComponent::send_history_message(const std::string& muc_name, const std::string& nick, const std::string& body_txt, const std::string& jid_to, Database::time_point::rep timestamp) { Stanza message("message"); @@ -422,6 +422,7 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std: this->send_stanza(message); } +#endif void XmppComponent::send_muc_leave(const std::string& muc_name, const std::string& nick, Xmpp::body&& message, const std::string& jid_to, const bool self, const bool user_requested) diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 3950863..cef26c1 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -1,8 +1,10 @@ #pragma once +#include "biboumi.h" #include #include +#include #include #include @@ -133,11 +135,13 @@ public: */ void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to, std::string uuid); +#ifdef USE_DATABASE /** * 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); +#endif /** * Send an unavailable presence for this nick */ -- cgit v1.2.3 From 369da19d41142a20235372f3bfbe180e41008b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 30 Jan 2018 09:16:12 +0100 Subject: Remove the debug logs from previous commit --- src/xmpp/biboumi_component.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index dd66aca..481ebb9 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -729,16 +729,12 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza) if ((limit == -1 && start.empty() && end.empty()) || limit > 100) limit = 101; - log_debug("limit: ", limit); auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end); bool complete = true; if (lines.size() > 100) { - log_debug("incomplete"); complete = false; - log_debug("size of lines before erase: ", lines.size()); lines.erase(lines.begin(), std::prev(lines.end(), 100)); - log_debug("size of lines after erase: ", lines.size()); } for (const Database::MucLogLine& line: lines) { -- cgit v1.2.3 From d7cf736adb4837c55d8112160cd4718e549ebaaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 4 Feb 2018 11:02:56 +0100 Subject: Fix argument types in declaration of send_history_message --- src/xmpp/xmpp_component.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index cef26c1..1daa6fb 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -140,7 +140,7 @@ public: * 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); + const std::string& jid_to, Database::time_point::rep timestamp); #endif /** * Send an unavailable presence for this nick -- cgit v1.2.3 From 1ebd8a2321c454129d921dc71777f47b97b8db97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 4 Feb 2018 16:58:04 +0100 Subject: Fix conversion warnings on 32 bits --- src/utils/time.cpp | 3 ++- src/utils/time.hpp | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/utils/time.cpp b/src/utils/time.cpp index bc2c18d..71306fd 100644 --- a/src/utils/time.cpp +++ b/src/utils/time.cpp @@ -9,9 +9,10 @@ namespace utils { -std::string to_string(const std::time_t& timestamp) +std::string to_string(const std::chrono::system_clock::time_point::rep& time) { constexpr std::size_t stamp_size = 21; + const std::time_t timestamp = static_cast(time); char date_buf[stamp_size]; if (std::strftime(date_buf, stamp_size, "%FT%TZ", std::gmtime(×tamp)) != stamp_size - 1) return ""; diff --git a/src/utils/time.hpp b/src/utils/time.hpp index c71cd9c..4b19634 100644 --- a/src/utils/time.hpp +++ b/src/utils/time.hpp @@ -2,9 +2,10 @@ #include #include +#include namespace utils { -std::string to_string(const std::time_t& timestamp); +std::string to_string(const std::chrono::system_clock::time_point::rep& timestamp); std::time_t parse_datetime(const std::string& stamp); -} \ No newline at end of file +} -- cgit v1.2.3 From e0d1a0b44add408504e428d6ce4a8a2121ea7a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 5 Feb 2018 12:27:52 +0100 Subject: =?UTF-8?q?Add=20a=20check=20for=20GCC=E2=80=99s=20and=20Clang?= =?UTF-8?q?=E2=80=99s=20minimal=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f30037c..2b3f292 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,16 @@ set(${PROJECT_NAME}_VERSION_MAJOR 8) set(${PROJECT_NAME}_VERSION_MINOR 0) set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + message(FATAL_ERROR "GCC version must be at least 5.0.") + endif() +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.4) + message(FATAL_ERROR "Clang version must be at least 3.4.") + endif() +endif() + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type (Release/Debug/RelWithDebInfo/MinSizeRel)" FORCE) -- cgit v1.2.3