changeset 550:64cb98dd8e9d

Irccd: finish custom IRC library, closes #581 Finally remove the lovely libircclient that has been used since the beginning of irccd back in 2013. Several issues made the choice of developing a custom IRC library: - libircclient requires select(2) system call and works in procedural paradigm which is incompatible with our Boost.Asio main loop, - there was no way in the libircclient to detect custom message like PING to handle dead servers, this required to bundle the library within the application, - the library use static output buffer which requires to keep an internal queue of commands that we flush at each iteration.
author David Demelier <markand@malikania.fr>
date Fri, 24 Nov 2017 20:05:15 +0100
parents 68032209609d
children a5e1c91abb8e
files CREDITS.md libirccd-js/irccd/js/server_jsapi.cpp libirccd-js/irccd/js/util_jsapi.cpp libirccd/irccd/command.cpp libirccd/irccd/irc.cpp libirccd/irccd/irc.hpp libirccd/irccd/irccd.cpp libirccd/irccd/server.cpp libirccd/irccd/server.hpp libirccd/irccd/service.cpp libirccd/irccd/service.hpp tests/irc/main.cpp tests/js-util/main.cpp
diffstat 13 files changed, 116 insertions(+), 113 deletions(-) [+]
line wrap: on
line diff
--- a/CREDITS.md	Thu Nov 23 22:45:12 2017 +0100
+++ b/CREDITS.md	Fri Nov 24 20:05:15 2017 +0100
@@ -5,7 +5,8 @@
 ----------------------
 
 - libircclient, http://www.ulduzsoft.com/linux/libircclient
-  Very powerful and great C IRC library.
+  Very powerful and great C IRC library used until version 3.0.0, has been a
+  source of inspiration.
 
 - OpenSSL, http://openssl.org
   Free, open-source famous crypto library.
--- a/libirccd-js/irccd/js/server_jsapi.cpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/libirccd-js/irccd/js/server_jsapi.cpp	Fri Nov 24 20:05:15 2017 +0100
@@ -392,7 +392,7 @@
 
     try {
         auto json = duk_json_encode(ctx, 0);
-        auto s = server::from_json(nlohmann::json::parse(json));
+        auto s = server::from_json(dukx_get_irccd(ctx).service(), nlohmann::json::parse(json));
 
         duk_push_this(ctx);
         duk_push_pointer(ctx, new std::shared_ptr<server>(std::move(s)));
--- a/libirccd-js/irccd/js/util_jsapi.cpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/libirccd-js/irccd/js/util_jsapi.cpp	Fri Nov 24 20:05:15 2017 +0100
@@ -238,13 +238,7 @@
  */
 duk_ret_t splituser(duk_context* ctx)
 {
-    auto target = duk_require_string(ctx, 0);
-    char nick[32] = {0};
-
-#if 0
-    irc_target_get_nick(target, nick, sizeof (nick) -1);
-#endif
-    duk_push_string(ctx, nick);
+    dukx_push_std_string(ctx, irc::user::parse(duk_require_string(ctx, 0)).nick());
 
     return 1;
 }
@@ -262,13 +256,7 @@
  */
 duk_ret_t splithost(duk_context* ctx)
 {
-    auto target = duk_require_string(ctx, 0);
-    char host[32] = {0};
-
-#if 0
-    irc_target_get_host(target, host, sizeof (host) -1);
-#endif
-    duk_push_string(ctx, host);
+    dukx_push_std_string(ctx, irc::user::parse(duk_require_string(ctx, 0)).host());
 
     return 1;
 }
--- a/libirccd/irccd/command.cpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/libirccd/irccd/command.cpp	Fri Nov 24 20:05:15 2017 +0100
@@ -257,7 +257,7 @@
 
 void server_connect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    auto server = server::from_json(args);
+    auto server = server::from_json(irccd.service(), args);
 
     if (irccd.servers().has(server->name()))
         client.error("server-connect", "server already exists");
--- a/libirccd/irccd/irc.cpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/libirccd/irccd/irc.cpp	Fri Nov 24 20:05:15 2017 +0100
@@ -16,6 +16,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <cassert>
 #include <iterator>
 #include <sstream>
 
@@ -103,12 +104,32 @@
     return {std::move(prefix), std::move(command), std::move(args)};
 }
 
+bool message::is_ctcp(unsigned index) const noexcept
+{
+    auto a = arg(index);
+
+    if (a.empty())
+        return false;
+
+    return a.front() == 0x01 && a.back() == 0x01;
+}
+
+std::string message::ctcp(unsigned index) const
+{
+    assert(is_ctcp(index));
+
+    return args_[index].substr(1, args_[index].size() - 1);
+}
+
 user user::parse(const std::string& line)
 {
+    if (line.empty())
+        return {"", ""};
+
     auto pos = line.find("!");
 
     if (pos == std::string::npos)
-        return {"", ""};
+        return {line, ""};
 
     return {line.substr(0, pos), line.substr(pos + 1)};
 }
--- a/libirccd/irccd/irc.hpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/libirccd/irccd/irc.hpp	Fri Nov 24 20:05:15 2017 +0100
@@ -276,6 +276,23 @@
     }
 
     /**
+     * Tells if the message is a CTCP.
+     *
+     * \param index the param index (maybe out of bounds)
+     * \return true if CTCP
+     */
+    bool is_ctcp(unsigned index) const noexcept;
+
+    /**
+     * Parse a CTCP message.
+     *
+     * \pre is_ctcp(index)
+     * \param index the param index
+     * \return the CTCP command
+     */
+    std::string ctcp(unsigned index) const;
+
+    /**
      * Parse a IRC message.
      *
      * \param line the buffer content (without \r\n)
@@ -489,7 +506,8 @@
 template <typename Socket>
 void basic_connection<Socket>::do_send(const std::string& message, send_t handler)
 {
-    boost::asio::async_write(socket_, boost::asio::buffer(message), [handler, message] (auto code, auto xfer) {
+    boost::asio::async_write(socket_, boost::asio::buffer(message), [handler, message] (auto code, auto) {
+        // TODO: xfer
         handler(code);
     });
 }
--- a/libirccd/irccd/irccd.cpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/libirccd/irccd/irccd.cpp	Fri Nov 24 20:05:15 2017 +0100
@@ -49,19 +49,24 @@
 
 void irccd::run()
 {
+    for (;;)
+        service_.run();
+
+#if 0
     while (running_) {
         net_util::poll(100, *this);
         service_.poll();
     }
+#endif
 }
 
-void irccd::prepare(fd_set& in, fd_set& out, net::Handle& max)
+void irccd::prepare(fd_set&, fd_set&, net::Handle&)
 {
-    net_util::prepare(in, out, max, *itr_service_, *server_service_);
 }
 
-void irccd::sync(fd_set& in, fd_set& out)
+void irccd::sync(fd_set& , fd_set& )
 {
+#if 0
     if (!running_)
         return;
 
@@ -89,6 +94,7 @@
 
     for (auto& ev : copy)
         ev(*this);
+#endif
 }
 
 void irccd::stop()
--- a/libirccd/irccd/server.cpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/libirccd/irccd/server.cpp	Fri Nov 24 20:05:15 2017 +0100
@@ -77,6 +77,7 @@
 
     int j = 0;
     bool read_modes = true;
+
     for (size_t i = 0; i < buf.size(); ++i) {
         if (buf[i] == '(')
             continue;
@@ -110,24 +111,9 @@
     jchannels_.erase(std::remove(jchannels_.begin(), jchannels_.end(), channel), jchannels_.end());
 }
 
-#if 0
-
-void server::handle_channel(const char* orig, const char** params) noexcept
-{
-    on_message({shared_from_this(), strify(orig), strify(params[0]), strify(params[1])});
-}
-
-void server::handle_ctcp_action(const char* orig, const char** params) noexcept
+std::shared_ptr<server> server::from_json(boost::asio::io_service& service, const nlohmann::json& object)
 {
-    on_me({shared_from_this(), strify(orig), strify(params[0]), strify(params[1])});
-}
-
-#endif
-
-std::shared_ptr<server> server::from_json(const nlohmann::json& object)
-{
-#if 0
-    auto sv = std::make_shared<server>(json_util::require_identifier(object, "name"));
+    auto sv = std::make_shared<server>(service, json_util::require_identifier(object, "name"));
 
     sv->set_host(json_util::require_string(object, "host"));
     sv->set_password(json_util::get_string(object, "password"));
@@ -153,8 +139,6 @@
         sv->set_flags(sv->flags() | server::join_invite);
 
     return sv;
-#endif
-    return nullptr;
 }
 
 channel server::split_channel(const std::string& value)
@@ -178,16 +162,6 @@
     username_ = user.empty() ? "irccd" : user;
 }
 
-void server::dispatch_channel_mode(const irc::message& msg)
-{
-    on_channel_mode({ shared_from_this(), msg.arg(0), msg.arg(1), msg.arg(2), msg.arg(3)});
-}
-
-void server::dispatch_channel_notice(const irc::message& msg)
-{
-    on_channel_notice({shared_from_this(), msg.arg(0), msg.arg(0), msg.arg(1)});
-}
-
 void server::dispatch_connect(const irc::message&)
 {
     recocur_ = 1;
@@ -241,6 +215,7 @@
      * params[2] == End of WHOIS list
      */
     auto it = whois_map_.find(msg.arg(1));
+
     if (it != whois_map_.end()) {
         on_whois({shared_from_this(), it->second});
 
@@ -251,11 +226,11 @@
 
 void server::dispatch_invite(const irc::message& msg)
 {
-    // If joininvite is set, join the channel.
-    if ((flags_ & join_invite) && is_self(msg.arg(1)))
-        join(msg.arg(2));
+    // If join-invite is set, join the channel.
+    if ((flags_ & join_invite) && is_self(msg.arg(0)))
+        join(msg.arg(1));
 
-    on_invite({shared_from_this(), msg.arg(0), msg.arg(2), msg.arg(1)});
+    on_invite({shared_from_this(), msg.prefix(), msg.arg(1), msg.arg(0)});
 }
 
 void server::dispatch_isupport(const irc::message& msg)
@@ -283,36 +258,36 @@
             break;
         }
     }
-
 }
 
 void server::dispatch_join(const irc::message& msg)
 {
-    if (is_self(msg.arg(0)))
-        jchannels_.push_back(msg.arg(1));
+    if (is_self(msg.prefix()))
+        jchannels_.push_back(msg.arg(0));
 
-    on_join({shared_from_this(), msg.arg(0), msg.arg(1)});
+    on_join({shared_from_this(), msg.prefix(), msg.arg(0)});
 }
 
 void server::dispatch_kick(const irc::message& msg)
 {
-    if (is_self(msg.arg(2))) {
+    if (is_self(msg.arg(1))) {
         // Remove the channel from the joined list.
-        remove_joined_channel(msg.arg(1));
+        remove_joined_channel(msg.arg(0));
 
         // Rejoin the channel if the option has been set and I was kicked.
         if (flags_ & auto_rejoin)
-            join(msg.arg(1));
+            join(msg.arg(0));
     }
 
-    on_kick({ shared_from_this(), msg.arg(0), msg.arg(1), msg.arg(2), msg.arg(3)});
+    on_kick({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1), msg.arg(2)});
 }
 
 void server::dispatch_mode(const irc::message& msg)
 {
-#if 0
-    on_mode({shared_from_this(), msg.arg(0), msg.arg(1)});
-#endif
+    if (is_self(msg.arg(1)))
+        on_mode({shared_from_this(), msg.prefix(), msg.arg(1)});
+    else
+        on_channel_mode({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1), msg.arg(2)});
 }
 
 void server::dispatch_namreply(const irc::message& msg)
@@ -341,31 +316,49 @@
 void server::dispatch_nick(const irc::message& msg)
 {
     // Update our nickname.
-    if (is_self(msg.arg(0)))
+    if (is_self(msg.prefix()))
         nickname_ = msg.arg(0);
 
-    on_nick({shared_from_this(), msg.arg(0), msg.arg(1)});
+    on_nick({shared_from_this(), msg.prefix(), msg.arg(0)});
 }
 
 void server::dispatch_notice(const irc::message& msg)
 {
-    on_notice({shared_from_this(), msg.arg(0), msg.arg(2)});
+    if (is_self(msg.arg(1)))
+        on_notice({shared_from_this(), msg.prefix(), msg.arg(1)});
+    else
+        on_channel_notice({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1)});
 }
 
 void server::dispatch_part(const irc::message& msg)
 {
     // Remove the channel from the joined list if I left a channel.
-    if (is_self(msg.arg(0)))
+    if (is_self(msg.prefix()))
         remove_joined_channel(msg.arg(1));
 
-    on_part({shared_from_this(), msg.arg(0), msg.arg(1), msg.arg(2)});
+    on_part({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1)});
 }
 
 void server::dispatch_ping(const irc::message& msg)
 {
     assert(msg.command() == "PING");
 
-    conn_->send(string_util::sprintf("PONG %s", msg.arg(1)));
+    conn_->send(string_util::sprintf("PONG %s", msg.arg(0)));
+}
+
+void server::dispatch_privmsg(const irc::message& msg)
+{
+    assert(msg.command() == "PRIVMSG");
+
+    if (msg.is_ctcp(1)) {
+        auto cmd = msg.ctcp(1);
+
+        if (cmd.compare(0, 6, "ACTION") == 0)
+            on_me({shared_from_this(), msg.prefix(), msg.arg(0), cmd.substr(7)});
+    } else if (is_self(msg.arg(0)))
+        on_query({shared_from_this(), msg.prefix(), msg.arg(1)});
+    else
+        on_message({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1)});
 }
 
 void server::dispatch_topic(const irc::message& msg)
@@ -430,13 +423,15 @@
     if (message.is(5))
         dispatch_isupport(message);
     else if (message.is(irc::err::nomotd) || message.is(irc::rpl::endofmotd))
-            dispatch_connect(message);
+        dispatch_connect(message);
     else if (message.command() == "INVITE")
         dispatch_invite(message);
     else if (message.command() == "JOIN")
         dispatch_join(message);
     else if (message.command() == "KICK")
         dispatch_kick(message);
+    else if (message.command() == "MODE")
+        dispatch_mode(message);
     else if (message.command() == "NICK")
         dispatch_nick(message);
     else if (message.command() == "NOTICE")
@@ -447,6 +442,8 @@
         dispatch_part(message);
     else if (message.command() == "PING")
         dispatch_ping(message);
+    else if (message.command() == "PRIVMSG")
+        dispatch_privmsg(message);
     else if (message.is(irc::rpl::namreply))
         dispatch_namreply(message);
     else if (message.is(irc::rpl::endofnames))
--- a/libirccd/irccd/server.hpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/libirccd/irccd/server.hpp	Fri Nov 24 20:05:15 2017 +0100
@@ -476,6 +476,7 @@
     void dispatch_notice(const irc::message&);
     void dispatch_part(const irc::message&);
     void dispatch_ping(const irc::message&);
+    void dispatch_privmsg(const irc::message&);
     void dispatch_topic(const irc::message&);
     void dispatch_whoischannels(const irc::message&);
     void dispatch_whoisuser(const irc::message&);
@@ -492,11 +493,12 @@
      *
      * Used in JavaScript API and transport commands.
      *
+     * \param service the io service
      * \param object the object
      * \return the server
      * \throw std::exception on failures
      */
-    static std::shared_ptr<server> from_json(const nlohmann::json& object);
+    static std::shared_ptr<server> from_json(boost::asio::io_service& service, const nlohmann::json& object);
 
     /**
      * Split a channel from the form channel:password into a server_channel
--- a/libirccd/irccd/service.cpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/libirccd/irccd/service.cpp	Fri Nov 24 20:05:15 2017 +0100
@@ -793,7 +793,7 @@
     log::debug() << "  username: " << ev.whois.user << "\n";
     log::debug() << "  host: " << ev.whois.host << "\n";
     log::debug() << "  realname: " << ev.whois.realname << "\n";
-    log::debug() << "  channels: " << string_util::join(ev.whois.channels.begin(), ev.whois.channels.end()) << std::endl;
+    log::debug() << "  channels: " << string_util::join(ev.whois.channels.begin(), ev.whois.channels.end(), ", ") << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onWhois"           },
@@ -819,24 +819,6 @@
 {
 }
 
-void server_service::prepare(fd_set& in, fd_set& out, net::Handle& max)
-{
-#if 0
-    for (auto& server : servers_) {
-        server->update();
-        server->prepare(in, out, max);
-    }
-#endif
-}
-
-void server_service::sync(fd_set& in, fd_set& out)
-{
-#if 0
-    for (auto& server : servers_)
-        server->sync(in, out);
-#endif
-}
-
 bool server_service::has(const std::string& name) const noexcept
 {
     return std::count_if(servers_.cbegin(), servers_.end(), [&] (const auto& server) {
--- a/libirccd/irccd/service.hpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/libirccd/irccd/service.hpp	Fri Nov 24 20:05:15 2017 +0100
@@ -404,16 +404,6 @@
     server_service(irccd& instance);
 
     /**
-     * \copydoc Service::prepare
-     */
-    void prepare(fd_set& in, fd_set& out, net::Handle& max);
-
-    /**
-     * \copydoc Service::sync
-     */
-    void sync(fd_set& in, fd_set& out);
-
-    /**
      * Get the list of servers
      *
      * \return the servers
--- a/tests/irc/main.cpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/tests/irc/main.cpp	Fri Nov 24 20:05:15 2017 +0100
@@ -65,18 +65,16 @@
 
     BOOST_TEST(user.nick() == "jean");
     BOOST_TEST(user.host() == "~jean@127.0.0.1");
+
+    auto usersimple = irc::user::parse("jean");
+
+    BOOST_TEST(usersimple.nick() == "jean");
+    BOOST_TEST(usersimple.host().empty());
 }
 
-BOOST_AUTO_TEST_CASE(invalid)
+BOOST_AUTO_TEST_CASE(empty)
 {
-    irc::user user("", "");
-
-    user = irc::user::parse("notavalidcombination");
-
-    BOOST_TEST(user.nick().empty());
-    BOOST_TEST(user.host().empty());
-
-    user = irc::user::parse("");
+    auto user = irc::user::parse("");
 
     BOOST_TEST(user.nick().empty());
     BOOST_TEST(user.host().empty());
--- a/tests/js-util/main.cpp	Thu Nov 23 22:45:12 2017 +0100
+++ b/tests/js-util/main.cpp	Fri Nov 24 20:05:15 2017 +0100
@@ -60,7 +60,7 @@
         throw dukx_exception(plugin_->context(), -1);
 
     BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "!~user@hyper/super/host");
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "~user@hyper/super/host");
 }
 
 /*