Mercurial > irccd
changeset 711:fc66cc9706a7
CMake: export pkg-config files, closes #874 @1h
While here rename libcommon to libirccd-core to avoid collision when installed.
For consistency, rename libirccdctl to libirccd-ctl.
line wrap: on
line diff
--- a/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -28,8 +28,8 @@ # cmake/IrccdSystem.cmake - Contains some platforms checks and compile flags. # cmake/IrccdVersion.cmake - Defines the Irccd version and its plugins. # cmake/check - Platform checks in separate files. +# cmake/export - CMake and pkg-config exports. # cmake/function - Custom CMake functions. -# cmake/installer - Some files for the QtIFW installer. # cmake/internal - Some internal files (e.g. the sysconfig.h) # cmake/packages - Additional find_package modules. # @@ -37,7 +37,8 @@ # # doc - The documentation process. # extern - External libraries. -# libirccdctl - The irccdctl library. +# libirccd-core - Common code. +# libirccd-ctl - The irccdctl library. # libirccd-js - Javascript bindings library. # libirccd-test - Helpers for unit tests. # libirccd - The irccd library. @@ -86,9 +87,9 @@ add_subdirectory(extern/json) add_subdirectory(doc) -add_subdirectory(libcommon) +add_subdirectory(libirccd-core) add_subdirectory(libirccd) -add_subdirectory(libirccdctl) +add_subdirectory(libirccd-ctl) add_subdirectory(libirccd-test) if (IRCCD_HAVE_JS)
--- a/cmake/export/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/cmake/export/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -18,6 +18,7 @@ include(CMakePackageConfigHelpers) +# CMake export files. write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/irccd-config-version.cmake VERSION ${IRCCD_VERSION} @@ -38,3 +39,26 @@ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/irccd COMPONENT Devel ) + +# pkg-config files. +if (IRCCD_WITH_PKGCONFIG) + # Since javascript is optional, add it only if required. + if (IRCCD_HAVE_JS) + set(IRCCD_EXTRA_REQUIRES libirccd-js) + endif () + if (IRCCD_HAVE_SSL) + set(IRCCD_EXTRA_LIBS "-lssl -lcrypto") + endif () + + foreach (pkg libirccd-core libirccd libirccd-ctl libirccd-js libirccd-test) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/${pkg}.pc + ${CMAKE_CURRENT_BINARY_DIR}/${pkg}.pc + @ONLY + ) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${pkg}.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + ) + endforeach () +endif ()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmake/export/libirccd-core.pc Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ + +Name: libirccd-core +Description: irccd (core library) +Version: @IRCCD_VERSION@ +Cflags: -I${includedir} -I${includedir}/irccd/extern +Libs: -L${libdir} -lirccd-core -lboost_system -lboost_filesystem -pthread @IRCCD_EXTRA_LIBS@
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmake/export/libirccd-ctl.pc Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ + +Name: libirccd-ctl +Description: irccd (controller library) +Version: @IRCCD_VERSION@ +Cflags: -I${includedir} -I${includedir}/irccd/extern +Libs: -L${libdir} -lirccd-ctl +Requires: libirccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmake/export/libirccd-js.pc Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ + +Name: libirccd-js +Description: irccd (javascript bindings library) +Version: @IRCCD_VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lirccd-js +Requires: libirccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmake/export/libirccd-test.pc Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ + +Name: libirccd-test +Description: irccd (javascript bindings library) +Version: @IRCCD_VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lirccd-test +Requires: libirccd @IRCCD_EXTRA_REQUIRES@
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmake/export/libirccd.pc Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ + +Name: libirccd +Description: irccd (daemon library) +Version: @IRCCD_VERSION@ +Cflags: -I${includedir} -I${includedir}/irccd/extern +Libs: -L${libdir} -lirccd +Requires: libirccd-core
--- a/irccdctl/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/irccdctl/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -91,5 +91,5 @@ EXPORT DESCRIPTION "Irccd controller." SOURCES ${SOURCES} - LIBRARIES libirccdctl + LIBRARIES libirccd-ctl )
--- a/libcommon/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for irccd -# -# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -project(libcommon) - -find_package(Boost 1.60 REQUIRED QUIET COMPONENTS filesystem system) - -set( - HEADERS - ${libcommon_SOURCE_DIR}/irccd/acceptor.hpp - ${libcommon_SOURCE_DIR}/irccd/config.hpp - ${libcommon_SOURCE_DIR}/irccd/connector.hpp - ${libcommon_SOURCE_DIR}/irccd/fs_util.hpp - ${libcommon_SOURCE_DIR}/irccd/ini.hpp - ${libcommon_SOURCE_DIR}/irccd/ini_util.hpp - ${libcommon_SOURCE_DIR}/irccd/json_util.hpp - ${libcommon_SOURCE_DIR}/irccd/options.hpp - ${libcommon_SOURCE_DIR}/irccd/socket_acceptor.hpp - ${libcommon_SOURCE_DIR}/irccd/socket_connector.hpp - ${libcommon_SOURCE_DIR}/irccd/socket_stream.hpp - ${libcommon_SOURCE_DIR}/irccd/stream.hpp - ${libcommon_SOURCE_DIR}/irccd/string_util.hpp - ${libcommon_SOURCE_DIR}/irccd/system.hpp - ${libcommon_SOURCE_DIR}/irccd/tls_acceptor.hpp - ${libcommon_SOURCE_DIR}/irccd/tls_connector.hpp - ${libcommon_SOURCE_DIR}/irccd/tls_stream.hpp - ${libcommon_SOURCE_DIR}/irccd/xdg.hpp -) - -set( - SOURCES - ${libcommon_SOURCE_DIR}/irccd/config.cpp - ${libcommon_SOURCE_DIR}/irccd/ini.cpp - ${libcommon_SOURCE_DIR}/irccd/options.cpp - ${libcommon_SOURCE_DIR}/irccd/string_util.cpp - ${libcommon_SOURCE_DIR}/irccd/system.cpp -) - -irccd_define_library( - TARGET libcommon - EXPORT - HEADERS ${HEADERS} - HEADERS_DIRECTORY irccd - SOURCES - ${libcommon_SOURCE_DIR}/CMakeLists.txt - ${SOURCES} - LIBRARIES - libjson - Threads::Threads - Boost::filesystem - Boost::system - $<$<BOOL:${IRCCD_HAVE_SSL}>:OpenSSL::Crypto> - $<$<BOOL:${IRCCD_HAVE_SSL}>:OpenSSL::SSL> - $<$<BOOL:${APPLE}>:resolv> - $<$<BOOL:${WIN32}>:mswsock> - $<$<BOOL:${WIN32}>:shlwapi> - $<$<BOOL:${WIN32}>:ws2_32> - $<$<STREQUAL:${CMAKE_SYSTEM_NAME},Linux>:dl> - PUBLIC_INCLUDES - $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}> - $<BUILD_INTERFACE:${libcommon_SOURCE_DIR}> - $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> - $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/irccd/extern> -)
--- a/libcommon/irccd/acceptor.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -/* - * acceptor.hpp -- abstract stream acceptor interface - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_ACCEPTOR_HPP -#define IRCCD_COMMON_ACCEPTOR_HPP - -/** - * \file acceptor.hpp - * \brief Abstract stream acceptor interface. - */ - -#include <functional> -#include <memory> -#include <system_error> - -namespace irccd { - -namespace io { - -class stream; - -/** - * \brief Accept completion handler. - */ -using accept_handler = std::function<void (std::error_code, std::shared_ptr<stream>)>; - -/** - * \brief Abstract stream acceptor interface. - * - * This class is used to wait a new client in an asynchronous manner. Derived - * classes must implement a non-blocking accept function. - */ -class acceptor { -public: - /** - * Default constructor. - */ - acceptor() = default; - - /** - * Virtual destructor defaulted. - */ - virtual ~acceptor() = default; - - /** - * Start asynchronous accept. - * - * Once the client is accepted, the original acceptor must be kept until it - * is destroyed. - * - * \pre another accept operation must not be running - * \pre handler != nullptr - * \param handler the handler - */ - virtual void accept(accept_handler handler) = 0; -}; - -} // !io - -} // !irccd - -#endif // !IRCCD_COMMON_ACCEPTOR_HPP
--- a/libcommon/irccd/config.cpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -/* - * config.cpp -- irccd configuration loader - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <boost/filesystem.hpp> - -#include <irccd/system.hpp> - -#include "config.hpp" - -namespace irccd { - -boost::optional<config> config::search(const std::string& name) -{ - for (const auto& path : sys::config_filenames(name)) { - boost::system::error_code ec; - - if (boost::filesystem::exists(path, ec) && !ec) - return config(path); - } - - return boost::optional<config>(); -} - -} // !irccd
--- a/libcommon/irccd/config.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -/* - * config.hpp -- irccd configuration loader - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_CONFIG_HPP -#define IRCCD_COMMON_CONFIG_HPP - -/** - * \file config.hpp - * \brief Read .ini configuration file for irccd - */ - -#include <boost/optional.hpp> - -#include "ini.hpp" - -namespace irccd { - -/** - * \brief Read .ini configuration file for irccd - */ -class config : public ini::document { -private: - std::string path_; - -public: - /** - * Search the configuration file into the standard defined paths. - * - * \param name the file name - * \return the config or empty if not found - */ - static boost::optional<config> search(const std::string& name); - - /** - * Load the configuration from the specified path. - * - * \param path the path - */ - inline config(std::string path = "") - : document(path.empty() ? ini::document() : ini::read_file(path)) - , path_(std::move(path)) - { - } - - /** - * Get the path to the configuration file. - * - * \return the path - */ - inline const std::string& get_path() const noexcept - { - return path_; - } -}; - -} // !irccd - -#endif // !IRCCD_COMMON_CONFIG_HPP
--- a/libcommon/irccd/connector.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -/* - * connector.hpp -- abstract connection interface - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_CONNECTOR_HPP -#define IRCCD_COMMON_CONNECTOR_HPP - -/** - * \file connector.hpp - * \brief Abstract connection interface. - */ - -#include <functional> -#include <memory> -#include <system_error> - -namespace irccd { - -namespace io { - -class stream; - -/** - * \brief Connect completion handler. - */ -using connect_handler = std::function<void (std::error_code, std::shared_ptr<stream>)>; - -/** - * \brief Abstract connection interface. - * - * This class is used to connect to a stream end point (usually sockets) in an - * asynchronous manner. - * - * Derived class must implement non-blocking connect function. - */ -class connector { -public: - /** - * Default constructor. - */ - connector() = default; - - /** - * Virtual destructor defaulted. - */ - virtual ~connector() = default; - - /** - * Start asynchronous connect. - * - * Once the client is connected, the original acceptor must be kept until it - * is destroyed. - * - * \pre another connect operation must not be running - * \pre handler != nullptr - * \param handler the handler - */ - virtual void connect(connect_handler handler) = 0; -}; - -} // !io - -} // !irccd - -#endif // !IRCCD_COMMON_CONNECTOR_HPP
--- a/libcommon/irccd/fs_util.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -/* - * fs_util.hpp -- filesystem utilities - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_FS_UTIL_HPP -#define IRCCD_COMMON_FS_UTIL_HPP - -/** - * \file fs_util.hpp - * \brief Filesystem utilities. - */ - -#include <regex> -#include <string> - -#include <boost/filesystem.hpp> - -namespace irccd { - -/** - * \brief Filesystem utilities. - */ -namespace fs_util { - -// {{{ base_name - -/** - * Get the base name from a path. - * - * Example, base_name("/etc/foo.conf") returns foo.conf - * - * \param path the path - * \return the base name - */ -inline std::string base_name(const std::string& path) -{ - return boost::filesystem::path(path).filename().string(); -} - -// }}} - -// {{{ dir_name - -/** - * Get the parent directory from a path. - * - * Example, dir_name("/etc/foo.conf") returns /etc - * - * \param path the path - * \return the parent directory - */ -inline std::string dir_name(const std::string& path) -{ - return boost::filesystem::path(path).parent_path().string(); -} - -// }}} - -// {{{ find_if - -/** - * Search an item recursively. - * - * The predicate must have the following signature: - * void f(const boost::filesystem::directory_entry& entry) - * - * Where: - * - base is the current parent directory in the tree - * - entry is the current entry - * - * \param base the base directory - * \param predicate the predicate - * \param recursive true to do recursive search - * \return the full path name to the file or empty string if never found - * \throw boost::system::system_error on errors - */ -template <typename Predicate> -std::string find_if(const std::string& base, bool recursive, Predicate&& predicate) -{ - const auto find = [&] (auto it) -> std::string { - for (const auto& entry : it) - if (predicate(entry)) - return entry.path().string(); - - return ""; - }; - - return recursive - ? find(boost::filesystem::recursive_directory_iterator(base)) - : find(boost::filesystem::directory_iterator(base)); -} - -// }}} - -// {{{ find - -/** - * Find a file by name recursively. - * - * \param base the base directory - * \param name the file name - * \param recursive true to do recursive search - * \return the full path name to the file or empty string if never found - * \throw boost::system::system_error on errors - */ -inline std::string find(const std::string& base, const std::string& name, bool recursive = false) -{ - return find_if(base, recursive, [&] (const auto& entry) { - return entry.path().filename().string() == name; - }); -} - -/** - * Overload by regular expression. - * - * \param base the base directory - * \param regex the regular expression - * \param recursive true to do recursive search - * \return the full path name to the file or empty string if never found - * \throw boost::system::system_error on errors - */ -inline std::string find(const std::string& base, const std::regex& regex, bool recursive = false) -{ - return find_if(base, recursive, [&] (const auto& entry) { - return std::regex_match(entry.path().filename().string(), regex); - }); -} - -// }}} - -} // !fs_util - -} // !irccd - -#endif // !IRCCD_COMMON_FS_UTIL_HPP
--- a/libcommon/irccd/ini.cpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,418 +0,0 @@ -/* - * ini.cpp -- extended .ini file parser - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cctype> -#include <cstring> -#include <iostream> -#include <iterator> -#include <fstream> -#include <sstream> -#include <stdexcept> - -#include <irccd/sysconfig.hpp> - -#include <boost/predef.h> - -// for PathIsRelative. -#if BOOST_OS_WINDOWS -# include <shlwapi.h> -#endif - -#include "ini.hpp" - -namespace irccd { - -namespace ini { - -namespace { - -using stream_iterator = std::istreambuf_iterator<char>; -using token_iterator = std::vector<token>::const_iterator; - -inline bool is_absolute(const std::string& path) noexcept -{ -#if BOOST_OS_WINDOWS - return !PathIsRelative(path.c_str()); -#else - return path.size() > 0 && path[0] == '/'; -#endif -} - -inline bool is_quote(char c) noexcept -{ - return c == '\'' || c == '"'; -} - -inline bool is_space(char c) noexcept -{ - // Custom version because std::isspace includes \n as space. - return c == ' ' || c == '\t'; -} - -inline bool is_list(char c) noexcept -{ - return c == '(' || c == ')' || c == ','; -} - -inline bool is_reserved(char c) noexcept -{ - return is_list(c) || is_quote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '='; -} - -void analyse_line(int& line, int& column, stream_iterator& it) noexcept -{ - assert(*it == '\n'); - - ++ line; - ++ it; - column = 0; -} - -void analyse_comment(int& column, stream_iterator& it, stream_iterator end) noexcept -{ - assert(*it == '#'); - - while (it != end && *it != '\n') { - ++ column; - ++ it; - } -} - -void analyse_spaces(int& column, stream_iterator& it, stream_iterator end) noexcept -{ - assert(is_space(*it)); - - while (it != end && is_space(*it)) { - ++ column; - ++ it; - } -} - -void analyse_list(tokens& list, int line, int& column, stream_iterator& it) noexcept -{ - assert(is_list(*it)); - - switch (*it++) { - case '(': - list.emplace_back(token::list_begin, line, column++); - break; - case ')': - list.emplace_back(token::list_end, line, column++); - break; - case ',': - list.emplace_back(token::comma, line, column++); - break; - default: - break; - } -} - -void analyse_section(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end) -{ - assert(*it == '['); - - std::string value; - int save = column; - - // Read section name. - ++ it; - while (it != end && *it != ']') { - if (*it == '\n') - throw exception(line, column, "section not terminated, missing ']'"); - if (is_reserved(*it)) - throw exception(line, column, "section name expected after '[', got '" + std::string(1, *it) + "'"); - - ++ column; - value += *it++; - } - - if (it == end) - throw exception(line, column, "section name expected after '[', got <EOF>"); - if (value.empty()) - throw exception(line, column, "empty section name"); - - // Remove ']'. - ++ it; - - list.emplace_back(token::section, line, save, std::move(value)); -} - -void analyse_assign(tokens& list, int& line, int& column, stream_iterator& it) -{ - assert(*it == '='); - - list.push_back({ token::assign, line, column++ }); - ++ it; -} - -void analyse_quoted_word(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end) -{ - std::string value; - int save = column; - char quote = *it++; - - while (it != end && *it != quote) { - // TODO: escape sequence - ++ column; - value += *it++; - } - - if (it == end) - throw exception(line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>"); - - // Remove quote. - ++ it; - - list.push_back({ token::quoted_word, line, save, std::move(value) }); -} - -void analyse_word(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end) -{ - assert(!is_reserved(*it)); - - std::string value; - int save = column; - - while (it != end && !std::isspace(*it) && !is_reserved(*it)) { - ++ column; - value += *it++; - } - - list.push_back({ token::word, line, save, std::move(value) }); -} - -void analyse_include(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end) -{ - assert(*it == '@'); - - std::string include; - int save = column; - - // Read include. - ++ it; - while (it != end && !is_space(*it)) { - ++ column; - include += *it++; - } - - if (include != "include") - throw exception(line, column, "expected include after '@' token"); - - list.push_back({ token::include, line, save }); -} - -void parse_option_value_simple(option& option, token_iterator& it) -{ - assert(it->type() == token::word || it->type() == token::quoted_word); - - option.push_back((it++)->value()); -} - -void parse_option_value_list(option& option, token_iterator& it, token_iterator end) -{ - assert(it->type() == token::list_begin); - - token_iterator save = it++; - - while (it != end && it->type() != token::list_end) { - switch (it->type()) { - case token::comma: - // Previous must be a word. - if (it[-1].type() != token::word && it[-1].type() != token::quoted_word) - throw exception(it->line(), it->column(), "unexpected comma after '" + it[-1].value() + "'"); - - ++ it; - break; - case token::word: - case token::quoted_word: - option.push_back((it++)->value()); - break; - default: - throw exception(it->line(), it->column(), "unexpected '" + it[-1].value() + "' in list construct"); - break; - } - } - - if (it == end) - throw exception(save->line(), save->column(), "unterminated list construct"); - - // Remove ). - ++ it; -} - -void parse_option(section& sc, token_iterator& it, token_iterator end) -{ - option option(it->value()); - token_iterator save(it); - - // No '=' or something else? - if (++it == end) - throw exception(save->line(), save->column(), "expected '=' assignment, got <EOF>"); - if (it->type() != token::assign) - throw exception(it->line(), it->column(), "expected '=' assignment, got " + it->value()); - - // Empty options are allowed so just test for words. - if (++it != end) { - if (it->type() == token::word || it->type() == token::quoted_word) - parse_option_value_simple(option, it); - else if (it->type() == token::list_begin) - parse_option_value_list(option, it, end); - } - - sc.push_back(std::move(option)); -} - -void parse_include(document& doc, const std::string& path, token_iterator& it, token_iterator end) -{ - token_iterator save(it); - - if (++it == end) - throw exception(save->line(), save->column(), "expected file name after '@include' statement, got <EOF>"); - if (it->type() != token::word && it->type() != token::quoted_word) - throw exception(it->line(), it->column(), "expected file name after '@include' statement, got " + it->value()); - - std::string value = (it++)->value(); - std::string file; - - if (!is_absolute(value)) { -#if BOOST_OS_WINDOWS - file = path + "\\" + value; -#else - file = path + "/" + value; -#endif - } else - file = value; - - for (const auto& sc : read_file(file)) - doc.push_back(sc); -} - -void parse_section(document& doc, token_iterator& it, token_iterator end) -{ - section sc(it->value()); - - // Skip [section]. - ++ it; - - // Read until next section. - while (it != end && it->type() != token::section) { - if (it->type() != token::word) - throw exception(it->line(), it->column(), "unexpected token '" + it->value() + "' in section definition"); - - parse_option(sc, it, end); - } - - doc.push_back(std::move(sc)); -} - -} // !namespace - -tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end) -{ - tokens list; - int line = 1; - int column = 0; - - while (it != end) { - if (*it == '\n') - analyse_line(line, column, it); - else if (*it == '#') - analyse_comment(column, it, end); - else if (*it == '[') - analyse_section(list, line, column, it, end); - else if (*it == '=') - analyse_assign(list, line, column, it); - else if (is_space(*it)) - analyse_spaces(column, it, end); - else if (*it == '@') - analyse_include(list, line, column, it, end); - else if (is_quote(*it)) - analyse_quoted_word(list, line, column, it, end); - else if (is_list(*it)) - analyse_list(list, line, column, it); - else - analyse_word(list, line, column, it, end); - } - - return list; -} - -tokens analyse(std::istream& stream) -{ - return analyse(std::istreambuf_iterator<char>(stream), {}); -} - -document parse(const tokens& tokens, const std::string& path) -{ - document doc; - token_iterator it = tokens.cbegin(); - token_iterator end = tokens.cend(); - - while (it != end) { - switch (it->type()) { - case token::include: - parse_include(doc, path, it, end); - break; - case token::section: - parse_section(doc, it, end); - break; - default: - throw exception(it->line(), it->column(), "unexpected '" + it->value() + "' on root document"); - } - } - - return doc; -} - -document read_file(const std::string& filename) -{ - // Get parent path. - auto parent = filename; - auto pos = parent.find_last_of("/\\"); - - if (pos != std::string::npos) - parent.erase(pos); - else - parent = "."; - - std::ifstream input(filename); - - if (!input) - throw exception(0, 0, std::strerror(errno)); - - return parse(analyse(input), parent); -} - -document read_string(const std::string& buffer) -{ - std::istringstream iss(buffer); - - return parse(analyse(iss)); -} - -void dump(const tokens& tokens) -{ - for (const token& token: tokens) { - // TODO: add better description - std::cout << token.line() << ":" << token.column() << ": " << token.value() << std::endl; - } -} - -} // !ini - -} // !irccd
--- a/libcommon/irccd/ini.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,647 +0,0 @@ -/* - * ini.hpp -- extended .ini file parser - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_INI_HPP -#define IRCCD_COMMON_INI_HPP - -/** - * \file ini.hpp - * \brief Extended .ini file parser. - * \author David Demelier <markand@malikania.fr> - * \version 2.0.0 - */ - -/** - * \page Ini Ini - * \brief Extended .ini file parser. - * - * - \subpage ini-syntax - */ - -/** - * \page ini-syntax Syntax - * \brief File syntax. - * - * The syntax is similar to most of `.ini` implementations as: - * - * - a section is delimited by `[name]` can be redefined multiple times, - * - an option **must** always be defined in a section, - * - empty options must be surrounded by quotes, - * - lists can not include trailing commas, - * - include statements must always live at the beginning of files - * (in no sections), - * - comments start with # until the end of line, - * - options with spaces **must** use quotes. - * - * # Basic file - * - * ````ini - * # This is a comment. - * [section] - * option1 = value1 - * option2 = "value 2 with spaces" # comment is also allowed here - * ```` - * - * # Redefinition - * - * Sections can be redefined multiple times and are kept the order they are - * seen. - * - * ````ini - * [section] - * value = "1" - * - * [section] - * value = "2" - * ```` - * - * The ini::document object will contains two ini::section. - * - * # Lists - * - * Lists are defined using `()` and commas, like values, they may have quotes. - * - * ````ini - * [section] - * names = ( "x1", "x2" ) - * - * # This is also allowed. - * biglist = ( - * "abc", - * "def" - * ) - * ```` - * - * # Include statement - * - * You can split a file into several pieces, if the include statement contains a - * relative path, the path will be relative to the current file being parsed. - * - * You **must** use the include statement before any section. - * - * If the file contains spaces, use quotes. - * - * ````ini - * # main.conf - * @include "foo.conf" - * - * # foo.conf - * [section] - * option1 = value1 - * ```` - */ - -#include "sysconfig.hpp" - -#include <algorithm> -#include <cassert> -#include <exception> -#include <stdexcept> -#include <string> -#include <vector> - -namespace irccd { - -/** - * Namespace for ini related classes. - */ -namespace ini { - -class document; - -/** - * \brief exception in a file. - */ -class exception : public std::exception { -private: - int line_; - int column_; - std::string message_; - -public: - /** - * Constructor. - * - * \param line the line - * \param column the column - * \param msg the message - */ - inline exception(int line, int column, std::string msg) noexcept - : line_(line) - , column_(column) - , message_(std::move(msg)) - { - } - - /** - * Get the line number. - * - * \return the line - */ - inline int line() const noexcept - { - return line_; - } - - /** - * Get the column number. - * - * \return the column - */ - inline int column() const noexcept - { - return column_; - } - - /** - * Return the raw exception message (no line and column shown). - * - * \return the exception message - */ - const char* what() const noexcept override - { - return message_.c_str(); - } -}; - -/** - * \brief Describe a token read in the .ini source. - * - * This class can be used when you want to parse a .ini file yourself. - * - * \see analyse - */ -class token { -public: - /** - * \brief token type. - */ - enum type { - include, //!< include statement - section, //!< [section] - word, //!< word without quotes - quoted_word, //!< word with quotes - assign, //!< = assignment - list_begin, //!< begin of list ( - list_end, //!< end of list ) - comma //!< list separation - }; - -private: - type type_; - int line_; - int column_; - std::string value_; - -public: - /** - * Construct a token. - * - * \param type the type - * \param line the line - * \param column the column - * \param value the value - */ - token(type type, int line, int column, std::string value = "") noexcept - : type_(type) - , line_(line) - , column_(column) - { - switch (type) { - case include: - value_ = "@include"; - break; - case section: - case word: - case quoted_word: - value_ = value; - break; - case assign: - value_ = "="; - break; - case list_begin: - value_ = "("; - break; - case list_end: - value_ = ")"; - break; - case comma: - value_ = ","; - break; - default: - break; - } - } - - /** - * Get the type. - * - * \return the type - */ - inline type type() const noexcept - { - return type_; - } - - /** - * Get the line. - * - * \return the line - */ - inline int line() const noexcept - { - return line_; - } - - /** - * Get the column. - * - * \return the column - */ - inline int column() const noexcept - { - return column_; - } - - /** - * Get the value. For words, quoted words and section, the value is the - * content. Otherwise it's the characters parsed. - * - * \return the value - */ - inline const std::string& value() const noexcept - { - return value_; - } -}; - -/** - * List of tokens in order they are analyzed. - */ -using tokens = std::vector<token>; - -/** - * \brief option definition. - */ -class option : public std::vector<std::string> { -private: - std::string key_; - -public: - /** - * Construct an empty option. - * - * \pre key must not be empty - * \param key the key - */ - inline option(std::string key) noexcept - : std::vector<std::string>() - , key_(std::move(key)) - { - assert(!key_.empty()); - } - - /** - * Construct a single option. - * - * \pre key must not be empty - * \param key the key - * \param value the value - */ - inline option(std::string key, std::string value) noexcept - : key_(std::move(key)) - { - assert(!key_.empty()); - - push_back(std::move(value)); - } - - /** - * Construct a list option. - * - * \pre key must not be empty - * \param key the key - * \param values the values - */ - inline option(std::string key, std::vector<std::string> values) noexcept - : std::vector<std::string>(std::move(values)) - , key_(std::move(key)) - { - assert(!key_.empty()); - } - - /** - * Get the option key. - * - * \return the key - */ - inline const std::string& key() const noexcept - { - return key_; - } - - /** - * Get the option value. - * - * \return the value - */ - inline const std::string& value() const noexcept - { - static std::string dummy; - - return empty() ? dummy : (*this)[0]; - } -}; - -/** - * \brief Section that contains one or more options. - */ -class section : public std::vector<option> { -private: - std::string key_; - -public: - /** - * Construct a section with its name. - * - * \pre key must not be empty - * \param key the key - */ - inline section(std::string key) noexcept - : key_(std::move(key)) - { - assert(!key_.empty()); - } - - /** - * Get the section key. - * - * \return the key - */ - inline const std::string& key() const noexcept - { - return key_; - } - - /** - * Check if the section contains a specific option. - * - * \param key the option key - * \return true if the option exists - */ - inline bool contains(const std::string& key) const noexcept - { - return find(key) != end(); - } - - /** - * Find an option or return an empty one if not found. - * - * \param key the key - * \return the option or empty one if not found - */ - inline option get(const std::string& key) const noexcept - { - auto it = find(key); - - if (it == end()) - return option(key); - - return *it; - } - - /** - * Find an option by key and return an iterator. - * - * \param key the key - * \return the iterator or end() if not found - */ - inline iterator find(const std::string& key) noexcept - { - return std::find_if(begin(), end(), [&] (const auto& o) { - return o.key() == key; - }); - } - - /** - * Find an option by key and return an iterator. - * - * \param key the key - * \return the iterator or end() if not found - */ - inline const_iterator find(const std::string& key) const noexcept - { - return std::find_if(cbegin(), cend(), [&] (const auto& o) { - return o.key() == key; - }); - } - - /** - * Access an option at the specified key. - * - * \param key the key - * \return the option - * \pre contains(key) must return true - */ - inline option& operator[](const std::string& key) - { - assert(contains(key)); - - return *find(key); - } - - /** - * Overloaded function. - * - * \param key the key - * \return the option - * \pre contains(key) must return true - */ - inline const option& operator[](const std::string& key) const - { - assert(contains(key)); - - return *find(key); - } - - /** - * Inherited operators. - */ - using std::vector<option>::operator[]; -}; - -/** - * \brief Ini document description. - * \see read_file - * \see read_string - */ -class document : public std::vector<section> { -public: - /** - * Check if a document has a specific section. - * - * \param key the key - * \return true if the document contains the section - */ - inline bool contains(const std::string& key) const noexcept - { - return find(key) != end(); - } - - /** - * Find a section or return an empty one if not found. - * - * \param key the key - * \return the section or empty one if not found - */ - inline section get(const std::string& key) const noexcept - { - auto it = find(key); - - if (it == end()) - return section(key); - - return *it; - } - - /** - * Find a section by key and return an iterator. - * - * \param key the key - * \return the iterator or end() if not found - */ - inline iterator find(const std::string& key) noexcept - { - return std::find_if(begin(), end(), [&] (const auto& o) { - return o.key() == key; - }); - } - - /** - * Find a section by key and return an iterator. - * - * \param key the key - * \return the iterator or end() if not found - */ - inline const_iterator find(const std::string& key) const noexcept - { - return std::find_if(cbegin(), cend(), [&] (const auto& o) { - return o.key() == key; - }); - } - - /** - * Access a section at the specified key. - * - * \param key the key - * \return the section - * \pre contains(key) must return true - */ - inline section& operator[](const std::string& key) - { - assert(contains(key)); - - return *find(key); - } - - /** - * Overloaded function. - * - * \param key the key - * \return the section - * \pre contains(key) must return true - */ - inline const section& operator[](const std::string& key) const - { - assert(contains(key)); - - return *find(key); - } - - /** - * Inherited operators. - */ - using std::vector<section>::operator[]; -}; - -/** - * Analyse a stream and detect potential syntax errors. This does not parse the - * file like including other files in include statement. - * - * It does only analysis, for example if an option is defined under no section, - * this does not trigger an exception while it's invalid. - * - * \param it the iterator - * \param end where to stop - * \return the list of tokens - * \throws exception on errors - */ -tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end); - -/** - * Overloaded function for stream. - * - * \param stream the stream - * \return the list of tokens - * \throws exception on errors - */ -tokens analyse(std::istream& stream); - -/** - * Parse the produced tokens. - * - * \param tokens the tokens - * \param path the parent path - * \return the document - * \throw exception on errors - */ -document parse(const tokens& tokens, const std::string& path = "."); - -/** - * Parse a file. - * - * \param filename the file name - * \return the document - * \throw exception on errors - */ -document read_file(const std::string& filename); - -/** - * Parse a string. - * - * If the string contains include statements, they are relative to the current - * working directory. - * - * \param buffer the buffer - * \return the document - * \throw exception on exceptions - */ -document read_string(const std::string& buffer); - -/** - * Show all tokens and their description. - * - * \param tokens the tokens - */ -void dump(const tokens& tokens); - -} // !ini - -} // !irccd - -#endif // !IRCCD_COMMON_INI_HPP
--- a/libcommon/irccd/ini_util.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -/* - * ini_util.hpp -- ini utilities - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_INI_UTIL_HPP -#define IRCCD_COMMON_INI_UTIL_HPP - -/** - * \file ini_util.hpp - * \brief Ini utilities. - */ - -#include <boost/optional.hpp> - -#include "ini.hpp" -#include "string_util.hpp" - -namespace irccd { - -/** - * \brief Ini utilities. - */ -namespace ini_util { - -/** - * Get an unsigned integer from the configuration section. - * - * \param sc the section - * \param name the option name - * \return the value or none if not able to convert - */ -template <typename Int> -inline boost::optional<Int> get_uint(const ini::section& sc, const std::string& name) noexcept -{ - return string_util::to_uint<Int>(sc.get(name).value()); -} - -/** - * Get an optional string or the default value if not given. - * - * \param sc the section - * \param name the option name - * \param def the default value - * \return the value or def if not found - */ -inline std::string optional_string(const ini::section& sc, - const std::string& name, - const std::string& def) noexcept -{ - const auto it = sc.find(name); - - if (it == sc.end()) - return def; - - return it->value(); -} - -/** - * Get an optional unsigned integer from the configuration section. - * - * \param sc the section - * \param name the option name - * \param def the default value - * \return the value or none if not able to convert - */ -template <typename Int> -inline boost::optional<Int> optional_uint(const ini::section& sc, - const std::string& name, - Int def) noexcept -{ - const auto it = sc.find(name); - - if (it == sc.end()) - return def; - - return string_util::to_uint<Int>(it->value()); -} - -} // !ini_util - -} // !irccd - -#endif // !IRCCD_COMMON_INI_UTIL_HPP
--- a/libcommon/irccd/json_util.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,366 +0,0 @@ -/* - * json_util.hpp -- utilities for JSON - * - * Copyright (c) 2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_JSON_UTIL_HPP -#define IRCCD_JSON_UTIL_HPP - -/** - * \file json_util.hpp - * \brief Utilities for JSON. - */ - -#include <cstdint> -#include <limits> -#include <string> -#include <type_traits> - -#include <boost/optional.hpp> - -#include <json.hpp> - -namespace irccd { - -/** - * \brief Utilities for JSON. - */ -namespace json_util { - -/** - * \cond JSON_UTIL_HIDDEN_SYMBOLS - */ - -namespace detail { - -template <typename Int> -class parser_type_traits_uint : public std::true_type { -public: - static boost::optional<Int> get(const nlohmann::json& value) noexcept - { - if (!value.is_number_unsigned()) - return boost::none; - - const auto ret = value.get<std::uint64_t>(); - - if (ret > std::numeric_limits<Int>::max()) - return boost::none; - - return static_cast<Int>(ret); - } -}; - -template <typename Int> -class parser_type_traits_int : public std::true_type { -public: - static boost::optional<Int> get(const nlohmann::json& value) noexcept - { - if (!value.is_number_integer()) - return boost::none; - - const auto ret = value.get<std::int64_t>(); - - if (ret < std::numeric_limits<Int>::min() || ret > std::numeric_limits<Int>::max()) - return boost::none; - - return static_cast<Int>(ret); - } -}; - -} // !detail - -/** - * \endcond - */ - -/** - * \brief Describe how to convert a JSON value. - * - * This class must be specialized for every type you want to convert from JSON - * to its native type. - * - * You only need to implement the get function with the following signature: - * - * ```cpp - * static boost::optional<T> get(const nlohmann::json& value); - * ``` - * - * The implementation should not throw an exception but return a null optional - * instead. - * - * This class is already specialized for the given types: - * - * - bool - * - double - * - std::uint(8, 16, 32, 64) - * - std::string - */ -template <typename T> -class parser_type_traits : public std::false_type { -}; - -/** - * \brief Specialization for `bool`. - */ -template <> -class parser_type_traits<bool> : public std::true_type { -public: - /** - * Convert the JSON value to bool. - * - * \return the bool or none if not a boolean type - */ - static boost::optional<bool> get(const nlohmann::json& value) noexcept - { - if (!value.is_boolean()) - return boost::none; - - return value.get<bool>(); - } -}; - -/** - * \brief Specialization for `double`. - */ -template <> -class parser_type_traits<double> : public std::true_type { -public: - /** - * Convert the JSON value to bool. - * - * \return the double or none if not a double type - */ - static boost::optional<double> get(const nlohmann::json& value) noexcept - { - if (!value.is_number_float()) - return boost::none; - - return value.get<double>(); - } -}; - -/** - * \brief Specialization for `std::string`. - */ -template <> -class parser_type_traits<std::string> : public std::true_type { -public: - /** - * Convert the JSON value to bool. - * - * \return the string or none if not a string type - */ - static boost::optional<std::string> get(const nlohmann::json& value) - { - if (!value.is_string()) - return boost::none; - - return value.get<std::string>(); - } -}; - -/** - * \brief Specialization for `std::int8_t`. - */ -template <> -class parser_type_traits<std::int8_t> : public detail::parser_type_traits_int<std::int8_t> { -}; - -/** - * \brief Specialization for `std::int16_t`. - */ -template <> -class parser_type_traits<std::int16_t> : public detail::parser_type_traits_int<std::int16_t> { -}; - -/** - * \brief Specialization for `std::int32_t`. - */ -template <> -class parser_type_traits<std::int32_t> : public detail::parser_type_traits_int<std::int32_t> { -}; - -/** - * \brief Specialization for `std::int64_t`. - */ -template <> -class parser_type_traits<std::int64_t> : public std::true_type { -public: - /** - * Convert the JSON value to std::int64_t. - * - * \return the int or none if not a int type - */ - static boost::optional<std::int64_t> get(const nlohmann::json& value) noexcept - { - if (!value.is_number_integer()) - return boost::none; - - return value.get<std::int64_t>(); - } -}; - -/** - * \brief Specialization for `std::int8_t`. - */ -template <> -class parser_type_traits<std::uint8_t> : public detail::parser_type_traits_uint<std::uint8_t> { -}; - -/** - * \brief Specialization for `std::int16_t`. - */ -template <> -class parser_type_traits<std::uint16_t> : public detail::parser_type_traits_uint<std::uint16_t> { -}; - -/** - * \brief Specialization for `std::int32_t`. - */ -template <> -class parser_type_traits<std::uint32_t> : public detail::parser_type_traits_uint<std::uint32_t> { -}; - -/** - * \brief Specialization for `std::int64_t`. - */ -template <> -class parser_type_traits<std::uint64_t> : public std::true_type { -public: - /** - * Convert the JSON value to std::uint64_t. - * - * \return the int or none if not a int type - */ - static boost::optional<std::uint64_t> get(const nlohmann::json& value) noexcept - { - if (!value.is_number_unsigned()) - return boost::none; - - return value.get<std::uint64_t>(); - } -}; - -/** - * \brief Convenient JSON object parser - * - * This class helps destructuring insecure JSON input by returning optional - * values if they are not present or invalid. - */ -class document : public nlohmann::json { -public: - /** - * Constructor. - * - * \param object the object - */ - inline document(nlohmann::json object) - : nlohmann::json(std::move(object)) - { - } - - /** - * Get a value from the document object. - * - * \param key the property key - * \return the value or boost::none if not found or not convertible - */ - template <typename Type> - inline boost::optional<Type> get(const std::string& key) const noexcept - { - static_assert(parser_type_traits<Type>::value, "type not supported"); - - const auto it = find(key); - - if (it == end()) - return boost::none; - - return parser_type_traits<Type>::get(*it); - } - - /** - * Get an optional value from the document object. - * - * If the value is undefined, the default value is returned. Otherwise, if - * the value is not in the given type, boost::none is returned. - * - * \param key the property key - * \param def the default value if property is undefined - * \return the value, boost::none or def - */ - template <typename Type, typename DefaultValue> - inline boost::optional<Type> optional(const std::string& key, DefaultValue&& def) const noexcept - { - static_assert(parser_type_traits<Type>::value, "type not supported"); - - const auto it = find(key); - - if (it == end()) - return boost::optional<Type>(std::forward<DefaultValue>(def)); - - return parser_type_traits<Type>::get(*it); - } -}; - -/** - * Print the value as human readable. - * - * \note This only works on flat objects. - * \param value the value - * \param indent the optional indent for objects/arrays - * \return the string - */ -inline std::string pretty(const nlohmann::json& value, int indent = 4) -{ - switch (value.type()) { - case nlohmann::json::value_t::null: - return "null"; - case nlohmann::json::value_t::string: - return value.get<std::string>(); - case nlohmann::json::value_t::boolean: - return value.get<bool>() ? "true" : "false"; - case nlohmann::json::value_t::number_integer: - return std::to_string(value.get<std::int64_t>()); - case nlohmann::json::value_t::number_unsigned: - return std::to_string(value.get<std::uint64_t>()); - case nlohmann::json::value_t::number_float: - return std::to_string(value.get<double>()); - default: - return value.dump(indent); - } -} - -/** - * Check if a JSON array contains a specific value in any order. - * - * \param array the JSON array - * \param value the JSON value - * \return true if value is present - */ -inline bool contains(const nlohmann::json& array, const nlohmann::json& value) noexcept -{ - for (const auto& v : array) - if (v == value) - return true; - - return false; -} - -} // !json_util - -} // !irccd - -#endif // !IRCCD_JSON_UTIL_HPP
--- a/libcommon/irccd/options.cpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,191 +0,0 @@ -/* - * options.cpp -- parse Unix command line options - * - * Copyright (c) 2015-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cassert> - -#include "options.hpp" - -namespace irccd { - -namespace option { - -namespace { - -using iterator = std::vector<std::string>::iterator; -using args = std::vector<std::string>; - -inline bool is_option(const std::string& arg) noexcept -{ - return arg.size() >= 2 && arg[0] == '-'; -} - -inline bool is_long_option(const std::string& arg) noexcept -{ - assert(is_option(arg)); - - return arg.size() >= 3 && arg[1] == '-'; -} - -inline bool is_short_simple(const std::string& arg) noexcept -{ - assert(is_option(arg) && !is_long_option(arg)); - - return arg.size() == 2; -} - -void parse_long_option(result& result, args& args, iterator& it, iterator& end, const options& definition) -{ - auto arg = *it++; - auto opt = definition.find(arg); - - if (opt == definition.end()) - throw invalid_option(arg); - - // Need argument? - if (opt->second) { - if (it == end || is_option(*it)) - throw missing_value(arg); - - result.insert(std::make_pair(arg, *it++)); - it = args.erase(args.begin(), it); - end = args.end(); - } else { - result.insert(std::make_pair(arg, "")); - it = args.erase(args.begin()); - end = args.end(); - } -} - -void parse_short_option_simple(result& result, args& args, iterator& it, iterator &end, const options& definition) -{ - /* - * Here two cases: - * - * -v (no option) - * -c value - */ - auto arg = *it++; - auto opt = definition.find(arg); - - if (opt == definition.end()) - throw invalid_option(arg); - - // Need argument? - if (opt->second) { - if (it == end || is_option(*it)) - throw missing_value(arg); - - result.insert(std::make_pair(arg, *it++)); - it = args.erase(args.begin(), it); - end = args.end(); - } else { - result.insert(std::make_pair(arg, "")); - it = args.erase(args.begin()); - end = args.end(); - } -} - -void parse_short_option_compressed(result& result, args& args, iterator& it, iterator &end, const options& definition) -{ - /* - * Here multiple scenarios: - * - * 1. -abc (-a -b -c if all are simple boolean arguments) - * 2. -vc foo.conf (-v -c foo.conf if -c is argument dependant) - * 3. -vcfoo.conf (-v -c foo.conf also) - */ - auto value = it->substr(1); - auto len = value.length(); - int toremove = 1; - - for (std::size_t i = 0; i < len; ++i) { - auto arg = std::string{'-'} + value[i]; - auto opt = definition.find(arg); - - if (opt == definition.end()) - throw invalid_option(arg); - - if (opt->second) { - if (i == (len - 1)) { - // End of string, get the next argument (see 2.). - if (++it == end || is_option(*it)) - throw missing_value(arg); - - result.insert(std::make_pair(arg, *it)); - toremove += 1; - } else { - result.insert(std::make_pair(arg, value.substr(i + 1))); - i = len; - } - } else - result.insert(std::make_pair(arg, "")); - } - - it = args.erase(args.begin(), args.begin() + toremove); - end = args.end(); -} - -void parse_short_option(result& result, args& args, iterator& it, iterator &end, const options& definition) -{ - if (is_short_simple(*it)) - parse_short_option_simple(result, args, it, end, definition); - else - parse_short_option_compressed(result, args, it, end, definition); -} - -} // !namespace - -result read(std::vector<std::string>& args, const options& definition) -{ - result result; - - auto it = args.begin(); - auto end = args.end(); - - while (it != end) { - if (!is_option(*it)) - break; - - if (is_long_option(*it)) - parse_long_option(result, args, it, end, definition); - else - parse_short_option(result, args, it, end, definition); - } - - return result; -} - -result read(int& argc, char**& argv, const options& definition) -{ - std::vector<std::string> args; - - for (int i = 0; i < argc; ++i) - args.push_back(argv[i]); - - auto before = args.size(); - auto result = read(args, definition); - - argc -= before - args.size(); - argv += before - args.size(); - - return result; -} - -} // !option - -} // !irccd
--- a/libcommon/irccd/options.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -/* - * options.hpp -- parse Unix command line options - * - * Copyright (c) 2015-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_OPTIONS_HPP -#define IRCCD_COMMON_OPTIONS_HPP - -/** - * \file options.hpp - * \brief Basic Unix options parser. - */ - -#include <exception> -#include <map> -#include <string> -#include <utility> -#include <vector> - -namespace irccd { - -/** - * Namespace for options parsing. - */ -namespace option { - -/** - * \brief This exception is thrown when an invalid option has been found. - */ -class invalid_option : public std::exception { -private: - std::string message_; - std::string name_; - -public: - /** - * Construct the exception. - * - * \param name the argument missing - */ - inline invalid_option(std::string name) - : name_(std::move(name)) - { - message_ = std::string("invalid option: ") + name_; - } - - /** - * Get the option name. - * - * \return the name - */ - inline const std::string& name() const noexcept - { - return name_; - } - - /** - * Get the error message. - * - * \return the error message - */ - const char* what() const noexcept override - { - return message_.c_str(); - } -}; - -/** - * \brief This exception is thrown when an option requires a value and no value - * has been given. - */ -class missing_value : public std::exception { -private: - std::string message_; - std::string name_; - -public: - /** - * Construct the exception. - * - * \param name the option that requires a value - */ - inline missing_value(std::string name) - : name_(std::move(name)) - { - message_ = std::string("missing argument for: ") + name_; - } - - /** - * Get the option name. - * - * \return the name - */ - inline const std::string& name() const noexcept - { - return name_; - } - - /** - * Get the error message. - * - * \return the error message - */ - const char* what() const noexcept override - { - return message_.c_str(); - } -}; - -/** - * Packed multimap of options. - */ -using result = std::multimap<std::string, std::string>; - -/** - * Define the allowed options. - */ -using options = std::map<std::string, bool>; - -/** - * Extract the command line options and return a result. - * - * \param args the arguments - * \param definition - * \warning the arguments vector is modified in place to remove parsed options - * \throw missing_value - * \throw invalid_option - */ -result read(std::vector<std::string>& args, const options& definition); - -/** - * Overloaded function for usage with main() arguments. - * - * \param argc the number of arguments - * \param argv the argument vector - * \param definition - * \note don't forget to remove the first argv[0] argument - * \warning the argc and argv are modified in place to remove parsed options - * \throw missing_value - * \throw invalid_option - */ -result read(int& argc, char**& argv, const options& definition); - -} // !option - -} // !irccd - -#endif // !IRCCD_COMMON_OPTIONS_HPP
--- a/libcommon/irccd/socket_acceptor.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +0,0 @@ -/* - * socket_acceptor.hpp -- socket stream acceptor interface - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_SOCKET_ACCEPTOR_HPP -#define IRCCD_COMMON_SOCKET_ACCEPTOR_HPP - -/** - * \file socket_acceptor.hpp - * \brief Socket stream acceptor interface. - */ - -#include <irccd/sysconfig.hpp> - -#include "acceptor.hpp" -#include "socket_stream.hpp" - -namespace irccd { - -namespace io { - -/** - * \brief Socket stream acceptor interface. - * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) - */ -template <typename Protocol> -class socket_acceptor : public acceptor { -public: - /** - * Convenient endpoint alias. - */ - using endpoint = typename Protocol::endpoint; - - /** - * Convenient acceptor alias. - */ - using acceptor = typename Protocol::acceptor; - - /** - * Convenient socket alias. - */ - using socket = typename Protocol::socket; - -private: - acceptor acceptor_; - -#if !defined(NDEBUG) - bool is_accepting_{false}; -#endif - -protected: - /** - * Helper to accept on the real underlying socket. - * - * \param socket the real socket - * \param handler the handler - */ - template <typename Socket, typename Handler> - void do_accept(Socket& socket, Handler handler); - -public: - /** - * Construct the socket_acceptor. - * - * \pre acceptor must be ready (is_open() returns true) - * \param acceptor the Boost.Asio acceptor - */ - inline socket_acceptor(acceptor acceptor) noexcept - : acceptor_(std::move(acceptor)) - { - assert(acceptor_.is_open()); - } - - /** - * Get the underlying acceptor. - * - * \return the acceptor - */ - inline const acceptor& get_acceptor() const noexcept - { - return acceptor_; - } - - /** - * Overloaded function. - * - * \return the acceptor - */ - inline acceptor& get_acceptor() noexcept - { - return acceptor_; - } - - /** - * \copydoc acceptor::accept - */ - void accept(accept_handler handler) override; -}; - -template <typename Protocol> -template <typename Socket, typename Handler> -void socket_acceptor<Protocol>::do_accept(Socket& socket, Handler handler) -{ -#if !defined(NDEBUG) - assert(!is_accepting_); - - is_accepting_ = true; -#endif - - acceptor_.async_accept(socket, [this, handler] (auto code) { -#if !defined(NDEBUG) - is_accepting_ = false; -#endif - handler(detail::convert(code)); - }); -} - -template <typename Protocol> -void socket_acceptor<Protocol>::accept(accept_handler handler) -{ - assert(handler); - - const auto client = std::make_shared<socket_stream<socket>>(acceptor_.get_io_service()); - - do_accept(client->get_socket(), [this, client, handler] (auto code) { - handler(std::move(code), code ? nullptr : std::move(client)); - }); -} - -/** - * Convenient TCP/IP acceptor type. - */ -using ip_acceptor = socket_acceptor<boost::asio::ip::tcp>; - -#if !BOOST_OS_WINDOWS - -/** - * Convenient Unix acceptor type. - */ -using local_acceptor = socket_acceptor<boost::asio::local::stream_protocol>; - -#endif - -} // !io - -} // !irccd - -#endif // !IRCCD_COMMON_SOCKET_ACCEPTOR_HPP
--- a/libcommon/irccd/socket_connector.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,176 +0,0 @@ -/* - * socket_connector.hpp -- socket connection interface - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_SOCKET_CONNECTOR_HPP -#define IRCCD_COMMON_SOCKET_CONNECTOR_HPP - -/** - * \file socket_connector.hpp - * \brief Socket connection interface. - */ - -#include <irccd/sysconfig.hpp> - -#include <vector> - -#include "connector.hpp" -#include "socket_stream.hpp" - -namespace irccd { - -namespace io { - -/** - * \brief Socket connection interface. - * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) - */ -template <typename Protocol> -class socket_connector : public connector { -public: - /** - * Convenient endpoint alias. - */ - using endpoint = typename Protocol::endpoint; - - /** - * Convenient socket alias. - */ - using socket = typename Protocol::socket; - -private: - boost::asio::io_service& service_; - std::vector<endpoint> endpoints_; - -#if !defined(NDEBUG) - bool is_connecting_{false}; -#endif - -protected: - /** - * Start trying to connect to all endpoints. - * - * \param socket the underlying socket - * \param handler handler with `void f(std::error_code)` signature - */ - template <typename Socket, typename Handler> - void do_connect(Socket& socket, Handler handler); - -public: - /** - * Construct the socket connector with only one endpoint. - * - * \param service the service - * \param endpoint the unique endpoint - */ - inline socket_connector(boost::asio::io_service& service, endpoint endpoint) noexcept - : service_(service) - , endpoints_{std::move(endpoint)} - { - } - - /** - * Construct the socket connection. - * - * \param service the service - * \param eps the endpoints - */ - inline socket_connector(boost::asio::io_service& service, std::vector<endpoint> eps) noexcept - : service_(service) - , endpoints_(std::move(eps)) - { - } - - /** - * Get the underlying I/O service. - * - * \return the I/O service - */ - inline const boost::asio::io_service& get_io_service() const noexcept - { - return service_; - } - - /** - * Overloaded function. - * - * \return the I/O service - */ - inline boost::asio::io_service& get_io_service() noexcept - { - return service_; - } - - /** - * \copydoc connector::connect - */ - void connect(connect_handler handler); -}; - -template <typename Protocol> -template <typename Socket, typename Handler> -void socket_connector<Protocol>::do_connect(Socket& socket, Handler handler) -{ -#if !defined(NDEBUG) - assert(!is_connecting_); - - is_connecting_ = true; -#endif - - boost::asio::async_connect(socket, endpoints_.begin(), endpoints_.end(), [this, handler] (auto code, auto ep) { -#if !defined(NDEBUG) - is_connecting_ = false; -#endif - - if (ep == endpoints_.end()) - handler(make_error_code(std::errc::host_unreachable)); - else - handler(detail::convert(code)); - }); -} - -template <typename Protocol> -void socket_connector<Protocol>::connect(connect_handler handler) -{ - assert(handler); - - const auto stream = std::make_shared<socket_stream<socket>>(service_); - - do_connect(stream->get_socket(), [handler, stream] (auto code) { - handler(code, code ? nullptr : std::move(stream)); - }); -} - -/** - * Convenient TCP/IP connector type. - */ -using ip_connector = socket_connector<boost::asio::ip::tcp>; - -#if !BOOST_OS_WINDOWS - -/** - * Convenient Unix conncetor type. - */ -using local_connector = socket_connector<boost::asio::local::stream_protocol>; - -#endif - -} // !io - -} // !irccd - -#endif // !IRCCD_COMMON_SOCKET_CONNECTOR_HPP
--- a/libcommon/irccd/socket_stream.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,246 +0,0 @@ -/* - * socket_stream.hpp -- socket stream interface - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_SOCKET_STREAM_HPP -#define IRCCD_COMMON_SOCKET_STREAM_HPP - -/** - * \file socket_stream.hpp - * \brief Socket stream interface. - */ - -#include <irccd/sysconfig.hpp> - -#include <cstddef> -#include <cassert> -#include <string> -#include <utility> - -#include <boost/asio.hpp> -#include <boost/predef/os.h> - -#include "stream.hpp" - -namespace irccd { - -namespace io { - -/** - * \cond HIDDEN_SYMBOLS - */ - -namespace detail { - -/** - * Convert boost::system::error_code to std. - * - * \param code the error code - * \return the std::error_code - */ -inline std::error_code convert(boost::system::error_code code) noexcept -{ - return std::error_code(code.value(), std::system_category()); -} - -} // !detail - -/** - * \endcond - */ - -/** - * \brief Socket implementation interface. - * \tparam Socket the Boost.Asio compatible socket. - * - * This class reimplements stream for Boost.Asio sockets. - */ -template <typename Socket> -class socket_stream : public stream { -private: - Socket socket_; - boost::asio::streambuf input_; - std::string output_; - -#if !defined(NDEBUG) - bool is_receiving_{false}; - bool is_sending_{false}; -#endif - - void handle_read(boost::system::error_code, std::size_t, read_handler); - void handle_write(boost::system::error_code, std::size_t, write_handler); - -public: - /** - * Create the socket stream. - * - * \param args the Socket constructor arguments - */ - template <typename... Args> - inline socket_stream(Args&&... args) - : socket_(std::forward<Args>(args)...) - { - } - - /** - * Get the underlying socket. - * - * \return the socket - */ - inline const Socket& get_socket() const noexcept - { - return socket_; - } - - /** - * Overloaded function - * - * \return the socket - */ - inline Socket& get_socket() noexcept - { - return socket_; - } - - /** - * \copydoc stream::read - */ - void read(read_handler handler) override; - - /** - * \copydoc stream::write - */ - void write(const nlohmann::json& json, write_handler handler) override; -}; - -template <typename Socket> -void socket_stream<Socket>::handle_read(boost::system::error_code code, - std::size_t xfer, - read_handler handler) -{ -#if !defined(NDEBUG) - is_receiving_ = false; -#endif - - if (xfer == 0U) { - handler(make_error_code(std::errc::not_connected), nullptr); - return; - } - if (code) { - handler(detail::convert(code), nullptr); - return; - } - - // 1. Convert the buffer safely. - std::string buffer; - - try { - buffer = std::string( - boost::asio::buffers_begin(input_.data()), - boost::asio::buffers_begin(input_.data()) + xfer - /* \r\n\r\n */ 4 - ); - - input_.consume(xfer); - } catch (const std::bad_alloc&) { - handler(make_error_code(std::errc::not_enough_memory), nullptr); - return; - } - - // 2. Convert to JSON. - nlohmann::json doc; - - try { - doc = nlohmann::json::parse(buffer); - } catch (const std::exception&) { - handler(make_error_code(std::errc::invalid_argument), nullptr); - return; - } - - if (!doc.is_object()) - handler(make_error_code(std::errc::invalid_argument), nullptr); - else - handler(std::error_code(), std::move(doc)); -} - -template <typename Socket> -void socket_stream<Socket>::handle_write(boost::system::error_code code, - std::size_t xfer, - write_handler handler) -{ -#if !defined(NDEBUG) - is_sending_ = false; -#endif - - if (xfer == 0) - handler(make_error_code(std::errc::not_connected)); - else - handler(detail::convert(code)); -} - -template <typename Socket> -void socket_stream<Socket>::read(read_handler handler) -{ -#if !defined(NDEBUG) - assert(!is_receiving_); - assert(handler); - - is_receiving_ = true; -#endif - - boost::asio::async_read_until(get_socket(), input_, "\r\n\r\n", [this, handler] (auto code, auto xfer) { - handle_read(code, xfer, std::move(handler)); - }); -} - -template <typename Socket> -void socket_stream<Socket>::write(const nlohmann::json& json, write_handler handler) -{ -#if !defined(NDEBUG) - assert(!is_sending_); - assert(handler); - - is_sending_ = true; -#endif - - output_ = json.dump(0) + "\r\n\r\n"; - - const auto buffer = boost::asio::buffer(output_.data(), output_.size()); - - boost::asio::async_write(get_socket(), buffer, [this, handler] (auto code, auto xfer) { - handle_write(code, xfer, std::move(handler)); - }); -} - -/** - * Convenient TCP/IP stream type. - */ -using ip_stream = socket_stream<boost::asio::ip::tcp::socket>; - -#if !BOOST_OS_WINDOWS - -/** - * Convenient Unix stream type. - */ -using local_stream = socket_stream<boost::asio::local::stream_protocol::socket>; - -#endif - -} // !io - -} // !irccd - -#endif // !IRCCD_COMMON_SOCKET_STREAM_HPP
--- a/libcommon/irccd/stream.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ -/* - * stream.hpp -- abstract stream interface - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_STREAM_HPP -#define IRCCD_COMMON_STREAM_HPP - -/** - * \file stream.hpp - * \brief Abstract stream interface. - */ - -#include <functional> -#include <system_error> - -#include "json.hpp" - -namespace irccd { - -namespace io { - -/** - * \brief Read completion handler. - */ -using read_handler = std::function<void (std::error_code, nlohmann::json)>; - -/** - * \brief Write completion handler. - */ -using write_handler = std::function<void (std::error_code)>; - -/** - * \brief Abstract stream interface - * - * Abstract I/O interface that allows reading/writing from a stream in an - * asynchronous manner. - * - * The derived classes must implement non-blocking read and write operations. - */ -class stream { -public: - /** - * Default constructor. - */ - stream() = default; - - /** - * Virtual destructor defaulted. - */ - virtual ~stream() = default; - - /** - * Start asynchronous read. - * - * \pre another read operation must not be running - * \pre handler != nullptr - * \param handler the handler - */ - virtual void read(read_handler handler) = 0; - - /** - * Start asynchronous write. - * - * \pre json.is_object() - * \pre another write operation must not be running - * \pre handler != nullptr - * \param json the JSON message - * \param handler the handler - */ - virtual void write(const nlohmann::json& json, write_handler handler) = 0; -}; - -} // !io - -} // !irccd - -#endif // !IRCCD_COMMON_STREAM_HPP
--- a/libcommon/irccd/string_util.cpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,446 +0,0 @@ -/* - * string_util.cpp -- string utilities - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <boost/predef/os.h> - -#include "sysconfig.hpp" - -#if defined(HAVE_POPEN) -# include <array> -# include <cerrno> -# include <cstring> -# include <functional> -# include <memory> -#endif - -#include <cassert> -#include <iomanip> - -#include "string_util.hpp" - -using namespace std::string_literals; - -namespace irccd { - -namespace string_util { - -// {{{ subst - -namespace { - -const std::unordered_map<std::string, int> irc_colors{ - { "white", 0 }, - { "black", 1 }, - { "blue", 2 }, - { "green", 3 }, - { "red", 4 }, - { "brown", 5 }, - { "purple", 6 }, - { "orange", 7 }, - { "yellow", 8 }, - { "lightgreen", 9 }, - { "cyan", 10 }, - { "lightcyan", 11 }, - { "lightblue", 12 }, - { "pink", 13 }, - { "grey", 14 }, - { "lightgrey", 15 } -}; - -const std::unordered_map<std::string, char> irc_attributes{ - { "bold", '\x02' }, - { "italic", '\x09' }, - { "strike", '\x13' }, - { "reset", '\x0f' }, - { "underline", '\x15' }, - { "underline2", '\x1f' }, - { "reverse", '\x16' } -}; - -const std::unordered_map<std::string, unsigned> shell_colors{ - { "black", 30 }, - { "red", 31 }, - { "green", 32 }, - { "orange", 33 }, - { "blue", 34 }, - { "purple", 35 }, - { "cyan", 36 }, - { "white", 37 }, - { "default", 39 }, -}; - -const std::unordered_map<std::string, unsigned> shell_attributes{ - { "bold", 1 }, - { "dim", 2 }, - { "underline", 4 }, - { "blink", 5 }, - { "reverse", 7 }, - { "hidden", 8 } -}; - -inline bool is_reserved(char token) noexcept -{ - return token == '#' || token == '@' || token == '$' || token == '!'; -} - -std::string subst_date(const std::string& text, const subst& params) -{ - std::ostringstream oss; - -#if defined(HAVE_STD_PUT_TIME) - oss << std::put_time(std::localtime(¶ms.time), text.c_str()); -#else - /* - * Quick and dirty hack because old version of GCC does not have this - * function. - */ - char buffer[4096]; - - std::strftime(buffer, sizeof (buffer) - 1, text.c_str(), std::localtime(¶ms.time)); - - oss << buffer; -#endif - - return oss.str(); -} - -std::string subst_keywords(const std::string& content, const subst& params) -{ - auto value = params.keywords.find(content); - - if (value != params.keywords.end()) - return value->second; - - return ""; -} - -std::string subst_env(const std::string& content) -{ - auto value = std::getenv(content.c_str()); - - if (value != nullptr) - return value; - - return ""; -} - -std::string subst_irc_attrs(const std::string& content) -{ - auto list = split(content, ","); - - // @{} means reset. - if (list.empty()) - return std::string(1, irc_attributes.at("reset")); - - std::ostringstream oss; - - // Remove useless spaces. - std::transform(list.begin(), list.end(), list.begin(), strip); - - /* - * 0: foreground - * 1: background - * 2-n: attributes - */ - auto foreground = list[0]; - if (!foreground.empty() || list.size() >= 2) { - // Color sequence. - oss << '\x03'; - - // Foreground. - auto it = irc_colors.find(foreground); - if (it != irc_colors.end()) - oss << it->second; - - // Background. - if (list.size() >= 2 && (it = irc_colors.find(list[1])) != irc_colors.end()) - oss << "," << it->second; - - // Attributes. - for (std::size_t i = 2; i < list.size(); ++i) { - auto attribute = irc_attributes.find(list[i]); - - if (attribute != irc_attributes.end()) - oss << attribute->second; - } - } - - return oss.str(); -} - -std::string subst_shell_attrs(const std::string& content) -{ -#if !BOOST_OS_WINDOWS - auto list = split(content, ","); - - if (list.empty()) - return "\033[0m"; - if (list.size() > 3) - return ""; - - std::vector<std::string> seq; - - /* - * Shell sequence looks like this: - * - * ^[[attributes;foreground;backgroundm - */ - if (list.size() >= 3) { - const auto it = shell_attributes.find(list[2]); - - if (it != shell_attributes.end()) - seq.push_back(std::to_string(it->second)); - else - return ""; - } - if (list.size() >= 1) { - const auto it = shell_colors.find(list[0]); - - if (it != shell_colors.end()) - seq.push_back(std::to_string(it->second)); - else - return ""; - } - if (list.size() >= 2) { - const auto it = shell_colors.find(list[1]); - - if (it != shell_colors.end()) - seq.push_back(std::to_string(it->second + 10)); - else - return ""; - } - - std::ostringstream oss; - - oss << "\033["; - oss << string_util::join(seq, ';'); - oss << "m"; - - return oss.str(); -#else - return ""; -#endif -} - -std::string subst_shell(const std::string& command) -{ -#if defined(HAVE_POPEN) - std::unique_ptr<FILE, std::function<int (FILE*)>> fp(popen(command.c_str(), "r"), pclose); - - if (fp == nullptr) - throw std::runtime_error(std::strerror(errno)); - - std::string result; - std::array<char, 128> buffer; - std::size_t n; - - while ((n = std::fread(buffer.data(), 1, 128, fp.get())) > 0) - result.append(buffer.data(), n); - if (std::ferror(fp.get())) - throw std::runtime_error(std::strerror(errno)); - - // Erase final '\n'. - auto it = result.find('\n'); - if (it != std::string::npos) - result.erase(it); - - return result; -#else - throw std::runtime_error("shell template not available"); -#endif -} - -std::string substitute(std::string::const_iterator& it, - std::string::const_iterator& end, - char token, - const subst& params) -{ - assert(is_reserved(token)); - - std::string content, value; - - if (it == end) - return ""; - - while (it != end && *it != '}') - content += *it++; - - if (it == end || *it != '}') - throw std::invalid_argument("unclosed "s + token + " construct"s); - - it++; - - // Create default original value if flag is disabled. - value = std::string(1, token) + "{"s + content + "}"s; - - switch (token) { - case '#': - if ((params.flags & subst_flags::keywords) == subst_flags::keywords) - value = subst_keywords(content, params); - break; - case '$': - if ((params.flags & subst_flags::env) == subst_flags::env) - value = subst_env(content); - break; - case '@': - if ((params.flags & subst_flags::irc_attrs) == subst_flags::irc_attrs) - value = subst_irc_attrs(content); - else if ((params.flags & subst_flags::shell_attrs) == subst_flags::shell_attrs) - value = subst_shell_attrs(content); - break; - case '!': - if ((params.flags & subst_flags::shell) == subst_flags::shell) - value = subst_shell(content); - break; - default: - break; - } - - return value; -} - -} // !namespace - -std::string format(std::string text, const subst& params) -{ - /* - * Change the date format before anything else to avoid interpolation with - * keywords and user input. - */ - if ((params.flags & subst_flags::date) == subst_flags::date) - text = subst_date(text, params); - - std::ostringstream oss; - - for (auto it = text.cbegin(), end = text.cend(); it != end; ) { - auto token = *it; - - // Is the current character a reserved token or not? - if (!is_reserved(token)) { - oss << *it++; - continue; - } - - // The token was at the end, just write it and return now. - if (++it == end) { - oss << token; - continue; - } - - // The token is declaring a template variable, substitute it. - if (*it == '{') { - oss << substitute(++it, end, token, params); - continue; - } - - /* - * If the next token is different from the previous one, just let the - * next iteration parse the string because we can have the following - * constructs. - * - * "@#{var}" -> "@value" - */ - if (*it != token) { - oss << token; - continue; - } - - /* - * Write the token only if it's not a variable because at this step we - * may have the following constructs. - * - * "##" -> "##" - * "##hello" -> "##hello" - * "##{hello}" -> "#{hello}" - */ - if (++it == end) - oss << token << token; - else if (*it == '{') - oss << token; - } - - return oss.str(); -} - -// }}} - -// {{{ strip - -std::string strip(std::string str) noexcept -{ - const auto test = [] (auto c) { return !std::isspace(c); }; - - str.erase(str.begin(), std::find_if(str.begin(), str.end(), test)); - str.erase(std::find_if(str.rbegin(), str.rend(), test).base(), str.end()); - - return str; -} - -// }}} - -// {{{ split - -std::vector<std::string> split(const std::string& list, const std::string& delimiters, int max) -{ - std::vector<std::string> result; - std::size_t next = -1, current; - int count = 1; - bool finished = false; - - if (list.empty()) - return result; - - do { - std::string val; - - current = next + 1; - next = list.find_first_of(delimiters, current); - - // split max, get until the end. - if (max >= 0 && count++ >= max) { - val = list.substr(current, std::string::npos); - finished = true; - } else { - val = list.substr(current, next - current); - finished = next == std::string::npos; - } - - result.push_back(val); - } while (!finished); - - return result; -} - -// }}} - -// {{{ is_boolean - -bool is_boolean(std::string value) noexcept -{ - std::transform(value.begin(), value.end(), value.begin(), [] (auto c) { - return toupper(c); - }); - - return value == "1" || value == "YES" || value == "TRUE" || value == "ON"; -} - -// }}} - -} // !string_util - -} // !util
--- a/libcommon/irccd/string_util.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,411 +0,0 @@ -/* - * string_util.hpp -- string utilities - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_STRING_UTIL_HPP -#define IRCCD_COMMON_STRING_UTIL_HPP - -/** - * \file string_util.hpp - * \brief String utilities. - */ - -#include "sysconfig.hpp" - -#include <ctime> -#include <initializer_list> -#include <limits> -#include <regex> -#include <sstream> -#include <stdexcept> -#include <string> -#include <type_traits> -#include <unordered_map> - -#include <boost/format.hpp> -#include <boost/optional.hpp> - -namespace irccd { - -/** - * \file string_util.hpp - * \brief String utilities. - */ -namespace string_util { - -// {{{ subst - -/** - * \brief Disable or enable some features. - */ -enum class subst_flags : unsigned { - date = (1 << 0), //!< date templates - keywords = (1 << 1), //!< keywords - env = (1 << 2), //!< environment variables - shell = (1 << 3), //!< command line command - irc_attrs = (1 << 4), //!< IRC escape codes - shell_attrs = (1 << 5) //!< shell attributes -}; - -/** - * \cond ENUM_HIDDEN_SYMBOLS - */ - -inline subst_flags operator^(subst_flags v1, subst_flags v2) noexcept -{ - return static_cast<subst_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2)); -} - -inline subst_flags operator&(subst_flags v1, subst_flags v2) noexcept -{ - return static_cast<subst_flags>(static_cast<unsigned>(v1)& static_cast<unsigned>(v2)); -} - -inline subst_flags operator|(subst_flags v1, subst_flags v2) noexcept -{ - return static_cast<subst_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2)); -} - -inline subst_flags operator~(subst_flags v) noexcept -{ - return static_cast<subst_flags>(~static_cast<unsigned>(v)); -} - -inline subst_flags& operator|=(subst_flags& v1, subst_flags v2) noexcept -{ - v1 = static_cast<subst_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2)); - - return v1; -} - -inline subst_flags& operator&=(subst_flags& v1, subst_flags v2) noexcept -{ - v1 = static_cast<subst_flags>(static_cast<unsigned>(v1)& static_cast<unsigned>(v2)); - - return v1; -} - -inline subst_flags& operator^=(subst_flags& v1, subst_flags v2) noexcept -{ - v1 = static_cast<subst_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2)); - - return v1; -} - -/** - * \endcond - */ - -/** - * \brief Used for format() function. - */ -class subst { -public: - /** - * Flags for selecting templates. - */ - subst_flags flags{ - subst_flags::date | - subst_flags::keywords | - subst_flags::env | - subst_flags::irc_attrs - }; - - /** - * Fill that field if you want a date. - */ - std::time_t time{std::time(nullptr)}; - - /** - * Fill that map if you want to replace keywords. - */ - std::unordered_map<std::string, std::string> keywords; -}; - -/** - * Format a string and update all templates. - * - * ## Syntax - * - * The syntax is <strong>?{}</strong> where <strong>?</strong> is replaced by - * one of the token defined below. Braces are mandatory and cannot be ommited. - * - * To write a literal template construct, prepend the token twice. - * - * ## Availables templates - * - * The following templates are available: - * - * - <strong>\#{name}</strong>: name will be substituted from the keywords in - * params, - * - <strong>\${name}</strong>: name will be substituted from the environment - * variable, - * - <strong>\@{attributes}</strong>: the attributes will be substituted to IRC - * or shell colors (see below), - * - <strong>%</strong>, any format accepted by strftime(3). - * - * ## Attributes - * - * The attribute format is composed of three parts, foreground, background and - * modifiers, each separated by a comma. - * - * **Note:** you cannot omit parameters, to specify the background, you must - * specify the foreground. - * - * ## Examples - * - * ### Valid constructs - * - * - <strong>\#{target}, welcome</strong>: if target is set to "irccd", - * becomes "irccd, welcome", - * - <strong>\@{red}\#{target}</strong>: if target is specified, it is written - * in red, - * - * ### Invalid or literals constructs - * - * - <strong>\#\#{target}</strong>: will output "\#{target}", - * - <strong>\#\#</strong>: will output "\#\#", - * - <strong>\#target</strong>: will output "\#target", - * - <strong>\#{target</strong>: will throw std::invalid_argument. - * - * ### Colors & attributes - * - * - <strong>\@{red,blue}</strong>: will write text red on blue background, - * - <strong>\@{default,yellow}</strong>: will write default color text on - * yellow background, - * - <strong>\@{white,black,bold,underline}</strong>: will write white text on - * black in both bold and underline. - */ -std::string format(std::string text, const subst& params = {}); - -// }}} - -// {{{ strip - -/** - * Remove leading and trailing spaces. - * - * \param str the string - * \return the removed white spaces - */ -std::string strip(std::string str) noexcept; - -// }}} - -// {{{ split - -/** - * Split a string by delimiters. - * - * \param list the string to split - * \param delimiters a list of delimiters - * \param max max number of split - * \return a list of string splitted - */ -std::vector<std::string> split(const std::string& list, const std::string& delimiters, int max = -1); - -// }}} - -// {{{ join - -/** - * Join values by a separator and return a string. - * - * \param first the first iterator - * \param last the last iterator - * \param delim the optional delimiter - * \return the string - */ -template <typename InputIt, typename DelimType = char> -std::string join(InputIt first, InputIt last, DelimType delim = ':') -{ - std::ostringstream oss; - - if (first != last) { - oss << *first; - - while (++first != last) - oss << delim << *first; - } - - return oss.str(); -} - -/** - * Overloaded function that takes a container. - * - * \param c the container - * \param delim the optional delimiter - * \return the string - */ -template <typename Container, typename DelimType = char> -std::string join(const Container& c, DelimType delim = ':') -{ - return join(c.begin(), c.end(), delim); -} - -/** - * Convenient overload. - * - * \param list the initializer list - * \param delim the delimiter - * \return the string - */ -template <typename T, typename DelimType = char> -inline std::string join(std::initializer_list<T> list, DelimType delim = ':') -{ - return join(list.begin(), list.end(), delim); -} - -// }}} - -// {{{ is_identifier - -/** - * Check if a string is a valid irccd identifier. - * - * \param name the identifier name - * \return true if is valid - */ -inline bool is_identifier(const std::string& name) -{ - return std::regex_match(name, std::regex("[A-Za-z0-9-_]+")); -} - -// }}} - -// {{{ is_boolean - -/** - * Check if the value is a boolean, 1, yes and true are accepted. - * - * \param value the value - * \return true if is boolean - * \note this function is case-insensitive - */ -bool is_boolean(std::string value) noexcept; - -// }}} - -// {{{ sprintf - -/** - * \cond HIDDEN_SYMBOLS - */ - -namespace detail { - -inline void sprintf(boost::format&) -{ -} - -template <typename Arg, typename... Args> -inline void sprintf(boost::format& fmter, const Arg& arg, const Args&... args) -{ - fmter % arg; - sprintf(fmter, args...); -} - -} // !detail - -/** - * \endcond - */ - -/** - * Convenient wrapper arount boost::format in sprintf style. - * - * This is identical as calling boost::format(format) % arg1 % arg2 % argN. - * - * \param format the format string - * \param args the arguments - * \return the string - */ -template <typename Format, typename... Args> -std::string sprintf(const Format& format, const Args&... args) -{ - boost::format fmter(format); - - detail::sprintf(fmter, args...); - - return fmter.str(); -} - -// }}} - -// {{{ to_int - -/** - * Convert the given string into a signed integer. - * - * \param str the string to convert - * \param min the minimum value allowed - * \param max the maximum value allowed - * \return the value or boost::none if not convertible - */ -template <typename T = int> -boost::optional<T> to_int(const std::string& str, - T min = std::numeric_limits<T>::min(), - T max = std::numeric_limits<T>::max()) noexcept -{ - static_assert(std::is_signed<T>::value, "must be signed"); - - char* end; - auto v = std::strtoll(str.c_str(), &end, 10); - - if (*end != '\0' || v < min || v > max) - return boost::none; - - return static_cast<T>(v); -} - -// }}} - -// {{{ to_uint - -/** - * Convert the given string into a unsigned integer. - * - * \note invalid numbers are valid as well - * \param str the string to convert - * \param min the minimum value allowed - * \param max the maximum value allowed - * \return the value or boost::none if not convertible - */ -template <typename T = unsigned> -boost::optional<T> to_uint(const std::string& str, - T min = std::numeric_limits<T>::min(), - T max = std::numeric_limits<T>::max()) noexcept -{ - static_assert(std::is_unsigned<T>::value, "must be unsigned"); - - char* end; - auto v = std::strtoull(str.c_str(), &end, 10); - - if (*end != '\0' || v < min || v > max) - return boost::none; - - return static_cast<T>(v); -} - -// }}} - -} // !string_util - -} // !irccd - -#endif // !IRCCD_COMMON_STRING_UTIL_HPP
--- a/libcommon/irccd/system.cpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,502 +0,0 @@ -/* - * system.cpp -- platform dependent functions for system inspection - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cassert> -#include <cerrno> -#include <cstdlib> -#include <cstring> -#include <ctime> -#include <stdexcept> -#include <string> - -#include <boost/dll.hpp> -#include <boost/filesystem.hpp> -#include <boost/predef/os.h> - -#include "sysconfig.hpp" - -#if BOOST_OS_WINDOWS -# include <sys/timeb.h> -# include <shlobj.h> -#else -# include <sys/utsname.h> -# include <sys/types.h> -# include <sys/param.h> -# include <sys/time.h> -# include <unistd.h> -#endif - -#if BOOST_OS_LINUX -# include <sys/sysinfo.h> -#endif - -#if BOOST_OS_MACOS -# include <sys/sysctl.h> -# include <libproc.h> -#endif - -#if defined(HAVE_GETLOGIN) -# include <unistd.h> -#endif - -#include "system.hpp" -#include "string_util.hpp" -#include "xdg.hpp" - -namespace irccd { - -namespace sys { - -namespace { - -// {{{ base_directory - -/* - * base_directory - * ------------------------------------------------------------------ - * - * Get the base program directory. - * - * If irccd has been compiled with relative paths, the base directory is - * evaluated by climbing the `bindir' directory from the executable path. - * - * Otherwise, use the installation prefix. - */ -boost::filesystem::path base_directory() -{ - static const boost::filesystem::path bindir(CMAKE_INSTALL_BINDIR); - static const boost::filesystem::path prefix(CMAKE_INSTALL_PREFIX); - - boost::filesystem::path path("."); - - if (bindir.is_relative()) { - try { - path = boost::dll::program_location(); - path = path.parent_path(); - } catch (...) { - path = "."; - } - - // Compute relative base directory. - for (auto len = std::distance(bindir.begin(), bindir.end()); len > 0; len--) - path = path.parent_path(); - if (path.empty()) - path = "."; - } else - path = prefix; - - return path; -} - -// }}} - -// {{{ system_directory - -/* - * system_directory - * ------------------------------------------------------------------ - * - * Compute the system directory path for the given component. - * - * Referenced by: - * - cachedir, - * - datadir, - * - sysconfigdir, - * - plugindir. - */ -boost::filesystem::path system_directory(const std::string& component) -{ - boost::filesystem::path path(component); - - if (path.is_relative()) - path = base_directory() / component; - - return path.string(); -} - -// }}} - -// {{{ user_config_directory - -/* - * user_config_directory - * ------------------------------------------------------------------ - * - * Get user configuration directory. - * - * Referenced by: config_filenames. - * Requires: - * - Windows: - * - <shlobj.h> - */ -boost::filesystem::path user_config_directory() -{ - boost::filesystem::path path; - -#if BOOST_OS_WINDOWS - char folder[MAX_PATH] = {0}; - - if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, folder) == S_OK) { - path /= folder; - path /= "\\irccd\\config"; - } else - path = "."; -#else - try { - path = xdg().config_home(); - } catch (...) { - path = sys::env("HOME"); - path /= ".config"; - } - - path /= "irccd"; -#endif - - return path; -} - -// }}} - -// {{{ user_plugin_directory - -/* - * user_plugin_directory - * ------------------------------------------------------------------ - * - * Referenced by: plugin_filenames. - * Requires: - * - Windows: - * - <shlobj.h> - * - * Like add user_config_directory but for plugins. - */ -boost::filesystem::path user_plugin_directory() -{ - boost::filesystem::path path; - -#if BOOST_OS_WINDOWS - char folder[MAX_PATH] = {0}; - - if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, folder) == S_OK) { - path /= folder; - path /= "\\irccd\\share"; - } -#else - try { - path = xdg().data_home(); - } catch (...) { - path = sys::env("HOME"); - path /= ".local/share"; - } - - path /= "irccd"; -#endif - - return path / "plugins"; -} - -// }}} - -} // !namespace - -// {{{ set_program_name - -void set_program_name(std::string name) noexcept -{ -#if defined(HAVE_SETPROGNAME) - static std::string save = name; - - setprogname(save.c_str()); -#else - (void)name; -#endif -} - -// }}} - -// {{{ name - -std::string name() -{ -#if BOOST_OS_LINUX - return "Linux"; -#elif BOOST_OS_WINDOWS - return "Windows"; -#elif BOOST_OS_BSD_FREE - return "FreeBSD"; -#elif BOOST_OS_BSD_DRAGONFLY - return "DragonFlyBSD"; -#elif BOOST_OS_BSD_OPEN - return "OpenBSD"; -#elif BOOST_OS_BSD_NET - return "NetBSD"; -#elif BOOST_OS_MACOS - return "macOS"; -#elif BOOST_OS_ANDROID - return "Android"; -#elif BOOST_OS_AIX - return "Aix"; -#elif BOOST_OS_HAIKU - return "Haiku"; -#elif BOOST_OS_IOS - return "iOS"; -#elif BOOST_OS_SOLARIS - return "Solaris"; -#else - return "Unknown"; -#endif -} - -// }}} - -// {{{ version - -/* - * Requires: - * - Windows: - * - <windows.h> - * - Others: - * - <sys/utsname.h> - */ -std::string version() -{ -#if BOOST_OS_WINDOWS - const auto version = GetVersion(); - const auto major = (DWORD)(LOBYTE(LOWORD(version))); - const auto minor = (DWORD)(HIBYTE(LOWORD(version))); - - return std::to_string(major) + "." + std::to_string(minor); -#else - struct utsname uts; - - if (::uname(&uts) < 0) - throw std::runtime_error(std::strerror(errno)); - - return std::string(uts.release); -#endif -} - -// }}} - -// {{{ uptime - -/* - * Requires: - * - Windows: - * - <windows.h> - * - Linux: - * - <sys/sysinfo.h> - * - Mac: - * - <sys/types.h> - * - <sys/sysctl.h> - * - Others: - * - <ctime> - */ -std::uint64_t uptime() -{ -#if BOOST_OS_WINDOWS - return ::GetTickCount64() / 1000; -#elif BOOST_OS_LINUX - struct sysinfo info; - - if (sysinfo(&info) < 0) - throw std::runtime_error(std::strerror(errno)); - - return info.uptime; -#elif BOOST_OS_MACOS - struct timeval boottime; - size_t length = sizeof (boottime); - int mib[2] = { CTL_KERN, KERN_BOOTTIME }; - - if (sysctl(mib, 2, &boottime, &length, nullptr, 0) < 0) - throw std::runtime_error(std::strerror(errno)); - - time_t bsec = boottime.tv_sec, csec = time(nullptr); - - return difftime(csec, bsec); -#else - struct timespec ts; - - if (clock_gettime(CLOCK_UPTIME, &ts) < 0) - throw std::runtime_error(std::strerror(errno)); - - return ts.tv_sec; -#endif -} - -// }}} - -// {{{ ticks - -/* - * Requires: - * - Windows: - * - <sys/timeb.h> - * - Others: - * - <sys/times.h> - */ -std::uint64_t ticks() -{ -#if BOOST_OS_WINDOWS - _timeb tp; - - _ftime(&tp); - - return tp.time * 1000LL + tp.millitm; -#else - struct timeval tp; - - gettimeofday(&tp, NULL); - - return tp.tv_sec * 1000LL + tp.tv_usec / 1000; -#endif -} - -// }}} - -// {{{ home - -/* - * Requires: - * - Windows: - * - <shlobj.h> - */ -std::string home() -{ -#if BOOST_OS_WINDOWS - char path[MAX_PATH]; - - if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path) != S_OK) - return ""; - - return std::string(path); -#else - return env("HOME"); -#endif -} - -// }}} - -// {{{ env - -/* - * Requires: - * - <cstdlib> - */ -std::string env(const std::string& var) -{ - const auto value = std::getenv(var.c_str()); - - if (value == nullptr) - return ""; - - return value; -} - -// }}} - -// {{{ cachedir - -boost::filesystem::path cachedir() -{ - return system_directory(CMAKE_INSTALL_LOCALSTATEDIR) / "cache/irccd"; -} - -// }}} - -// {{{ datadir - -boost::filesystem::path datadir() -{ - return system_directory(CMAKE_INSTALL_DATADIR); -} - -// }}} - -// {{{ sysconfdir - -boost::filesystem::path sysconfdir() -{ - return system_directory(CMAKE_INSTALL_SYSCONFDIR) / "irccd"; -} - -// }}} - -// {{{ plugindir - -boost::filesystem::path plugindir() -{ - return system_directory(CMAKE_INSTALL_LIBDIR) / "irccd"; -} - -// }}} - -// {{{ username - -/* - * Requires: - * - <unistd.h> - */ -std::string username() -{ -#if defined(HAVE_GETLOGIN) - auto v = getlogin(); - - if (v) - return v; -#endif - - return ""; -} - -// }}} - -// {{{ config_filenames - -std::vector<std::string> config_filenames(std::string file) -{ - return { - (user_config_directory() / file).string(), - (sysconfdir() / file).string() - }; -} - -// }}} - -// {{{ plugin_filenames - -std::vector<std::string> plugin_filenames(const std::string& name, - const std::vector<std::string>& extensions) -{ - assert(!extensions.empty()); - - std::vector<std::string> result; - - for (const auto& ext : extensions) - result.push_back((user_plugin_directory() / (name + ext)).string()); - for (const auto& ext : extensions) - result.push_back((plugindir() / (name + ext)).string()); - - return result; -} - -// }}} - -} // !sys - -} // !irccd
--- a/libcommon/irccd/system.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +0,0 @@ -/* - * system.hpp -- platform dependent functions for system inspection - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_SYSTEM_HPP -#define IRCCD_COMMON_SYSTEM_HPP - -/** - * \file system.hpp - * \brief System dependant functions - */ - -#include <cstdint> -#include <string> -#include <vector> - -#include <boost/filesystem.hpp> - -#include "sysconfig.hpp" - -namespace irccd { - -/** - * \brief Namespace for system functions. - */ -namespace sys { - -/** - * Set the program name, needed for some functions or some systems. - * - * \param name the program name - */ -void set_program_name(std::string name) noexcept; - -/** - * Get the system name. - * - * \return the name - */ -std::string name(); - -/** - * Get the system version. - * - * \return the version - */ -std::string version(); - -/** - * Get the number of seconds elapsed since the boottime. - * - * \return the number of seconds - */ -std::uint64_t uptime(); - -/** - * Get the milliseconds elapsed since the application - * startup. - * - * \return the milliseconds - */ -std::uint64_t ticks(); - -/** - * Get an environment variable. - * - * \return the value or empty string - */ -std::string env(const std::string& var); - -/** - * Get home directory usually /home/foo - * - * \return the home directory - */ -std::string home(); - -/** - * Get the cache directory as specified as compile time option - * CMAKE_INSTALL_LOCALSTATEDIR, if the value is absolute, it is returned as-is. - * - * If the component is relative, it is evaluated using the binary executable - * path. - * - * \return the evaluated cache directory. - * \see datadir - * \see configdir - */ -boost::filesystem::path cachedir(); - -/** - * Like cachedir but for CMAKE_INSTALL_DATADIR. - * - * \return the evaluated data directory. - * \see cachedir - * \see datadir - */ -boost::filesystem::path datadir(); - -/** - * Like cachedir but for CMAKE_INSTALL_SYSCONFDIR. - * - * \return the evaluated config directory. - * \see cachedir - * \see datadir - * \note use config_filenames for irccd.conf, irccdctl.conf files - */ - boost::filesystem::path sysconfdir(); - -/** - * Like cachedir but for CMAKE_INSTALL_LIBDIR. - * - * \return the evaluated system plugin directory. - * \see cachedir - * \see datadir - */ -boost::filesystem::path plugindir(); - -/** - * Get user account login or empty if not available. - * - * \return the user account name - */ -std::string username(); - -/** - * Construct a list of paths to read configuration files from. - * - * This function does not test the presence of the files as a condition race - * may occur. - * - * The caller is responsible of opening files for each path. - * - * \param file the filename to append for convenience - * \return the list of paths to check in order - */ -std::vector<std::string> config_filenames(std::string file); - -/** - * Construct a list of paths for reading plugins. - * - * \param name the plugin id (without extension) - * \param extensions the list of extensions supported - */ -std::vector<std::string> plugin_filenames(const std::string& name, - const std::vector<std::string>& extensions); - -} // !sys - -} // !irccd - -#endif // !IRCCD_COMMON_SYSTEM_HPP
--- a/libcommon/irccd/tls_acceptor.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -/* - * tls_acceptor.hpp -- TLS/SSL acceptors - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_TLS_ACCEPTOR_HPP -#define IRCCD_COMMON_TLS_ACCEPTOR_HPP - -/** - * \file tls_acceptor.hpp - * \brief TLS/SSL acceptors. - */ - -#include <irccd/sysconfig.hpp> - -#if defined(IRCCD_HAVE_SSL) - -#include "socket_acceptor.hpp" -#include "tls_stream.hpp" - -namespace irccd { - -namespace io { - -/** - * \brief TLS/SSL acceptors. - * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) - */ -template <typename Protocol = boost::asio::ip::tcp> -class tls_acceptor : public socket_acceptor<Protocol> { -private: - using socket = typename Protocol::socket; - - boost::asio::ssl::context context_; - -public: - /** - * Construct a secure layer transport server. - * - * \param context the SSL context - * \param args the socket_acceptor arguments - */ - template <typename... Args> - inline tls_acceptor(boost::asio::ssl::context context, Args&&... args) - : socket_acceptor<Protocol>(std::forward<Args>(args)...) - , context_(std::move(context)) - { - } - - /** - * \copydoc acceptor::accept - */ - void accept(accept_handler handler) override; -}; - -template <typename Protocol> -void tls_acceptor<Protocol>::accept(accept_handler handler) -{ - assert(handler); - - auto client = std::make_shared<tls_stream<socket>>(this->get_acceptor().get_io_service(), this->context_); - - socket_acceptor<Protocol>::do_accept(client->get_socket().lowest_layer(), [handler, client] (auto code) { - using boost::asio::ssl::stream_base; - - if (code) { - handler(code, nullptr); - return; - } - - client->get_socket().async_handshake(stream_base::server, [handler, client] (auto code) { - handler(detail::convert(code), code ? nullptr : std::move(client)); - }); - }); -} - -} // !io - -} // !irccd - -#endif // !IRCCD_HAVE_SSL - -#endif // !IRCCD_COMMON_TLS_ACCEPTOR_HPP
--- a/libcommon/irccd/tls_connector.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -/* - * tls_connector.hpp -- TLS/SSL connectors - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_TLS_CONNECTOR_HPP -#define IRCCD_COMMON_TLS_CONNECTOR_HPP - -/** - * \file tls_connector.hpp - * \brief TLS/SSL connectors. - */ - -#include <irccd/sysconfig.hpp> - -#if defined(IRCCD_HAVE_SSL) - -#include "socket_connector.hpp" -#include "tls_stream.hpp" - -namespace irccd { - -namespace io { - -/** - * \brief TLS/SSL connectors. - * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) - */ -template <typename Protocol = boost::asio::ip::tcp> -class tls_connector : public socket_connector<Protocol> { -private: - boost::asio::ssl::context context_; - -public: - /** - * Construct a secure layer transport server. - * - * \param context the SSL context - * \param args the arguments to socket_connector<Socket> constructor - */ - template <typename... Args> - inline tls_connector(boost::asio::ssl::context context, Args&&... args) - : socket_connector<Protocol>(std::forward<Args>(args)...) - , context_(std::move(context)) - { - } - - /** - * \copydoc socket_connector::connect - */ - void connect(connect_handler handler) override; -}; - -template <typename Protocol> -void tls_connector<Protocol>::connect(connect_handler handler) -{ - using boost::asio::ssl::stream_base; - using socket = typename Protocol::socket; - - assert(handler); - - const auto stream = std::make_shared<tls_stream<socket>>(this->get_io_service(), context_); - - socket_connector<Protocol>::do_connect(stream->get_socket().lowest_layer(), [this, handler, stream] (auto code) { - if (code) { - handler(code, nullptr); - return; - } - - stream->get_socket().async_handshake(stream_base::client, [handler, stream] (auto code) { - handler(detail::convert(code), code ? nullptr : std::move(stream)); - }); - }); -} - -} // !io - -} // !irccd - -#endif // !IRCCD_HAVE_SSL - -#endif // !IRCCD_COMMON_TLS_CONNECTOR_HPP
--- a/libcommon/irccd/tls_stream.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -/* - * tls_stream.hpp -- TLS/SSL streams - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_COMMON_TLS_STREAM_HPP -#define IRCCD_COMMON_TLS_STREAM_HPP - -/** - * \file tls_stream.hpp - * \brief TLS/SSL streams. - */ - -#include <irccd/sysconfig.hpp> - -#if defined(IRCCD_HAVE_SSL) - -#include <boost/asio/ssl.hpp> - -#include "socket_stream.hpp" - -namespace irccd { - -namespace io { - -/** - * \brief TLS/SSL streams. - * \tparam Socket the Boost.Asio compatible socket. - */ -template <typename Socket = boost::asio::ip::tcp::socket> -class tls_stream : public socket_stream<boost::asio::ssl::stream<Socket>> { -public: - /** - * Constructor. - * - * \param args the arguments to boost::asio::ssl::stream<Socket> - */ - template <typename... Args> - inline tls_stream(Args&&... args) - : socket_stream<boost::asio::ssl::stream<Socket>>(std::forward<Args>(args)...) - { - } -}; - -} // !io - -} // !irccd - -#endif // !IRCCD_HAVE_SSL - -#endif // !IRCCD_COMMON_TLS_STREAM_HPP
--- a/libcommon/irccd/xdg.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -/* - * xdg.hpp -- XDG directory specifications - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_XDG_HPP -#define IRCCD_XDG_HPP - -/** - * \file xdg.hpp - * \brief XDG directory specifications. - * \author David Demelier <markand@malikana.fr> - */ - -#include <cstdlib> -#include <sstream> -#include <stdexcept> -#include <string> -#include <vector> - -namespace irccd { - -/** - * \brief XDG directory specifications. - * - * Read and get XDG directories. - * - * This file should compiles on Windows to facilitate portability but its - * functions must not be used. - */ -class xdg { -private: - std::string config_home_; - std::string data_home_; - std::string cache_home_; - std::string runtime_dir_; - std::vector<std::string> config_dirs_; - std::vector<std::string> data_dirs_; - - inline bool is_absolute(const std::string& path) const noexcept - { - return path.length() > 0 && path[0] == '/'; - } - - std::vector<std::string> split(const std::string& arg) const - { - std::stringstream iss(arg); - std::string item; - std::vector<std::string> elems; - - while (std::getline(iss, item, ':')) { - if (is_absolute(item)) - elems.push_back(item); - } - - return elems; - } - - std::string env_or_home(const std::string& var, const std::string& repl) const - { - auto value = std::getenv(var.c_str()); - - if (value == nullptr || !is_absolute(value)) { - auto home = std::getenv("HOME"); - - if (home == nullptr) - throw std::runtime_error("could not get home directory"); - - return std::string(home) + "/" + repl; - } - - return value; - } - - std::vector<std::string> list_or_defaults(const std::string& var, - const std::vector<std::string>& list) const - { - auto value = std::getenv(var.c_str()); - - if (!value) - return list; - - // No valid item at all? Use defaults. - auto result = split(value); - - return (result.size() == 0) ? list : result; - } - -public: - /** - * Open an xdg instance and load directories. - * - * \throw std::runtime_error on failures - */ - xdg() - { - config_home_ = env_or_home("XDG_CONFIG_HOME", ".config"); - data_home_ = env_or_home("XDG_DATA_HOME", ".local/share"); - cache_home_ = env_or_home("XDG_CACHE_HOME", ".cache"); - - config_dirs_ = list_or_defaults("XDG_CONFIG_DIRS", { "/etc/xdg" }); - data_dirs_ = list_or_defaults("XDG_DATA_DIRS", { "/usr/local/share", "/usr/share" }); - - /* - * Runtime directory is a special case and does not have a replacement, - * the application should manage this by itself. - */ - auto runtime = std::getenv("XDG_RUNTIME_DIR"); - - if (runtime && is_absolute(runtime)) - runtime_dir_ = runtime; - } - - /** - * Get the config directory. ${XDG_CONFIG_HOME} or ${HOME}/.config - * - * \return the config directory - */ - inline const std::string& config_home() const noexcept - { - return config_home_; - } - - /** - * Get the data directory. ${XDG_DATA_HOME} or ${HOME}/.local/share - * - * \return the data directory - */ - inline const std::string& data_home() const noexcept - { - return data_home_; - } - - /** - * Get the cache directory. ${XDG_CACHE_HOME} or ${HOME}/.cache - * - * \return the cache directory - */ - inline const std::string& cache_home() const noexcept - { - return cache_home_; - } - - /** - * Get the runtime directory. - * - * There is no replacement for XDG_RUNTIME_DIR, if it is not set, an empty - * value is returned and the user is responsible of using something else. - * - * \return the runtime directory - */ - inline const std::string& runtime_dir() const noexcept - { - return runtime_dir_; - } - - /** - * Get the standard config directories. ${XDG_CONFIG_DIRS} or { "/etc/xdg" } - * - * \return the list of config directories - */ - inline const std::vector<std::string>& config_dirs() const noexcept - { - return config_dirs_; - } - - /** - * Get the data directories. ${XDG_DATA_DIRS} or { "/usr/local/share", - * "/usr/share" } - * - * \return the list of data directories - */ - inline const std::vector<std::string>& data_dirs() const noexcept - { - return data_dirs_; - } -}; - -} // !irccd - -#endif // !IRCCD_XDG_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,79 @@ +# +# CMakeLists.txt -- CMake build system for irccd +# +# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +project(libirccd-core) + +find_package(Boost 1.60 REQUIRED QUIET COMPONENTS filesystem system) + +set( + HEADERS + ${libirccd-core_SOURCE_DIR}/irccd/acceptor.hpp + ${libirccd-core_SOURCE_DIR}/irccd/config.hpp + ${libirccd-core_SOURCE_DIR}/irccd/connector.hpp + ${libirccd-core_SOURCE_DIR}/irccd/fs_util.hpp + ${libirccd-core_SOURCE_DIR}/irccd/ini.hpp + ${libirccd-core_SOURCE_DIR}/irccd/ini_util.hpp + ${libirccd-core_SOURCE_DIR}/irccd/json_util.hpp + ${libirccd-core_SOURCE_DIR}/irccd/options.hpp + ${libirccd-core_SOURCE_DIR}/irccd/socket_acceptor.hpp + ${libirccd-core_SOURCE_DIR}/irccd/socket_connector.hpp + ${libirccd-core_SOURCE_DIR}/irccd/socket_stream.hpp + ${libirccd-core_SOURCE_DIR}/irccd/stream.hpp + ${libirccd-core_SOURCE_DIR}/irccd/string_util.hpp + ${libirccd-core_SOURCE_DIR}/irccd/system.hpp + ${libirccd-core_SOURCE_DIR}/irccd/tls_acceptor.hpp + ${libirccd-core_SOURCE_DIR}/irccd/tls_connector.hpp + ${libirccd-core_SOURCE_DIR}/irccd/tls_stream.hpp + ${libirccd-core_SOURCE_DIR}/irccd/xdg.hpp +) + +set( + SOURCES + ${libirccd-core_SOURCE_DIR}/irccd/config.cpp + ${libirccd-core_SOURCE_DIR}/irccd/ini.cpp + ${libirccd-core_SOURCE_DIR}/irccd/options.cpp + ${libirccd-core_SOURCE_DIR}/irccd/string_util.cpp + ${libirccd-core_SOURCE_DIR}/irccd/system.cpp +) + +irccd_define_library( + TARGET libirccd-core + EXPORT + HEADERS ${HEADERS} + HEADERS_DIRECTORY irccd + SOURCES + ${libirccd-core_SOURCE_DIR}/CMakeLists.txt + ${SOURCES} + LIBRARIES + libjson + Threads::Threads + Boost::filesystem + Boost::system + $<$<BOOL:${IRCCD_HAVE_SSL}>:OpenSSL::Crypto> + $<$<BOOL:${IRCCD_HAVE_SSL}>:OpenSSL::SSL> + $<$<BOOL:${APPLE}>:resolv> + $<$<BOOL:${WIN32}>:mswsock> + $<$<BOOL:${WIN32}>:shlwapi> + $<$<BOOL:${WIN32}>:ws2_32> + $<$<STREQUAL:${CMAKE_SYSTEM_NAME},Linux>:dl> + PUBLIC_INCLUDES + $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}> + $<BUILD_INTERFACE:${libirccd-core_SOURCE_DIR}> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/irccd/extern> +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/acceptor.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,77 @@ +/* + * acceptor.hpp -- abstract stream acceptor interface + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_ACCEPTOR_HPP +#define IRCCD_COMMON_ACCEPTOR_HPP + +/** + * \file acceptor.hpp + * \brief Abstract stream acceptor interface. + */ + +#include <functional> +#include <memory> +#include <system_error> + +namespace irccd { + +namespace io { + +class stream; + +/** + * \brief Accept completion handler. + */ +using accept_handler = std::function<void (std::error_code, std::shared_ptr<stream>)>; + +/** + * \brief Abstract stream acceptor interface. + * + * This class is used to wait a new client in an asynchronous manner. Derived + * classes must implement a non-blocking accept function. + */ +class acceptor { +public: + /** + * Default constructor. + */ + acceptor() = default; + + /** + * Virtual destructor defaulted. + */ + virtual ~acceptor() = default; + + /** + * Start asynchronous accept. + * + * Once the client is accepted, the original acceptor must be kept until it + * is destroyed. + * + * \pre another accept operation must not be running + * \pre handler != nullptr + * \param handler the handler + */ + virtual void accept(accept_handler handler) = 0; +}; + +} // !io + +} // !irccd + +#endif // !IRCCD_COMMON_ACCEPTOR_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/config.cpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,39 @@ +/* + * config.cpp -- irccd configuration loader + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <boost/filesystem.hpp> + +#include <irccd/system.hpp> + +#include "config.hpp" + +namespace irccd { + +boost::optional<config> config::search(const std::string& name) +{ + for (const auto& path : sys::config_filenames(name)) { + boost::system::error_code ec; + + if (boost::filesystem::exists(path, ec) && !ec) + return config(path); + } + + return boost::optional<config>(); +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/config.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,73 @@ +/* + * config.hpp -- irccd configuration loader + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_CONFIG_HPP +#define IRCCD_COMMON_CONFIG_HPP + +/** + * \file config.hpp + * \brief Read .ini configuration file for irccd + */ + +#include <boost/optional.hpp> + +#include "ini.hpp" + +namespace irccd { + +/** + * \brief Read .ini configuration file for irccd + */ +class config : public ini::document { +private: + std::string path_; + +public: + /** + * Search the configuration file into the standard defined paths. + * + * \param name the file name + * \return the config or empty if not found + */ + static boost::optional<config> search(const std::string& name); + + /** + * Load the configuration from the specified path. + * + * \param path the path + */ + inline config(std::string path = "") + : document(path.empty() ? ini::document() : ini::read_file(path)) + , path_(std::move(path)) + { + } + + /** + * Get the path to the configuration file. + * + * \return the path + */ + inline const std::string& get_path() const noexcept + { + return path_; + } +}; + +} // !irccd + +#endif // !IRCCD_COMMON_CONFIG_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/connector.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,79 @@ +/* + * connector.hpp -- abstract connection interface + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_CONNECTOR_HPP +#define IRCCD_COMMON_CONNECTOR_HPP + +/** + * \file connector.hpp + * \brief Abstract connection interface. + */ + +#include <functional> +#include <memory> +#include <system_error> + +namespace irccd { + +namespace io { + +class stream; + +/** + * \brief Connect completion handler. + */ +using connect_handler = std::function<void (std::error_code, std::shared_ptr<stream>)>; + +/** + * \brief Abstract connection interface. + * + * This class is used to connect to a stream end point (usually sockets) in an + * asynchronous manner. + * + * Derived class must implement non-blocking connect function. + */ +class connector { +public: + /** + * Default constructor. + */ + connector() = default; + + /** + * Virtual destructor defaulted. + */ + virtual ~connector() = default; + + /** + * Start asynchronous connect. + * + * Once the client is connected, the original acceptor must be kept until it + * is destroyed. + * + * \pre another connect operation must not be running + * \pre handler != nullptr + * \param handler the handler + */ + virtual void connect(connect_handler handler) = 0; +}; + +} // !io + +} // !irccd + +#endif // !IRCCD_COMMON_CONNECTOR_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/fs_util.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,149 @@ +/* + * fs_util.hpp -- filesystem utilities + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_FS_UTIL_HPP +#define IRCCD_COMMON_FS_UTIL_HPP + +/** + * \file fs_util.hpp + * \brief Filesystem utilities. + */ + +#include <regex> +#include <string> + +#include <boost/filesystem.hpp> + +namespace irccd { + +/** + * \brief Filesystem utilities. + */ +namespace fs_util { + +// {{{ base_name + +/** + * Get the base name from a path. + * + * Example, base_name("/etc/foo.conf") returns foo.conf + * + * \param path the path + * \return the base name + */ +inline std::string base_name(const std::string& path) +{ + return boost::filesystem::path(path).filename().string(); +} + +// }}} + +// {{{ dir_name + +/** + * Get the parent directory from a path. + * + * Example, dir_name("/etc/foo.conf") returns /etc + * + * \param path the path + * \return the parent directory + */ +inline std::string dir_name(const std::string& path) +{ + return boost::filesystem::path(path).parent_path().string(); +} + +// }}} + +// {{{ find_if + +/** + * Search an item recursively. + * + * The predicate must have the following signature: + * void f(const boost::filesystem::directory_entry& entry) + * + * Where: + * - base is the current parent directory in the tree + * - entry is the current entry + * + * \param base the base directory + * \param predicate the predicate + * \param recursive true to do recursive search + * \return the full path name to the file or empty string if never found + * \throw boost::system::system_error on errors + */ +template <typename Predicate> +std::string find_if(const std::string& base, bool recursive, Predicate&& predicate) +{ + const auto find = [&] (auto it) -> std::string { + for (const auto& entry : it) + if (predicate(entry)) + return entry.path().string(); + + return ""; + }; + + return recursive + ? find(boost::filesystem::recursive_directory_iterator(base)) + : find(boost::filesystem::directory_iterator(base)); +} + +// }}} + +// {{{ find + +/** + * Find a file by name recursively. + * + * \param base the base directory + * \param name the file name + * \param recursive true to do recursive search + * \return the full path name to the file or empty string if never found + * \throw boost::system::system_error on errors + */ +inline std::string find(const std::string& base, const std::string& name, bool recursive = false) +{ + return find_if(base, recursive, [&] (const auto& entry) { + return entry.path().filename().string() == name; + }); +} + +/** + * Overload by regular expression. + * + * \param base the base directory + * \param regex the regular expression + * \param recursive true to do recursive search + * \return the full path name to the file or empty string if never found + * \throw boost::system::system_error on errors + */ +inline std::string find(const std::string& base, const std::regex& regex, bool recursive = false) +{ + return find_if(base, recursive, [&] (const auto& entry) { + return std::regex_match(entry.path().filename().string(), regex); + }); +} + +// }}} + +} // !fs_util + +} // !irccd + +#endif // !IRCCD_COMMON_FS_UTIL_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/ini.cpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,418 @@ +/* + * ini.cpp -- extended .ini file parser + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cctype> +#include <cstring> +#include <iostream> +#include <iterator> +#include <fstream> +#include <sstream> +#include <stdexcept> + +#include <irccd/sysconfig.hpp> + +#include <boost/predef.h> + +// for PathIsRelative. +#if BOOST_OS_WINDOWS +# include <shlwapi.h> +#endif + +#include "ini.hpp" + +namespace irccd { + +namespace ini { + +namespace { + +using stream_iterator = std::istreambuf_iterator<char>; +using token_iterator = std::vector<token>::const_iterator; + +inline bool is_absolute(const std::string& path) noexcept +{ +#if BOOST_OS_WINDOWS + return !PathIsRelative(path.c_str()); +#else + return path.size() > 0 && path[0] == '/'; +#endif +} + +inline bool is_quote(char c) noexcept +{ + return c == '\'' || c == '"'; +} + +inline bool is_space(char c) noexcept +{ + // Custom version because std::isspace includes \n as space. + return c == ' ' || c == '\t'; +} + +inline bool is_list(char c) noexcept +{ + return c == '(' || c == ')' || c == ','; +} + +inline bool is_reserved(char c) noexcept +{ + return is_list(c) || is_quote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '='; +} + +void analyse_line(int& line, int& column, stream_iterator& it) noexcept +{ + assert(*it == '\n'); + + ++ line; + ++ it; + column = 0; +} + +void analyse_comment(int& column, stream_iterator& it, stream_iterator end) noexcept +{ + assert(*it == '#'); + + while (it != end && *it != '\n') { + ++ column; + ++ it; + } +} + +void analyse_spaces(int& column, stream_iterator& it, stream_iterator end) noexcept +{ + assert(is_space(*it)); + + while (it != end && is_space(*it)) { + ++ column; + ++ it; + } +} + +void analyse_list(tokens& list, int line, int& column, stream_iterator& it) noexcept +{ + assert(is_list(*it)); + + switch (*it++) { + case '(': + list.emplace_back(token::list_begin, line, column++); + break; + case ')': + list.emplace_back(token::list_end, line, column++); + break; + case ',': + list.emplace_back(token::comma, line, column++); + break; + default: + break; + } +} + +void analyse_section(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end) +{ + assert(*it == '['); + + std::string value; + int save = column; + + // Read section name. + ++ it; + while (it != end && *it != ']') { + if (*it == '\n') + throw exception(line, column, "section not terminated, missing ']'"); + if (is_reserved(*it)) + throw exception(line, column, "section name expected after '[', got '" + std::string(1, *it) + "'"); + + ++ column; + value += *it++; + } + + if (it == end) + throw exception(line, column, "section name expected after '[', got <EOF>"); + if (value.empty()) + throw exception(line, column, "empty section name"); + + // Remove ']'. + ++ it; + + list.emplace_back(token::section, line, save, std::move(value)); +} + +void analyse_assign(tokens& list, int& line, int& column, stream_iterator& it) +{ + assert(*it == '='); + + list.push_back({ token::assign, line, column++ }); + ++ it; +} + +void analyse_quoted_word(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end) +{ + std::string value; + int save = column; + char quote = *it++; + + while (it != end && *it != quote) { + // TODO: escape sequence + ++ column; + value += *it++; + } + + if (it == end) + throw exception(line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>"); + + // Remove quote. + ++ it; + + list.push_back({ token::quoted_word, line, save, std::move(value) }); +} + +void analyse_word(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end) +{ + assert(!is_reserved(*it)); + + std::string value; + int save = column; + + while (it != end && !std::isspace(*it) && !is_reserved(*it)) { + ++ column; + value += *it++; + } + + list.push_back({ token::word, line, save, std::move(value) }); +} + +void analyse_include(tokens& list, int& line, int& column, stream_iterator& it, stream_iterator end) +{ + assert(*it == '@'); + + std::string include; + int save = column; + + // Read include. + ++ it; + while (it != end && !is_space(*it)) { + ++ column; + include += *it++; + } + + if (include != "include") + throw exception(line, column, "expected include after '@' token"); + + list.push_back({ token::include, line, save }); +} + +void parse_option_value_simple(option& option, token_iterator& it) +{ + assert(it->type() == token::word || it->type() == token::quoted_word); + + option.push_back((it++)->value()); +} + +void parse_option_value_list(option& option, token_iterator& it, token_iterator end) +{ + assert(it->type() == token::list_begin); + + token_iterator save = it++; + + while (it != end && it->type() != token::list_end) { + switch (it->type()) { + case token::comma: + // Previous must be a word. + if (it[-1].type() != token::word && it[-1].type() != token::quoted_word) + throw exception(it->line(), it->column(), "unexpected comma after '" + it[-1].value() + "'"); + + ++ it; + break; + case token::word: + case token::quoted_word: + option.push_back((it++)->value()); + break; + default: + throw exception(it->line(), it->column(), "unexpected '" + it[-1].value() + "' in list construct"); + break; + } + } + + if (it == end) + throw exception(save->line(), save->column(), "unterminated list construct"); + + // Remove ). + ++ it; +} + +void parse_option(section& sc, token_iterator& it, token_iterator end) +{ + option option(it->value()); + token_iterator save(it); + + // No '=' or something else? + if (++it == end) + throw exception(save->line(), save->column(), "expected '=' assignment, got <EOF>"); + if (it->type() != token::assign) + throw exception(it->line(), it->column(), "expected '=' assignment, got " + it->value()); + + // Empty options are allowed so just test for words. + if (++it != end) { + if (it->type() == token::word || it->type() == token::quoted_word) + parse_option_value_simple(option, it); + else if (it->type() == token::list_begin) + parse_option_value_list(option, it, end); + } + + sc.push_back(std::move(option)); +} + +void parse_include(document& doc, const std::string& path, token_iterator& it, token_iterator end) +{ + token_iterator save(it); + + if (++it == end) + throw exception(save->line(), save->column(), "expected file name after '@include' statement, got <EOF>"); + if (it->type() != token::word && it->type() != token::quoted_word) + throw exception(it->line(), it->column(), "expected file name after '@include' statement, got " + it->value()); + + std::string value = (it++)->value(); + std::string file; + + if (!is_absolute(value)) { +#if BOOST_OS_WINDOWS + file = path + "\\" + value; +#else + file = path + "/" + value; +#endif + } else + file = value; + + for (const auto& sc : read_file(file)) + doc.push_back(sc); +} + +void parse_section(document& doc, token_iterator& it, token_iterator end) +{ + section sc(it->value()); + + // Skip [section]. + ++ it; + + // Read until next section. + while (it != end && it->type() != token::section) { + if (it->type() != token::word) + throw exception(it->line(), it->column(), "unexpected token '" + it->value() + "' in section definition"); + + parse_option(sc, it, end); + } + + doc.push_back(std::move(sc)); +} + +} // !namespace + +tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end) +{ + tokens list; + int line = 1; + int column = 0; + + while (it != end) { + if (*it == '\n') + analyse_line(line, column, it); + else if (*it == '#') + analyse_comment(column, it, end); + else if (*it == '[') + analyse_section(list, line, column, it, end); + else if (*it == '=') + analyse_assign(list, line, column, it); + else if (is_space(*it)) + analyse_spaces(column, it, end); + else if (*it == '@') + analyse_include(list, line, column, it, end); + else if (is_quote(*it)) + analyse_quoted_word(list, line, column, it, end); + else if (is_list(*it)) + analyse_list(list, line, column, it); + else + analyse_word(list, line, column, it, end); + } + + return list; +} + +tokens analyse(std::istream& stream) +{ + return analyse(std::istreambuf_iterator<char>(stream), {}); +} + +document parse(const tokens& tokens, const std::string& path) +{ + document doc; + token_iterator it = tokens.cbegin(); + token_iterator end = tokens.cend(); + + while (it != end) { + switch (it->type()) { + case token::include: + parse_include(doc, path, it, end); + break; + case token::section: + parse_section(doc, it, end); + break; + default: + throw exception(it->line(), it->column(), "unexpected '" + it->value() + "' on root document"); + } + } + + return doc; +} + +document read_file(const std::string& filename) +{ + // Get parent path. + auto parent = filename; + auto pos = parent.find_last_of("/\\"); + + if (pos != std::string::npos) + parent.erase(pos); + else + parent = "."; + + std::ifstream input(filename); + + if (!input) + throw exception(0, 0, std::strerror(errno)); + + return parse(analyse(input), parent); +} + +document read_string(const std::string& buffer) +{ + std::istringstream iss(buffer); + + return parse(analyse(iss)); +} + +void dump(const tokens& tokens) +{ + for (const token& token: tokens) { + // TODO: add better description + std::cout << token.line() << ":" << token.column() << ": " << token.value() << std::endl; + } +} + +} // !ini + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/ini.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,647 @@ +/* + * ini.hpp -- extended .ini file parser + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_INI_HPP +#define IRCCD_COMMON_INI_HPP + +/** + * \file ini.hpp + * \brief Extended .ini file parser. + * \author David Demelier <markand@malikania.fr> + * \version 2.0.0 + */ + +/** + * \page Ini Ini + * \brief Extended .ini file parser. + * + * - \subpage ini-syntax + */ + +/** + * \page ini-syntax Syntax + * \brief File syntax. + * + * The syntax is similar to most of `.ini` implementations as: + * + * - a section is delimited by `[name]` can be redefined multiple times, + * - an option **must** always be defined in a section, + * - empty options must be surrounded by quotes, + * - lists can not include trailing commas, + * - include statements must always live at the beginning of files + * (in no sections), + * - comments start with # until the end of line, + * - options with spaces **must** use quotes. + * + * # Basic file + * + * ````ini + * # This is a comment. + * [section] + * option1 = value1 + * option2 = "value 2 with spaces" # comment is also allowed here + * ```` + * + * # Redefinition + * + * Sections can be redefined multiple times and are kept the order they are + * seen. + * + * ````ini + * [section] + * value = "1" + * + * [section] + * value = "2" + * ```` + * + * The ini::document object will contains two ini::section. + * + * # Lists + * + * Lists are defined using `()` and commas, like values, they may have quotes. + * + * ````ini + * [section] + * names = ( "x1", "x2" ) + * + * # This is also allowed. + * biglist = ( + * "abc", + * "def" + * ) + * ```` + * + * # Include statement + * + * You can split a file into several pieces, if the include statement contains a + * relative path, the path will be relative to the current file being parsed. + * + * You **must** use the include statement before any section. + * + * If the file contains spaces, use quotes. + * + * ````ini + * # main.conf + * @include "foo.conf" + * + * # foo.conf + * [section] + * option1 = value1 + * ```` + */ + +#include "sysconfig.hpp" + +#include <algorithm> +#include <cassert> +#include <exception> +#include <stdexcept> +#include <string> +#include <vector> + +namespace irccd { + +/** + * Namespace for ini related classes. + */ +namespace ini { + +class document; + +/** + * \brief exception in a file. + */ +class exception : public std::exception { +private: + int line_; + int column_; + std::string message_; + +public: + /** + * Constructor. + * + * \param line the line + * \param column the column + * \param msg the message + */ + inline exception(int line, int column, std::string msg) noexcept + : line_(line) + , column_(column) + , message_(std::move(msg)) + { + } + + /** + * Get the line number. + * + * \return the line + */ + inline int line() const noexcept + { + return line_; + } + + /** + * Get the column number. + * + * \return the column + */ + inline int column() const noexcept + { + return column_; + } + + /** + * Return the raw exception message (no line and column shown). + * + * \return the exception message + */ + const char* what() const noexcept override + { + return message_.c_str(); + } +}; + +/** + * \brief Describe a token read in the .ini source. + * + * This class can be used when you want to parse a .ini file yourself. + * + * \see analyse + */ +class token { +public: + /** + * \brief token type. + */ + enum type { + include, //!< include statement + section, //!< [section] + word, //!< word without quotes + quoted_word, //!< word with quotes + assign, //!< = assignment + list_begin, //!< begin of list ( + list_end, //!< end of list ) + comma //!< list separation + }; + +private: + type type_; + int line_; + int column_; + std::string value_; + +public: + /** + * Construct a token. + * + * \param type the type + * \param line the line + * \param column the column + * \param value the value + */ + token(type type, int line, int column, std::string value = "") noexcept + : type_(type) + , line_(line) + , column_(column) + { + switch (type) { + case include: + value_ = "@include"; + break; + case section: + case word: + case quoted_word: + value_ = value; + break; + case assign: + value_ = "="; + break; + case list_begin: + value_ = "("; + break; + case list_end: + value_ = ")"; + break; + case comma: + value_ = ","; + break; + default: + break; + } + } + + /** + * Get the type. + * + * \return the type + */ + inline type type() const noexcept + { + return type_; + } + + /** + * Get the line. + * + * \return the line + */ + inline int line() const noexcept + { + return line_; + } + + /** + * Get the column. + * + * \return the column + */ + inline int column() const noexcept + { + return column_; + } + + /** + * Get the value. For words, quoted words and section, the value is the + * content. Otherwise it's the characters parsed. + * + * \return the value + */ + inline const std::string& value() const noexcept + { + return value_; + } +}; + +/** + * List of tokens in order they are analyzed. + */ +using tokens = std::vector<token>; + +/** + * \brief option definition. + */ +class option : public std::vector<std::string> { +private: + std::string key_; + +public: + /** + * Construct an empty option. + * + * \pre key must not be empty + * \param key the key + */ + inline option(std::string key) noexcept + : std::vector<std::string>() + , key_(std::move(key)) + { + assert(!key_.empty()); + } + + /** + * Construct a single option. + * + * \pre key must not be empty + * \param key the key + * \param value the value + */ + inline option(std::string key, std::string value) noexcept + : key_(std::move(key)) + { + assert(!key_.empty()); + + push_back(std::move(value)); + } + + /** + * Construct a list option. + * + * \pre key must not be empty + * \param key the key + * \param values the values + */ + inline option(std::string key, std::vector<std::string> values) noexcept + : std::vector<std::string>(std::move(values)) + , key_(std::move(key)) + { + assert(!key_.empty()); + } + + /** + * Get the option key. + * + * \return the key + */ + inline const std::string& key() const noexcept + { + return key_; + } + + /** + * Get the option value. + * + * \return the value + */ + inline const std::string& value() const noexcept + { + static std::string dummy; + + return empty() ? dummy : (*this)[0]; + } +}; + +/** + * \brief Section that contains one or more options. + */ +class section : public std::vector<option> { +private: + std::string key_; + +public: + /** + * Construct a section with its name. + * + * \pre key must not be empty + * \param key the key + */ + inline section(std::string key) noexcept + : key_(std::move(key)) + { + assert(!key_.empty()); + } + + /** + * Get the section key. + * + * \return the key + */ + inline const std::string& key() const noexcept + { + return key_; + } + + /** + * Check if the section contains a specific option. + * + * \param key the option key + * \return true if the option exists + */ + inline bool contains(const std::string& key) const noexcept + { + return find(key) != end(); + } + + /** + * Find an option or return an empty one if not found. + * + * \param key the key + * \return the option or empty one if not found + */ + inline option get(const std::string& key) const noexcept + { + auto it = find(key); + + if (it == end()) + return option(key); + + return *it; + } + + /** + * Find an option by key and return an iterator. + * + * \param key the key + * \return the iterator or end() if not found + */ + inline iterator find(const std::string& key) noexcept + { + return std::find_if(begin(), end(), [&] (const auto& o) { + return o.key() == key; + }); + } + + /** + * Find an option by key and return an iterator. + * + * \param key the key + * \return the iterator or end() if not found + */ + inline const_iterator find(const std::string& key) const noexcept + { + return std::find_if(cbegin(), cend(), [&] (const auto& o) { + return o.key() == key; + }); + } + + /** + * Access an option at the specified key. + * + * \param key the key + * \return the option + * \pre contains(key) must return true + */ + inline option& operator[](const std::string& key) + { + assert(contains(key)); + + return *find(key); + } + + /** + * Overloaded function. + * + * \param key the key + * \return the option + * \pre contains(key) must return true + */ + inline const option& operator[](const std::string& key) const + { + assert(contains(key)); + + return *find(key); + } + + /** + * Inherited operators. + */ + using std::vector<option>::operator[]; +}; + +/** + * \brief Ini document description. + * \see read_file + * \see read_string + */ +class document : public std::vector<section> { +public: + /** + * Check if a document has a specific section. + * + * \param key the key + * \return true if the document contains the section + */ + inline bool contains(const std::string& key) const noexcept + { + return find(key) != end(); + } + + /** + * Find a section or return an empty one if not found. + * + * \param key the key + * \return the section or empty one if not found + */ + inline section get(const std::string& key) const noexcept + { + auto it = find(key); + + if (it == end()) + return section(key); + + return *it; + } + + /** + * Find a section by key and return an iterator. + * + * \param key the key + * \return the iterator or end() if not found + */ + inline iterator find(const std::string& key) noexcept + { + return std::find_if(begin(), end(), [&] (const auto& o) { + return o.key() == key; + }); + } + + /** + * Find a section by key and return an iterator. + * + * \param key the key + * \return the iterator or end() if not found + */ + inline const_iterator find(const std::string& key) const noexcept + { + return std::find_if(cbegin(), cend(), [&] (const auto& o) { + return o.key() == key; + }); + } + + /** + * Access a section at the specified key. + * + * \param key the key + * \return the section + * \pre contains(key) must return true + */ + inline section& operator[](const std::string& key) + { + assert(contains(key)); + + return *find(key); + } + + /** + * Overloaded function. + * + * \param key the key + * \return the section + * \pre contains(key) must return true + */ + inline const section& operator[](const std::string& key) const + { + assert(contains(key)); + + return *find(key); + } + + /** + * Inherited operators. + */ + using std::vector<section>::operator[]; +}; + +/** + * Analyse a stream and detect potential syntax errors. This does not parse the + * file like including other files in include statement. + * + * It does only analysis, for example if an option is defined under no section, + * this does not trigger an exception while it's invalid. + * + * \param it the iterator + * \param end where to stop + * \return the list of tokens + * \throws exception on errors + */ +tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end); + +/** + * Overloaded function for stream. + * + * \param stream the stream + * \return the list of tokens + * \throws exception on errors + */ +tokens analyse(std::istream& stream); + +/** + * Parse the produced tokens. + * + * \param tokens the tokens + * \param path the parent path + * \return the document + * \throw exception on errors + */ +document parse(const tokens& tokens, const std::string& path = "."); + +/** + * Parse a file. + * + * \param filename the file name + * \return the document + * \throw exception on errors + */ +document read_file(const std::string& filename); + +/** + * Parse a string. + * + * If the string contains include statements, they are relative to the current + * working directory. + * + * \param buffer the buffer + * \return the document + * \throw exception on exceptions + */ +document read_string(const std::string& buffer); + +/** + * Show all tokens and their description. + * + * \param tokens the tokens + */ +void dump(const tokens& tokens); + +} // !ini + +} // !irccd + +#endif // !IRCCD_COMMON_INI_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/ini_util.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,97 @@ +/* + * ini_util.hpp -- ini utilities + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_INI_UTIL_HPP +#define IRCCD_COMMON_INI_UTIL_HPP + +/** + * \file ini_util.hpp + * \brief Ini utilities. + */ + +#include <boost/optional.hpp> + +#include "ini.hpp" +#include "string_util.hpp" + +namespace irccd { + +/** + * \brief Ini utilities. + */ +namespace ini_util { + +/** + * Get an unsigned integer from the configuration section. + * + * \param sc the section + * \param name the option name + * \return the value or none if not able to convert + */ +template <typename Int> +inline boost::optional<Int> get_uint(const ini::section& sc, const std::string& name) noexcept +{ + return string_util::to_uint<Int>(sc.get(name).value()); +} + +/** + * Get an optional string or the default value if not given. + * + * \param sc the section + * \param name the option name + * \param def the default value + * \return the value or def if not found + */ +inline std::string optional_string(const ini::section& sc, + const std::string& name, + const std::string& def) noexcept +{ + const auto it = sc.find(name); + + if (it == sc.end()) + return def; + + return it->value(); +} + +/** + * Get an optional unsigned integer from the configuration section. + * + * \param sc the section + * \param name the option name + * \param def the default value + * \return the value or none if not able to convert + */ +template <typename Int> +inline boost::optional<Int> optional_uint(const ini::section& sc, + const std::string& name, + Int def) noexcept +{ + const auto it = sc.find(name); + + if (it == sc.end()) + return def; + + return string_util::to_uint<Int>(it->value()); +} + +} // !ini_util + +} // !irccd + +#endif // !IRCCD_COMMON_INI_UTIL_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/json_util.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,366 @@ +/* + * json_util.hpp -- utilities for JSON + * + * Copyright (c) 2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_JSON_UTIL_HPP +#define IRCCD_JSON_UTIL_HPP + +/** + * \file json_util.hpp + * \brief Utilities for JSON. + */ + +#include <cstdint> +#include <limits> +#include <string> +#include <type_traits> + +#include <boost/optional.hpp> + +#include <json.hpp> + +namespace irccd { + +/** + * \brief Utilities for JSON. + */ +namespace json_util { + +/** + * \cond JSON_UTIL_HIDDEN_SYMBOLS + */ + +namespace detail { + +template <typename Int> +class parser_type_traits_uint : public std::true_type { +public: + static boost::optional<Int> get(const nlohmann::json& value) noexcept + { + if (!value.is_number_unsigned()) + return boost::none; + + const auto ret = value.get<std::uint64_t>(); + + if (ret > std::numeric_limits<Int>::max()) + return boost::none; + + return static_cast<Int>(ret); + } +}; + +template <typename Int> +class parser_type_traits_int : public std::true_type { +public: + static boost::optional<Int> get(const nlohmann::json& value) noexcept + { + if (!value.is_number_integer()) + return boost::none; + + const auto ret = value.get<std::int64_t>(); + + if (ret < std::numeric_limits<Int>::min() || ret > std::numeric_limits<Int>::max()) + return boost::none; + + return static_cast<Int>(ret); + } +}; + +} // !detail + +/** + * \endcond + */ + +/** + * \brief Describe how to convert a JSON value. + * + * This class must be specialized for every type you want to convert from JSON + * to its native type. + * + * You only need to implement the get function with the following signature: + * + * ```cpp + * static boost::optional<T> get(const nlohmann::json& value); + * ``` + * + * The implementation should not throw an exception but return a null optional + * instead. + * + * This class is already specialized for the given types: + * + * - bool + * - double + * - std::uint(8, 16, 32, 64) + * - std::string + */ +template <typename T> +class parser_type_traits : public std::false_type { +}; + +/** + * \brief Specialization for `bool`. + */ +template <> +class parser_type_traits<bool> : public std::true_type { +public: + /** + * Convert the JSON value to bool. + * + * \return the bool or none if not a boolean type + */ + static boost::optional<bool> get(const nlohmann::json& value) noexcept + { + if (!value.is_boolean()) + return boost::none; + + return value.get<bool>(); + } +}; + +/** + * \brief Specialization for `double`. + */ +template <> +class parser_type_traits<double> : public std::true_type { +public: + /** + * Convert the JSON value to bool. + * + * \return the double or none if not a double type + */ + static boost::optional<double> get(const nlohmann::json& value) noexcept + { + if (!value.is_number_float()) + return boost::none; + + return value.get<double>(); + } +}; + +/** + * \brief Specialization for `std::string`. + */ +template <> +class parser_type_traits<std::string> : public std::true_type { +public: + /** + * Convert the JSON value to bool. + * + * \return the string or none if not a string type + */ + static boost::optional<std::string> get(const nlohmann::json& value) + { + if (!value.is_string()) + return boost::none; + + return value.get<std::string>(); + } +}; + +/** + * \brief Specialization for `std::int8_t`. + */ +template <> +class parser_type_traits<std::int8_t> : public detail::parser_type_traits_int<std::int8_t> { +}; + +/** + * \brief Specialization for `std::int16_t`. + */ +template <> +class parser_type_traits<std::int16_t> : public detail::parser_type_traits_int<std::int16_t> { +}; + +/** + * \brief Specialization for `std::int32_t`. + */ +template <> +class parser_type_traits<std::int32_t> : public detail::parser_type_traits_int<std::int32_t> { +}; + +/** + * \brief Specialization for `std::int64_t`. + */ +template <> +class parser_type_traits<std::int64_t> : public std::true_type { +public: + /** + * Convert the JSON value to std::int64_t. + * + * \return the int or none if not a int type + */ + static boost::optional<std::int64_t> get(const nlohmann::json& value) noexcept + { + if (!value.is_number_integer()) + return boost::none; + + return value.get<std::int64_t>(); + } +}; + +/** + * \brief Specialization for `std::int8_t`. + */ +template <> +class parser_type_traits<std::uint8_t> : public detail::parser_type_traits_uint<std::uint8_t> { +}; + +/** + * \brief Specialization for `std::int16_t`. + */ +template <> +class parser_type_traits<std::uint16_t> : public detail::parser_type_traits_uint<std::uint16_t> { +}; + +/** + * \brief Specialization for `std::int32_t`. + */ +template <> +class parser_type_traits<std::uint32_t> : public detail::parser_type_traits_uint<std::uint32_t> { +}; + +/** + * \brief Specialization for `std::int64_t`. + */ +template <> +class parser_type_traits<std::uint64_t> : public std::true_type { +public: + /** + * Convert the JSON value to std::uint64_t. + * + * \return the int or none if not a int type + */ + static boost::optional<std::uint64_t> get(const nlohmann::json& value) noexcept + { + if (!value.is_number_unsigned()) + return boost::none; + + return value.get<std::uint64_t>(); + } +}; + +/** + * \brief Convenient JSON object parser + * + * This class helps destructuring insecure JSON input by returning optional + * values if they are not present or invalid. + */ +class document : public nlohmann::json { +public: + /** + * Constructor. + * + * \param object the object + */ + inline document(nlohmann::json object) + : nlohmann::json(std::move(object)) + { + } + + /** + * Get a value from the document object. + * + * \param key the property key + * \return the value or boost::none if not found or not convertible + */ + template <typename Type> + inline boost::optional<Type> get(const std::string& key) const noexcept + { + static_assert(parser_type_traits<Type>::value, "type not supported"); + + const auto it = find(key); + + if (it == end()) + return boost::none; + + return parser_type_traits<Type>::get(*it); + } + + /** + * Get an optional value from the document object. + * + * If the value is undefined, the default value is returned. Otherwise, if + * the value is not in the given type, boost::none is returned. + * + * \param key the property key + * \param def the default value if property is undefined + * \return the value, boost::none or def + */ + template <typename Type, typename DefaultValue> + inline boost::optional<Type> optional(const std::string& key, DefaultValue&& def) const noexcept + { + static_assert(parser_type_traits<Type>::value, "type not supported"); + + const auto it = find(key); + + if (it == end()) + return boost::optional<Type>(std::forward<DefaultValue>(def)); + + return parser_type_traits<Type>::get(*it); + } +}; + +/** + * Print the value as human readable. + * + * \note This only works on flat objects. + * \param value the value + * \param indent the optional indent for objects/arrays + * \return the string + */ +inline std::string pretty(const nlohmann::json& value, int indent = 4) +{ + switch (value.type()) { + case nlohmann::json::value_t::null: + return "null"; + case nlohmann::json::value_t::string: + return value.get<std::string>(); + case nlohmann::json::value_t::boolean: + return value.get<bool>() ? "true" : "false"; + case nlohmann::json::value_t::number_integer: + return std::to_string(value.get<std::int64_t>()); + case nlohmann::json::value_t::number_unsigned: + return std::to_string(value.get<std::uint64_t>()); + case nlohmann::json::value_t::number_float: + return std::to_string(value.get<double>()); + default: + return value.dump(indent); + } +} + +/** + * Check if a JSON array contains a specific value in any order. + * + * \param array the JSON array + * \param value the JSON value + * \return true if value is present + */ +inline bool contains(const nlohmann::json& array, const nlohmann::json& value) noexcept +{ + for (const auto& v : array) + if (v == value) + return true; + + return false; +} + +} // !json_util + +} // !irccd + +#endif // !IRCCD_JSON_UTIL_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/options.cpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,191 @@ +/* + * options.cpp -- parse Unix command line options + * + * Copyright (c) 2015-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cassert> + +#include "options.hpp" + +namespace irccd { + +namespace option { + +namespace { + +using iterator = std::vector<std::string>::iterator; +using args = std::vector<std::string>; + +inline bool is_option(const std::string& arg) noexcept +{ + return arg.size() >= 2 && arg[0] == '-'; +} + +inline bool is_long_option(const std::string& arg) noexcept +{ + assert(is_option(arg)); + + return arg.size() >= 3 && arg[1] == '-'; +} + +inline bool is_short_simple(const std::string& arg) noexcept +{ + assert(is_option(arg) && !is_long_option(arg)); + + return arg.size() == 2; +} + +void parse_long_option(result& result, args& args, iterator& it, iterator& end, const options& definition) +{ + auto arg = *it++; + auto opt = definition.find(arg); + + if (opt == definition.end()) + throw invalid_option(arg); + + // Need argument? + if (opt->second) { + if (it == end || is_option(*it)) + throw missing_value(arg); + + result.insert(std::make_pair(arg, *it++)); + it = args.erase(args.begin(), it); + end = args.end(); + } else { + result.insert(std::make_pair(arg, "")); + it = args.erase(args.begin()); + end = args.end(); + } +} + +void parse_short_option_simple(result& result, args& args, iterator& it, iterator &end, const options& definition) +{ + /* + * Here two cases: + * + * -v (no option) + * -c value + */ + auto arg = *it++; + auto opt = definition.find(arg); + + if (opt == definition.end()) + throw invalid_option(arg); + + // Need argument? + if (opt->second) { + if (it == end || is_option(*it)) + throw missing_value(arg); + + result.insert(std::make_pair(arg, *it++)); + it = args.erase(args.begin(), it); + end = args.end(); + } else { + result.insert(std::make_pair(arg, "")); + it = args.erase(args.begin()); + end = args.end(); + } +} + +void parse_short_option_compressed(result& result, args& args, iterator& it, iterator &end, const options& definition) +{ + /* + * Here multiple scenarios: + * + * 1. -abc (-a -b -c if all are simple boolean arguments) + * 2. -vc foo.conf (-v -c foo.conf if -c is argument dependant) + * 3. -vcfoo.conf (-v -c foo.conf also) + */ + auto value = it->substr(1); + auto len = value.length(); + int toremove = 1; + + for (std::size_t i = 0; i < len; ++i) { + auto arg = std::string{'-'} + value[i]; + auto opt = definition.find(arg); + + if (opt == definition.end()) + throw invalid_option(arg); + + if (opt->second) { + if (i == (len - 1)) { + // End of string, get the next argument (see 2.). + if (++it == end || is_option(*it)) + throw missing_value(arg); + + result.insert(std::make_pair(arg, *it)); + toremove += 1; + } else { + result.insert(std::make_pair(arg, value.substr(i + 1))); + i = len; + } + } else + result.insert(std::make_pair(arg, "")); + } + + it = args.erase(args.begin(), args.begin() + toremove); + end = args.end(); +} + +void parse_short_option(result& result, args& args, iterator& it, iterator &end, const options& definition) +{ + if (is_short_simple(*it)) + parse_short_option_simple(result, args, it, end, definition); + else + parse_short_option_compressed(result, args, it, end, definition); +} + +} // !namespace + +result read(std::vector<std::string>& args, const options& definition) +{ + result result; + + auto it = args.begin(); + auto end = args.end(); + + while (it != end) { + if (!is_option(*it)) + break; + + if (is_long_option(*it)) + parse_long_option(result, args, it, end, definition); + else + parse_short_option(result, args, it, end, definition); + } + + return result; +} + +result read(int& argc, char**& argv, const options& definition) +{ + std::vector<std::string> args; + + for (int i = 0; i < argc; ++i) + args.push_back(argv[i]); + + auto before = args.size(); + auto result = read(args, definition); + + argc -= before - args.size(); + argv += before - args.size(); + + return result; +} + +} // !option + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/options.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,161 @@ +/* + * options.hpp -- parse Unix command line options + * + * Copyright (c) 2015-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_OPTIONS_HPP +#define IRCCD_COMMON_OPTIONS_HPP + +/** + * \file options.hpp + * \brief Basic Unix options parser. + */ + +#include <exception> +#include <map> +#include <string> +#include <utility> +#include <vector> + +namespace irccd { + +/** + * Namespace for options parsing. + */ +namespace option { + +/** + * \brief This exception is thrown when an invalid option has been found. + */ +class invalid_option : public std::exception { +private: + std::string message_; + std::string name_; + +public: + /** + * Construct the exception. + * + * \param name the argument missing + */ + inline invalid_option(std::string name) + : name_(std::move(name)) + { + message_ = std::string("invalid option: ") + name_; + } + + /** + * Get the option name. + * + * \return the name + */ + inline const std::string& name() const noexcept + { + return name_; + } + + /** + * Get the error message. + * + * \return the error message + */ + const char* what() const noexcept override + { + return message_.c_str(); + } +}; + +/** + * \brief This exception is thrown when an option requires a value and no value + * has been given. + */ +class missing_value : public std::exception { +private: + std::string message_; + std::string name_; + +public: + /** + * Construct the exception. + * + * \param name the option that requires a value + */ + inline missing_value(std::string name) + : name_(std::move(name)) + { + message_ = std::string("missing argument for: ") + name_; + } + + /** + * Get the option name. + * + * \return the name + */ + inline const std::string& name() const noexcept + { + return name_; + } + + /** + * Get the error message. + * + * \return the error message + */ + const char* what() const noexcept override + { + return message_.c_str(); + } +}; + +/** + * Packed multimap of options. + */ +using result = std::multimap<std::string, std::string>; + +/** + * Define the allowed options. + */ +using options = std::map<std::string, bool>; + +/** + * Extract the command line options and return a result. + * + * \param args the arguments + * \param definition + * \warning the arguments vector is modified in place to remove parsed options + * \throw missing_value + * \throw invalid_option + */ +result read(std::vector<std::string>& args, const options& definition); + +/** + * Overloaded function for usage with main() arguments. + * + * \param argc the number of arguments + * \param argv the argument vector + * \param definition + * \note don't forget to remove the first argv[0] argument + * \warning the argc and argv are modified in place to remove parsed options + * \throw missing_value + * \throw invalid_option + */ +result read(int& argc, char**& argv, const options& definition); + +} // !option + +} // !irccd + +#endif // !IRCCD_COMMON_OPTIONS_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/socket_acceptor.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,162 @@ +/* + * socket_acceptor.hpp -- socket stream acceptor interface + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_SOCKET_ACCEPTOR_HPP +#define IRCCD_COMMON_SOCKET_ACCEPTOR_HPP + +/** + * \file socket_acceptor.hpp + * \brief Socket stream acceptor interface. + */ + +#include <irccd/sysconfig.hpp> + +#include "acceptor.hpp" +#include "socket_stream.hpp" + +namespace irccd { + +namespace io { + +/** + * \brief Socket stream acceptor interface. + * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) + */ +template <typename Protocol> +class socket_acceptor : public acceptor { +public: + /** + * Convenient endpoint alias. + */ + using endpoint = typename Protocol::endpoint; + + /** + * Convenient acceptor alias. + */ + using acceptor = typename Protocol::acceptor; + + /** + * Convenient socket alias. + */ + using socket = typename Protocol::socket; + +private: + acceptor acceptor_; + +#if !defined(NDEBUG) + bool is_accepting_{false}; +#endif + +protected: + /** + * Helper to accept on the real underlying socket. + * + * \param socket the real socket + * \param handler the handler + */ + template <typename Socket, typename Handler> + void do_accept(Socket& socket, Handler handler); + +public: + /** + * Construct the socket_acceptor. + * + * \pre acceptor must be ready (is_open() returns true) + * \param acceptor the Boost.Asio acceptor + */ + inline socket_acceptor(acceptor acceptor) noexcept + : acceptor_(std::move(acceptor)) + { + assert(acceptor_.is_open()); + } + + /** + * Get the underlying acceptor. + * + * \return the acceptor + */ + inline const acceptor& get_acceptor() const noexcept + { + return acceptor_; + } + + /** + * Overloaded function. + * + * \return the acceptor + */ + inline acceptor& get_acceptor() noexcept + { + return acceptor_; + } + + /** + * \copydoc acceptor::accept + */ + void accept(accept_handler handler) override; +}; + +template <typename Protocol> +template <typename Socket, typename Handler> +void socket_acceptor<Protocol>::do_accept(Socket& socket, Handler handler) +{ +#if !defined(NDEBUG) + assert(!is_accepting_); + + is_accepting_ = true; +#endif + + acceptor_.async_accept(socket, [this, handler] (auto code) { +#if !defined(NDEBUG) + is_accepting_ = false; +#endif + handler(detail::convert(code)); + }); +} + +template <typename Protocol> +void socket_acceptor<Protocol>::accept(accept_handler handler) +{ + assert(handler); + + const auto client = std::make_shared<socket_stream<socket>>(acceptor_.get_io_service()); + + do_accept(client->get_socket(), [this, client, handler] (auto code) { + handler(std::move(code), code ? nullptr : std::move(client)); + }); +} + +/** + * Convenient TCP/IP acceptor type. + */ +using ip_acceptor = socket_acceptor<boost::asio::ip::tcp>; + +#if !BOOST_OS_WINDOWS + +/** + * Convenient Unix acceptor type. + */ +using local_acceptor = socket_acceptor<boost::asio::local::stream_protocol>; + +#endif + +} // !io + +} // !irccd + +#endif // !IRCCD_COMMON_SOCKET_ACCEPTOR_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/socket_connector.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,176 @@ +/* + * socket_connector.hpp -- socket connection interface + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_SOCKET_CONNECTOR_HPP +#define IRCCD_COMMON_SOCKET_CONNECTOR_HPP + +/** + * \file socket_connector.hpp + * \brief Socket connection interface. + */ + +#include <irccd/sysconfig.hpp> + +#include <vector> + +#include "connector.hpp" +#include "socket_stream.hpp" + +namespace irccd { + +namespace io { + +/** + * \brief Socket connection interface. + * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) + */ +template <typename Protocol> +class socket_connector : public connector { +public: + /** + * Convenient endpoint alias. + */ + using endpoint = typename Protocol::endpoint; + + /** + * Convenient socket alias. + */ + using socket = typename Protocol::socket; + +private: + boost::asio::io_service& service_; + std::vector<endpoint> endpoints_; + +#if !defined(NDEBUG) + bool is_connecting_{false}; +#endif + +protected: + /** + * Start trying to connect to all endpoints. + * + * \param socket the underlying socket + * \param handler handler with `void f(std::error_code)` signature + */ + template <typename Socket, typename Handler> + void do_connect(Socket& socket, Handler handler); + +public: + /** + * Construct the socket connector with only one endpoint. + * + * \param service the service + * \param endpoint the unique endpoint + */ + inline socket_connector(boost::asio::io_service& service, endpoint endpoint) noexcept + : service_(service) + , endpoints_{std::move(endpoint)} + { + } + + /** + * Construct the socket connection. + * + * \param service the service + * \param eps the endpoints + */ + inline socket_connector(boost::asio::io_service& service, std::vector<endpoint> eps) noexcept + : service_(service) + , endpoints_(std::move(eps)) + { + } + + /** + * Get the underlying I/O service. + * + * \return the I/O service + */ + inline const boost::asio::io_service& get_io_service() const noexcept + { + return service_; + } + + /** + * Overloaded function. + * + * \return the I/O service + */ + inline boost::asio::io_service& get_io_service() noexcept + { + return service_; + } + + /** + * \copydoc connector::connect + */ + void connect(connect_handler handler); +}; + +template <typename Protocol> +template <typename Socket, typename Handler> +void socket_connector<Protocol>::do_connect(Socket& socket, Handler handler) +{ +#if !defined(NDEBUG) + assert(!is_connecting_); + + is_connecting_ = true; +#endif + + boost::asio::async_connect(socket, endpoints_.begin(), endpoints_.end(), [this, handler] (auto code, auto ep) { +#if !defined(NDEBUG) + is_connecting_ = false; +#endif + + if (ep == endpoints_.end()) + handler(make_error_code(std::errc::host_unreachable)); + else + handler(detail::convert(code)); + }); +} + +template <typename Protocol> +void socket_connector<Protocol>::connect(connect_handler handler) +{ + assert(handler); + + const auto stream = std::make_shared<socket_stream<socket>>(service_); + + do_connect(stream->get_socket(), [handler, stream] (auto code) { + handler(code, code ? nullptr : std::move(stream)); + }); +} + +/** + * Convenient TCP/IP connector type. + */ +using ip_connector = socket_connector<boost::asio::ip::tcp>; + +#if !BOOST_OS_WINDOWS + +/** + * Convenient Unix conncetor type. + */ +using local_connector = socket_connector<boost::asio::local::stream_protocol>; + +#endif + +} // !io + +} // !irccd + +#endif // !IRCCD_COMMON_SOCKET_CONNECTOR_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/socket_stream.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,246 @@ +/* + * socket_stream.hpp -- socket stream interface + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_SOCKET_STREAM_HPP +#define IRCCD_COMMON_SOCKET_STREAM_HPP + +/** + * \file socket_stream.hpp + * \brief Socket stream interface. + */ + +#include <irccd/sysconfig.hpp> + +#include <cstddef> +#include <cassert> +#include <string> +#include <utility> + +#include <boost/asio.hpp> +#include <boost/predef/os.h> + +#include "stream.hpp" + +namespace irccd { + +namespace io { + +/** + * \cond HIDDEN_SYMBOLS + */ + +namespace detail { + +/** + * Convert boost::system::error_code to std. + * + * \param code the error code + * \return the std::error_code + */ +inline std::error_code convert(boost::system::error_code code) noexcept +{ + return std::error_code(code.value(), std::system_category()); +} + +} // !detail + +/** + * \endcond + */ + +/** + * \brief Socket implementation interface. + * \tparam Socket the Boost.Asio compatible socket. + * + * This class reimplements stream for Boost.Asio sockets. + */ +template <typename Socket> +class socket_stream : public stream { +private: + Socket socket_; + boost::asio::streambuf input_; + std::string output_; + +#if !defined(NDEBUG) + bool is_receiving_{false}; + bool is_sending_{false}; +#endif + + void handle_read(boost::system::error_code, std::size_t, read_handler); + void handle_write(boost::system::error_code, std::size_t, write_handler); + +public: + /** + * Create the socket stream. + * + * \param args the Socket constructor arguments + */ + template <typename... Args> + inline socket_stream(Args&&... args) + : socket_(std::forward<Args>(args)...) + { + } + + /** + * Get the underlying socket. + * + * \return the socket + */ + inline const Socket& get_socket() const noexcept + { + return socket_; + } + + /** + * Overloaded function + * + * \return the socket + */ + inline Socket& get_socket() noexcept + { + return socket_; + } + + /** + * \copydoc stream::read + */ + void read(read_handler handler) override; + + /** + * \copydoc stream::write + */ + void write(const nlohmann::json& json, write_handler handler) override; +}; + +template <typename Socket> +void socket_stream<Socket>::handle_read(boost::system::error_code code, + std::size_t xfer, + read_handler handler) +{ +#if !defined(NDEBUG) + is_receiving_ = false; +#endif + + if (xfer == 0U) { + handler(make_error_code(std::errc::not_connected), nullptr); + return; + } + if (code) { + handler(detail::convert(code), nullptr); + return; + } + + // 1. Convert the buffer safely. + std::string buffer; + + try { + buffer = std::string( + boost::asio::buffers_begin(input_.data()), + boost::asio::buffers_begin(input_.data()) + xfer - /* \r\n\r\n */ 4 + ); + + input_.consume(xfer); + } catch (const std::bad_alloc&) { + handler(make_error_code(std::errc::not_enough_memory), nullptr); + return; + } + + // 2. Convert to JSON. + nlohmann::json doc; + + try { + doc = nlohmann::json::parse(buffer); + } catch (const std::exception&) { + handler(make_error_code(std::errc::invalid_argument), nullptr); + return; + } + + if (!doc.is_object()) + handler(make_error_code(std::errc::invalid_argument), nullptr); + else + handler(std::error_code(), std::move(doc)); +} + +template <typename Socket> +void socket_stream<Socket>::handle_write(boost::system::error_code code, + std::size_t xfer, + write_handler handler) +{ +#if !defined(NDEBUG) + is_sending_ = false; +#endif + + if (xfer == 0) + handler(make_error_code(std::errc::not_connected)); + else + handler(detail::convert(code)); +} + +template <typename Socket> +void socket_stream<Socket>::read(read_handler handler) +{ +#if !defined(NDEBUG) + assert(!is_receiving_); + assert(handler); + + is_receiving_ = true; +#endif + + boost::asio::async_read_until(get_socket(), input_, "\r\n\r\n", [this, handler] (auto code, auto xfer) { + handle_read(code, xfer, std::move(handler)); + }); +} + +template <typename Socket> +void socket_stream<Socket>::write(const nlohmann::json& json, write_handler handler) +{ +#if !defined(NDEBUG) + assert(!is_sending_); + assert(handler); + + is_sending_ = true; +#endif + + output_ = json.dump(0) + "\r\n\r\n"; + + const auto buffer = boost::asio::buffer(output_.data(), output_.size()); + + boost::asio::async_write(get_socket(), buffer, [this, handler] (auto code, auto xfer) { + handle_write(code, xfer, std::move(handler)); + }); +} + +/** + * Convenient TCP/IP stream type. + */ +using ip_stream = socket_stream<boost::asio::ip::tcp::socket>; + +#if !BOOST_OS_WINDOWS + +/** + * Convenient Unix stream type. + */ +using local_stream = socket_stream<boost::asio::local::stream_protocol::socket>; + +#endif + +} // !io + +} // !irccd + +#endif // !IRCCD_COMMON_SOCKET_STREAM_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/stream.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,91 @@ +/* + * stream.hpp -- abstract stream interface + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_STREAM_HPP +#define IRCCD_COMMON_STREAM_HPP + +/** + * \file stream.hpp + * \brief Abstract stream interface. + */ + +#include <functional> +#include <system_error> + +#include "json.hpp" + +namespace irccd { + +namespace io { + +/** + * \brief Read completion handler. + */ +using read_handler = std::function<void (std::error_code, nlohmann::json)>; + +/** + * \brief Write completion handler. + */ +using write_handler = std::function<void (std::error_code)>; + +/** + * \brief Abstract stream interface + * + * Abstract I/O interface that allows reading/writing from a stream in an + * asynchronous manner. + * + * The derived classes must implement non-blocking read and write operations. + */ +class stream { +public: + /** + * Default constructor. + */ + stream() = default; + + /** + * Virtual destructor defaulted. + */ + virtual ~stream() = default; + + /** + * Start asynchronous read. + * + * \pre another read operation must not be running + * \pre handler != nullptr + * \param handler the handler + */ + virtual void read(read_handler handler) = 0; + + /** + * Start asynchronous write. + * + * \pre json.is_object() + * \pre another write operation must not be running + * \pre handler != nullptr + * \param json the JSON message + * \param handler the handler + */ + virtual void write(const nlohmann::json& json, write_handler handler) = 0; +}; + +} // !io + +} // !irccd + +#endif // !IRCCD_COMMON_STREAM_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/string_util.cpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,446 @@ +/* + * string_util.cpp -- string utilities + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <boost/predef/os.h> + +#include "sysconfig.hpp" + +#if defined(HAVE_POPEN) +# include <array> +# include <cerrno> +# include <cstring> +# include <functional> +# include <memory> +#endif + +#include <cassert> +#include <iomanip> + +#include "string_util.hpp" + +using namespace std::string_literals; + +namespace irccd { + +namespace string_util { + +// {{{ subst + +namespace { + +const std::unordered_map<std::string, int> irc_colors{ + { "white", 0 }, + { "black", 1 }, + { "blue", 2 }, + { "green", 3 }, + { "red", 4 }, + { "brown", 5 }, + { "purple", 6 }, + { "orange", 7 }, + { "yellow", 8 }, + { "lightgreen", 9 }, + { "cyan", 10 }, + { "lightcyan", 11 }, + { "lightblue", 12 }, + { "pink", 13 }, + { "grey", 14 }, + { "lightgrey", 15 } +}; + +const std::unordered_map<std::string, char> irc_attributes{ + { "bold", '\x02' }, + { "italic", '\x09' }, + { "strike", '\x13' }, + { "reset", '\x0f' }, + { "underline", '\x15' }, + { "underline2", '\x1f' }, + { "reverse", '\x16' } +}; + +const std::unordered_map<std::string, unsigned> shell_colors{ + { "black", 30 }, + { "red", 31 }, + { "green", 32 }, + { "orange", 33 }, + { "blue", 34 }, + { "purple", 35 }, + { "cyan", 36 }, + { "white", 37 }, + { "default", 39 }, +}; + +const std::unordered_map<std::string, unsigned> shell_attributes{ + { "bold", 1 }, + { "dim", 2 }, + { "underline", 4 }, + { "blink", 5 }, + { "reverse", 7 }, + { "hidden", 8 } +}; + +inline bool is_reserved(char token) noexcept +{ + return token == '#' || token == '@' || token == '$' || token == '!'; +} + +std::string subst_date(const std::string& text, const subst& params) +{ + std::ostringstream oss; + +#if defined(HAVE_STD_PUT_TIME) + oss << std::put_time(std::localtime(¶ms.time), text.c_str()); +#else + /* + * Quick and dirty hack because old version of GCC does not have this + * function. + */ + char buffer[4096]; + + std::strftime(buffer, sizeof (buffer) - 1, text.c_str(), std::localtime(¶ms.time)); + + oss << buffer; +#endif + + return oss.str(); +} + +std::string subst_keywords(const std::string& content, const subst& params) +{ + auto value = params.keywords.find(content); + + if (value != params.keywords.end()) + return value->second; + + return ""; +} + +std::string subst_env(const std::string& content) +{ + auto value = std::getenv(content.c_str()); + + if (value != nullptr) + return value; + + return ""; +} + +std::string subst_irc_attrs(const std::string& content) +{ + auto list = split(content, ","); + + // @{} means reset. + if (list.empty()) + return std::string(1, irc_attributes.at("reset")); + + std::ostringstream oss; + + // Remove useless spaces. + std::transform(list.begin(), list.end(), list.begin(), strip); + + /* + * 0: foreground + * 1: background + * 2-n: attributes + */ + auto foreground = list[0]; + if (!foreground.empty() || list.size() >= 2) { + // Color sequence. + oss << '\x03'; + + // Foreground. + auto it = irc_colors.find(foreground); + if (it != irc_colors.end()) + oss << it->second; + + // Background. + if (list.size() >= 2 && (it = irc_colors.find(list[1])) != irc_colors.end()) + oss << "," << it->second; + + // Attributes. + for (std::size_t i = 2; i < list.size(); ++i) { + auto attribute = irc_attributes.find(list[i]); + + if (attribute != irc_attributes.end()) + oss << attribute->second; + } + } + + return oss.str(); +} + +std::string subst_shell_attrs(const std::string& content) +{ +#if !BOOST_OS_WINDOWS + auto list = split(content, ","); + + if (list.empty()) + return "\033[0m"; + if (list.size() > 3) + return ""; + + std::vector<std::string> seq; + + /* + * Shell sequence looks like this: + * + * ^[[attributes;foreground;backgroundm + */ + if (list.size() >= 3) { + const auto it = shell_attributes.find(list[2]); + + if (it != shell_attributes.end()) + seq.push_back(std::to_string(it->second)); + else + return ""; + } + if (list.size() >= 1) { + const auto it = shell_colors.find(list[0]); + + if (it != shell_colors.end()) + seq.push_back(std::to_string(it->second)); + else + return ""; + } + if (list.size() >= 2) { + const auto it = shell_colors.find(list[1]); + + if (it != shell_colors.end()) + seq.push_back(std::to_string(it->second + 10)); + else + return ""; + } + + std::ostringstream oss; + + oss << "\033["; + oss << string_util::join(seq, ';'); + oss << "m"; + + return oss.str(); +#else + return ""; +#endif +} + +std::string subst_shell(const std::string& command) +{ +#if defined(HAVE_POPEN) + std::unique_ptr<FILE, std::function<int (FILE*)>> fp(popen(command.c_str(), "r"), pclose); + + if (fp == nullptr) + throw std::runtime_error(std::strerror(errno)); + + std::string result; + std::array<char, 128> buffer; + std::size_t n; + + while ((n = std::fread(buffer.data(), 1, 128, fp.get())) > 0) + result.append(buffer.data(), n); + if (std::ferror(fp.get())) + throw std::runtime_error(std::strerror(errno)); + + // Erase final '\n'. + auto it = result.find('\n'); + if (it != std::string::npos) + result.erase(it); + + return result; +#else + throw std::runtime_error("shell template not available"); +#endif +} + +std::string substitute(std::string::const_iterator& it, + std::string::const_iterator& end, + char token, + const subst& params) +{ + assert(is_reserved(token)); + + std::string content, value; + + if (it == end) + return ""; + + while (it != end && *it != '}') + content += *it++; + + if (it == end || *it != '}') + throw std::invalid_argument("unclosed "s + token + " construct"s); + + it++; + + // Create default original value if flag is disabled. + value = std::string(1, token) + "{"s + content + "}"s; + + switch (token) { + case '#': + if ((params.flags & subst_flags::keywords) == subst_flags::keywords) + value = subst_keywords(content, params); + break; + case '$': + if ((params.flags & subst_flags::env) == subst_flags::env) + value = subst_env(content); + break; + case '@': + if ((params.flags & subst_flags::irc_attrs) == subst_flags::irc_attrs) + value = subst_irc_attrs(content); + else if ((params.flags & subst_flags::shell_attrs) == subst_flags::shell_attrs) + value = subst_shell_attrs(content); + break; + case '!': + if ((params.flags & subst_flags::shell) == subst_flags::shell) + value = subst_shell(content); + break; + default: + break; + } + + return value; +} + +} // !namespace + +std::string format(std::string text, const subst& params) +{ + /* + * Change the date format before anything else to avoid interpolation with + * keywords and user input. + */ + if ((params.flags & subst_flags::date) == subst_flags::date) + text = subst_date(text, params); + + std::ostringstream oss; + + for (auto it = text.cbegin(), end = text.cend(); it != end; ) { + auto token = *it; + + // Is the current character a reserved token or not? + if (!is_reserved(token)) { + oss << *it++; + continue; + } + + // The token was at the end, just write it and return now. + if (++it == end) { + oss << token; + continue; + } + + // The token is declaring a template variable, substitute it. + if (*it == '{') { + oss << substitute(++it, end, token, params); + continue; + } + + /* + * If the next token is different from the previous one, just let the + * next iteration parse the string because we can have the following + * constructs. + * + * "@#{var}" -> "@value" + */ + if (*it != token) { + oss << token; + continue; + } + + /* + * Write the token only if it's not a variable because at this step we + * may have the following constructs. + * + * "##" -> "##" + * "##hello" -> "##hello" + * "##{hello}" -> "#{hello}" + */ + if (++it == end) + oss << token << token; + else if (*it == '{') + oss << token; + } + + return oss.str(); +} + +// }}} + +// {{{ strip + +std::string strip(std::string str) noexcept +{ + const auto test = [] (auto c) { return !std::isspace(c); }; + + str.erase(str.begin(), std::find_if(str.begin(), str.end(), test)); + str.erase(std::find_if(str.rbegin(), str.rend(), test).base(), str.end()); + + return str; +} + +// }}} + +// {{{ split + +std::vector<std::string> split(const std::string& list, const std::string& delimiters, int max) +{ + std::vector<std::string> result; + std::size_t next = -1, current; + int count = 1; + bool finished = false; + + if (list.empty()) + return result; + + do { + std::string val; + + current = next + 1; + next = list.find_first_of(delimiters, current); + + // split max, get until the end. + if (max >= 0 && count++ >= max) { + val = list.substr(current, std::string::npos); + finished = true; + } else { + val = list.substr(current, next - current); + finished = next == std::string::npos; + } + + result.push_back(val); + } while (!finished); + + return result; +} + +// }}} + +// {{{ is_boolean + +bool is_boolean(std::string value) noexcept +{ + std::transform(value.begin(), value.end(), value.begin(), [] (auto c) { + return toupper(c); + }); + + return value == "1" || value == "YES" || value == "TRUE" || value == "ON"; +} + +// }}} + +} // !string_util + +} // !util
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/string_util.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,411 @@ +/* + * string_util.hpp -- string utilities + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_STRING_UTIL_HPP +#define IRCCD_COMMON_STRING_UTIL_HPP + +/** + * \file string_util.hpp + * \brief String utilities. + */ + +#include "sysconfig.hpp" + +#include <ctime> +#include <initializer_list> +#include <limits> +#include <regex> +#include <sstream> +#include <stdexcept> +#include <string> +#include <type_traits> +#include <unordered_map> + +#include <boost/format.hpp> +#include <boost/optional.hpp> + +namespace irccd { + +/** + * \file string_util.hpp + * \brief String utilities. + */ +namespace string_util { + +// {{{ subst + +/** + * \brief Disable or enable some features. + */ +enum class subst_flags : unsigned { + date = (1 << 0), //!< date templates + keywords = (1 << 1), //!< keywords + env = (1 << 2), //!< environment variables + shell = (1 << 3), //!< command line command + irc_attrs = (1 << 4), //!< IRC escape codes + shell_attrs = (1 << 5) //!< shell attributes +}; + +/** + * \cond ENUM_HIDDEN_SYMBOLS + */ + +inline subst_flags operator^(subst_flags v1, subst_flags v2) noexcept +{ + return static_cast<subst_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2)); +} + +inline subst_flags operator&(subst_flags v1, subst_flags v2) noexcept +{ + return static_cast<subst_flags>(static_cast<unsigned>(v1)& static_cast<unsigned>(v2)); +} + +inline subst_flags operator|(subst_flags v1, subst_flags v2) noexcept +{ + return static_cast<subst_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2)); +} + +inline subst_flags operator~(subst_flags v) noexcept +{ + return static_cast<subst_flags>(~static_cast<unsigned>(v)); +} + +inline subst_flags& operator|=(subst_flags& v1, subst_flags v2) noexcept +{ + v1 = static_cast<subst_flags>(static_cast<unsigned>(v1) | static_cast<unsigned>(v2)); + + return v1; +} + +inline subst_flags& operator&=(subst_flags& v1, subst_flags v2) noexcept +{ + v1 = static_cast<subst_flags>(static_cast<unsigned>(v1)& static_cast<unsigned>(v2)); + + return v1; +} + +inline subst_flags& operator^=(subst_flags& v1, subst_flags v2) noexcept +{ + v1 = static_cast<subst_flags>(static_cast<unsigned>(v1) ^ static_cast<unsigned>(v2)); + + return v1; +} + +/** + * \endcond + */ + +/** + * \brief Used for format() function. + */ +class subst { +public: + /** + * Flags for selecting templates. + */ + subst_flags flags{ + subst_flags::date | + subst_flags::keywords | + subst_flags::env | + subst_flags::irc_attrs + }; + + /** + * Fill that field if you want a date. + */ + std::time_t time{std::time(nullptr)}; + + /** + * Fill that map if you want to replace keywords. + */ + std::unordered_map<std::string, std::string> keywords; +}; + +/** + * Format a string and update all templates. + * + * ## Syntax + * + * The syntax is <strong>?{}</strong> where <strong>?</strong> is replaced by + * one of the token defined below. Braces are mandatory and cannot be ommited. + * + * To write a literal template construct, prepend the token twice. + * + * ## Availables templates + * + * The following templates are available: + * + * - <strong>\#{name}</strong>: name will be substituted from the keywords in + * params, + * - <strong>\${name}</strong>: name will be substituted from the environment + * variable, + * - <strong>\@{attributes}</strong>: the attributes will be substituted to IRC + * or shell colors (see below), + * - <strong>%</strong>, any format accepted by strftime(3). + * + * ## Attributes + * + * The attribute format is composed of three parts, foreground, background and + * modifiers, each separated by a comma. + * + * **Note:** you cannot omit parameters, to specify the background, you must + * specify the foreground. + * + * ## Examples + * + * ### Valid constructs + * + * - <strong>\#{target}, welcome</strong>: if target is set to "irccd", + * becomes "irccd, welcome", + * - <strong>\@{red}\#{target}</strong>: if target is specified, it is written + * in red, + * + * ### Invalid or literals constructs + * + * - <strong>\#\#{target}</strong>: will output "\#{target}", + * - <strong>\#\#</strong>: will output "\#\#", + * - <strong>\#target</strong>: will output "\#target", + * - <strong>\#{target</strong>: will throw std::invalid_argument. + * + * ### Colors & attributes + * + * - <strong>\@{red,blue}</strong>: will write text red on blue background, + * - <strong>\@{default,yellow}</strong>: will write default color text on + * yellow background, + * - <strong>\@{white,black,bold,underline}</strong>: will write white text on + * black in both bold and underline. + */ +std::string format(std::string text, const subst& params = {}); + +// }}} + +// {{{ strip + +/** + * Remove leading and trailing spaces. + * + * \param str the string + * \return the removed white spaces + */ +std::string strip(std::string str) noexcept; + +// }}} + +// {{{ split + +/** + * Split a string by delimiters. + * + * \param list the string to split + * \param delimiters a list of delimiters + * \param max max number of split + * \return a list of string splitted + */ +std::vector<std::string> split(const std::string& list, const std::string& delimiters, int max = -1); + +// }}} + +// {{{ join + +/** + * Join values by a separator and return a string. + * + * \param first the first iterator + * \param last the last iterator + * \param delim the optional delimiter + * \return the string + */ +template <typename InputIt, typename DelimType = char> +std::string join(InputIt first, InputIt last, DelimType delim = ':') +{ + std::ostringstream oss; + + if (first != last) { + oss << *first; + + while (++first != last) + oss << delim << *first; + } + + return oss.str(); +} + +/** + * Overloaded function that takes a container. + * + * \param c the container + * \param delim the optional delimiter + * \return the string + */ +template <typename Container, typename DelimType = char> +std::string join(const Container& c, DelimType delim = ':') +{ + return join(c.begin(), c.end(), delim); +} + +/** + * Convenient overload. + * + * \param list the initializer list + * \param delim the delimiter + * \return the string + */ +template <typename T, typename DelimType = char> +inline std::string join(std::initializer_list<T> list, DelimType delim = ':') +{ + return join(list.begin(), list.end(), delim); +} + +// }}} + +// {{{ is_identifier + +/** + * Check if a string is a valid irccd identifier. + * + * \param name the identifier name + * \return true if is valid + */ +inline bool is_identifier(const std::string& name) +{ + return std::regex_match(name, std::regex("[A-Za-z0-9-_]+")); +} + +// }}} + +// {{{ is_boolean + +/** + * Check if the value is a boolean, 1, yes and true are accepted. + * + * \param value the value + * \return true if is boolean + * \note this function is case-insensitive + */ +bool is_boolean(std::string value) noexcept; + +// }}} + +// {{{ sprintf + +/** + * \cond HIDDEN_SYMBOLS + */ + +namespace detail { + +inline void sprintf(boost::format&) +{ +} + +template <typename Arg, typename... Args> +inline void sprintf(boost::format& fmter, const Arg& arg, const Args&... args) +{ + fmter % arg; + sprintf(fmter, args...); +} + +} // !detail + +/** + * \endcond + */ + +/** + * Convenient wrapper arount boost::format in sprintf style. + * + * This is identical as calling boost::format(format) % arg1 % arg2 % argN. + * + * \param format the format string + * \param args the arguments + * \return the string + */ +template <typename Format, typename... Args> +std::string sprintf(const Format& format, const Args&... args) +{ + boost::format fmter(format); + + detail::sprintf(fmter, args...); + + return fmter.str(); +} + +// }}} + +// {{{ to_int + +/** + * Convert the given string into a signed integer. + * + * \param str the string to convert + * \param min the minimum value allowed + * \param max the maximum value allowed + * \return the value or boost::none if not convertible + */ +template <typename T = int> +boost::optional<T> to_int(const std::string& str, + T min = std::numeric_limits<T>::min(), + T max = std::numeric_limits<T>::max()) noexcept +{ + static_assert(std::is_signed<T>::value, "must be signed"); + + char* end; + auto v = std::strtoll(str.c_str(), &end, 10); + + if (*end != '\0' || v < min || v > max) + return boost::none; + + return static_cast<T>(v); +} + +// }}} + +// {{{ to_uint + +/** + * Convert the given string into a unsigned integer. + * + * \note invalid numbers are valid as well + * \param str the string to convert + * \param min the minimum value allowed + * \param max the maximum value allowed + * \return the value or boost::none if not convertible + */ +template <typename T = unsigned> +boost::optional<T> to_uint(const std::string& str, + T min = std::numeric_limits<T>::min(), + T max = std::numeric_limits<T>::max()) noexcept +{ + static_assert(std::is_unsigned<T>::value, "must be unsigned"); + + char* end; + auto v = std::strtoull(str.c_str(), &end, 10); + + if (*end != '\0' || v < min || v > max) + return boost::none; + + return static_cast<T>(v); +} + +// }}} + +} // !string_util + +} // !irccd + +#endif // !IRCCD_COMMON_STRING_UTIL_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/system.cpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,502 @@ +/* + * system.cpp -- platform dependent functions for system inspection + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cassert> +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <stdexcept> +#include <string> + +#include <boost/dll.hpp> +#include <boost/filesystem.hpp> +#include <boost/predef/os.h> + +#include "sysconfig.hpp" + +#if BOOST_OS_WINDOWS +# include <sys/timeb.h> +# include <shlobj.h> +#else +# include <sys/utsname.h> +# include <sys/types.h> +# include <sys/param.h> +# include <sys/time.h> +# include <unistd.h> +#endif + +#if BOOST_OS_LINUX +# include <sys/sysinfo.h> +#endif + +#if BOOST_OS_MACOS +# include <sys/sysctl.h> +# include <libproc.h> +#endif + +#if defined(HAVE_GETLOGIN) +# include <unistd.h> +#endif + +#include "system.hpp" +#include "string_util.hpp" +#include "xdg.hpp" + +namespace irccd { + +namespace sys { + +namespace { + +// {{{ base_directory + +/* + * base_directory + * ------------------------------------------------------------------ + * + * Get the base program directory. + * + * If irccd has been compiled with relative paths, the base directory is + * evaluated by climbing the `bindir' directory from the executable path. + * + * Otherwise, use the installation prefix. + */ +boost::filesystem::path base_directory() +{ + static const boost::filesystem::path bindir(CMAKE_INSTALL_BINDIR); + static const boost::filesystem::path prefix(CMAKE_INSTALL_PREFIX); + + boost::filesystem::path path("."); + + if (bindir.is_relative()) { + try { + path = boost::dll::program_location(); + path = path.parent_path(); + } catch (...) { + path = "."; + } + + // Compute relative base directory. + for (auto len = std::distance(bindir.begin(), bindir.end()); len > 0; len--) + path = path.parent_path(); + if (path.empty()) + path = "."; + } else + path = prefix; + + return path; +} + +// }}} + +// {{{ system_directory + +/* + * system_directory + * ------------------------------------------------------------------ + * + * Compute the system directory path for the given component. + * + * Referenced by: + * - cachedir, + * - datadir, + * - sysconfigdir, + * - plugindir. + */ +boost::filesystem::path system_directory(const std::string& component) +{ + boost::filesystem::path path(component); + + if (path.is_relative()) + path = base_directory() / component; + + return path.string(); +} + +// }}} + +// {{{ user_config_directory + +/* + * user_config_directory + * ------------------------------------------------------------------ + * + * Get user configuration directory. + * + * Referenced by: config_filenames. + * Requires: + * - Windows: + * - <shlobj.h> + */ +boost::filesystem::path user_config_directory() +{ + boost::filesystem::path path; + +#if BOOST_OS_WINDOWS + char folder[MAX_PATH] = {0}; + + if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, folder) == S_OK) { + path /= folder; + path /= "\\irccd\\config"; + } else + path = "."; +#else + try { + path = xdg().config_home(); + } catch (...) { + path = sys::env("HOME"); + path /= ".config"; + } + + path /= "irccd"; +#endif + + return path; +} + +// }}} + +// {{{ user_plugin_directory + +/* + * user_plugin_directory + * ------------------------------------------------------------------ + * + * Referenced by: plugin_filenames. + * Requires: + * - Windows: + * - <shlobj.h> + * + * Like add user_config_directory but for plugins. + */ +boost::filesystem::path user_plugin_directory() +{ + boost::filesystem::path path; + +#if BOOST_OS_WINDOWS + char folder[MAX_PATH] = {0}; + + if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, folder) == S_OK) { + path /= folder; + path /= "\\irccd\\share"; + } +#else + try { + path = xdg().data_home(); + } catch (...) { + path = sys::env("HOME"); + path /= ".local/share"; + } + + path /= "irccd"; +#endif + + return path / "plugins"; +} + +// }}} + +} // !namespace + +// {{{ set_program_name + +void set_program_name(std::string name) noexcept +{ +#if defined(HAVE_SETPROGNAME) + static std::string save = name; + + setprogname(save.c_str()); +#else + (void)name; +#endif +} + +// }}} + +// {{{ name + +std::string name() +{ +#if BOOST_OS_LINUX + return "Linux"; +#elif BOOST_OS_WINDOWS + return "Windows"; +#elif BOOST_OS_BSD_FREE + return "FreeBSD"; +#elif BOOST_OS_BSD_DRAGONFLY + return "DragonFlyBSD"; +#elif BOOST_OS_BSD_OPEN + return "OpenBSD"; +#elif BOOST_OS_BSD_NET + return "NetBSD"; +#elif BOOST_OS_MACOS + return "macOS"; +#elif BOOST_OS_ANDROID + return "Android"; +#elif BOOST_OS_AIX + return "Aix"; +#elif BOOST_OS_HAIKU + return "Haiku"; +#elif BOOST_OS_IOS + return "iOS"; +#elif BOOST_OS_SOLARIS + return "Solaris"; +#else + return "Unknown"; +#endif +} + +// }}} + +// {{{ version + +/* + * Requires: + * - Windows: + * - <windows.h> + * - Others: + * - <sys/utsname.h> + */ +std::string version() +{ +#if BOOST_OS_WINDOWS + const auto version = GetVersion(); + const auto major = (DWORD)(LOBYTE(LOWORD(version))); + const auto minor = (DWORD)(HIBYTE(LOWORD(version))); + + return std::to_string(major) + "." + std::to_string(minor); +#else + struct utsname uts; + + if (::uname(&uts) < 0) + throw std::runtime_error(std::strerror(errno)); + + return std::string(uts.release); +#endif +} + +// }}} + +// {{{ uptime + +/* + * Requires: + * - Windows: + * - <windows.h> + * - Linux: + * - <sys/sysinfo.h> + * - Mac: + * - <sys/types.h> + * - <sys/sysctl.h> + * - Others: + * - <ctime> + */ +std::uint64_t uptime() +{ +#if BOOST_OS_WINDOWS + return ::GetTickCount64() / 1000; +#elif BOOST_OS_LINUX + struct sysinfo info; + + if (sysinfo(&info) < 0) + throw std::runtime_error(std::strerror(errno)); + + return info.uptime; +#elif BOOST_OS_MACOS + struct timeval boottime; + size_t length = sizeof (boottime); + int mib[2] = { CTL_KERN, KERN_BOOTTIME }; + + if (sysctl(mib, 2, &boottime, &length, nullptr, 0) < 0) + throw std::runtime_error(std::strerror(errno)); + + time_t bsec = boottime.tv_sec, csec = time(nullptr); + + return difftime(csec, bsec); +#else + struct timespec ts; + + if (clock_gettime(CLOCK_UPTIME, &ts) < 0) + throw std::runtime_error(std::strerror(errno)); + + return ts.tv_sec; +#endif +} + +// }}} + +// {{{ ticks + +/* + * Requires: + * - Windows: + * - <sys/timeb.h> + * - Others: + * - <sys/times.h> + */ +std::uint64_t ticks() +{ +#if BOOST_OS_WINDOWS + _timeb tp; + + _ftime(&tp); + + return tp.time * 1000LL + tp.millitm; +#else + struct timeval tp; + + gettimeofday(&tp, NULL); + + return tp.tv_sec * 1000LL + tp.tv_usec / 1000; +#endif +} + +// }}} + +// {{{ home + +/* + * Requires: + * - Windows: + * - <shlobj.h> + */ +std::string home() +{ +#if BOOST_OS_WINDOWS + char path[MAX_PATH]; + + if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path) != S_OK) + return ""; + + return std::string(path); +#else + return env("HOME"); +#endif +} + +// }}} + +// {{{ env + +/* + * Requires: + * - <cstdlib> + */ +std::string env(const std::string& var) +{ + const auto value = std::getenv(var.c_str()); + + if (value == nullptr) + return ""; + + return value; +} + +// }}} + +// {{{ cachedir + +boost::filesystem::path cachedir() +{ + return system_directory(CMAKE_INSTALL_LOCALSTATEDIR) / "cache/irccd"; +} + +// }}} + +// {{{ datadir + +boost::filesystem::path datadir() +{ + return system_directory(CMAKE_INSTALL_DATADIR); +} + +// }}} + +// {{{ sysconfdir + +boost::filesystem::path sysconfdir() +{ + return system_directory(CMAKE_INSTALL_SYSCONFDIR) / "irccd"; +} + +// }}} + +// {{{ plugindir + +boost::filesystem::path plugindir() +{ + return system_directory(CMAKE_INSTALL_LIBDIR) / "irccd"; +} + +// }}} + +// {{{ username + +/* + * Requires: + * - <unistd.h> + */ +std::string username() +{ +#if defined(HAVE_GETLOGIN) + auto v = getlogin(); + + if (v) + return v; +#endif + + return ""; +} + +// }}} + +// {{{ config_filenames + +std::vector<std::string> config_filenames(std::string file) +{ + return { + (user_config_directory() / file).string(), + (sysconfdir() / file).string() + }; +} + +// }}} + +// {{{ plugin_filenames + +std::vector<std::string> plugin_filenames(const std::string& name, + const std::vector<std::string>& extensions) +{ + assert(!extensions.empty()); + + std::vector<std::string> result; + + for (const auto& ext : extensions) + result.push_back((user_plugin_directory() / (name + ext)).string()); + for (const auto& ext : extensions) + result.push_back((plugindir() / (name + ext)).string()); + + return result; +} + +// }}} + +} // !sys + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/system.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,166 @@ +/* + * system.hpp -- platform dependent functions for system inspection + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_SYSTEM_HPP +#define IRCCD_COMMON_SYSTEM_HPP + +/** + * \file system.hpp + * \brief System dependant functions + */ + +#include <cstdint> +#include <string> +#include <vector> + +#include <boost/filesystem.hpp> + +#include "sysconfig.hpp" + +namespace irccd { + +/** + * \brief Namespace for system functions. + */ +namespace sys { + +/** + * Set the program name, needed for some functions or some systems. + * + * \param name the program name + */ +void set_program_name(std::string name) noexcept; + +/** + * Get the system name. + * + * \return the name + */ +std::string name(); + +/** + * Get the system version. + * + * \return the version + */ +std::string version(); + +/** + * Get the number of seconds elapsed since the boottime. + * + * \return the number of seconds + */ +std::uint64_t uptime(); + +/** + * Get the milliseconds elapsed since the application + * startup. + * + * \return the milliseconds + */ +std::uint64_t ticks(); + +/** + * Get an environment variable. + * + * \return the value or empty string + */ +std::string env(const std::string& var); + +/** + * Get home directory usually /home/foo + * + * \return the home directory + */ +std::string home(); + +/** + * Get the cache directory as specified as compile time option + * CMAKE_INSTALL_LOCALSTATEDIR, if the value is absolute, it is returned as-is. + * + * If the component is relative, it is evaluated using the binary executable + * path. + * + * \return the evaluated cache directory. + * \see datadir + * \see configdir + */ +boost::filesystem::path cachedir(); + +/** + * Like cachedir but for CMAKE_INSTALL_DATADIR. + * + * \return the evaluated data directory. + * \see cachedir + * \see datadir + */ +boost::filesystem::path datadir(); + +/** + * Like cachedir but for CMAKE_INSTALL_SYSCONFDIR. + * + * \return the evaluated config directory. + * \see cachedir + * \see datadir + * \note use config_filenames for irccd.conf, irccdctl.conf files + */ + boost::filesystem::path sysconfdir(); + +/** + * Like cachedir but for CMAKE_INSTALL_LIBDIR. + * + * \return the evaluated system plugin directory. + * \see cachedir + * \see datadir + */ +boost::filesystem::path plugindir(); + +/** + * Get user account login or empty if not available. + * + * \return the user account name + */ +std::string username(); + +/** + * Construct a list of paths to read configuration files from. + * + * This function does not test the presence of the files as a condition race + * may occur. + * + * The caller is responsible of opening files for each path. + * + * \param file the filename to append for convenience + * \return the list of paths to check in order + */ +std::vector<std::string> config_filenames(std::string file); + +/** + * Construct a list of paths for reading plugins. + * + * \param name the plugin id (without extension) + * \param extensions the list of extensions supported + */ +std::vector<std::string> plugin_filenames(const std::string& name, + const std::vector<std::string>& extensions); + +} // !sys + +} // !irccd + +#endif // !IRCCD_COMMON_SYSTEM_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/tls_acceptor.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,96 @@ +/* + * tls_acceptor.hpp -- TLS/SSL acceptors + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_TLS_ACCEPTOR_HPP +#define IRCCD_COMMON_TLS_ACCEPTOR_HPP + +/** + * \file tls_acceptor.hpp + * \brief TLS/SSL acceptors. + */ + +#include <irccd/sysconfig.hpp> + +#if defined(IRCCD_HAVE_SSL) + +#include "socket_acceptor.hpp" +#include "tls_stream.hpp" + +namespace irccd { + +namespace io { + +/** + * \brief TLS/SSL acceptors. + * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) + */ +template <typename Protocol = boost::asio::ip::tcp> +class tls_acceptor : public socket_acceptor<Protocol> { +private: + using socket = typename Protocol::socket; + + boost::asio::ssl::context context_; + +public: + /** + * Construct a secure layer transport server. + * + * \param context the SSL context + * \param args the socket_acceptor arguments + */ + template <typename... Args> + inline tls_acceptor(boost::asio::ssl::context context, Args&&... args) + : socket_acceptor<Protocol>(std::forward<Args>(args)...) + , context_(std::move(context)) + { + } + + /** + * \copydoc acceptor::accept + */ + void accept(accept_handler handler) override; +}; + +template <typename Protocol> +void tls_acceptor<Protocol>::accept(accept_handler handler) +{ + assert(handler); + + auto client = std::make_shared<tls_stream<socket>>(this->get_acceptor().get_io_service(), this->context_); + + socket_acceptor<Protocol>::do_accept(client->get_socket().lowest_layer(), [handler, client] (auto code) { + using boost::asio::ssl::stream_base; + + if (code) { + handler(code, nullptr); + return; + } + + client->get_socket().async_handshake(stream_base::server, [handler, client] (auto code) { + handler(detail::convert(code), code ? nullptr : std::move(client)); + }); + }); +} + +} // !io + +} // !irccd + +#endif // !IRCCD_HAVE_SSL + +#endif // !IRCCD_COMMON_TLS_ACCEPTOR_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/tls_connector.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,95 @@ +/* + * tls_connector.hpp -- TLS/SSL connectors + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_TLS_CONNECTOR_HPP +#define IRCCD_COMMON_TLS_CONNECTOR_HPP + +/** + * \file tls_connector.hpp + * \brief TLS/SSL connectors. + */ + +#include <irccd/sysconfig.hpp> + +#if defined(IRCCD_HAVE_SSL) + +#include "socket_connector.hpp" +#include "tls_stream.hpp" + +namespace irccd { + +namespace io { + +/** + * \brief TLS/SSL connectors. + * \tparam Protocol a Boost.Asio compatible protocol (e.g. ip::tcp) + */ +template <typename Protocol = boost::asio::ip::tcp> +class tls_connector : public socket_connector<Protocol> { +private: + boost::asio::ssl::context context_; + +public: + /** + * Construct a secure layer transport server. + * + * \param context the SSL context + * \param args the arguments to socket_connector<Socket> constructor + */ + template <typename... Args> + inline tls_connector(boost::asio::ssl::context context, Args&&... args) + : socket_connector<Protocol>(std::forward<Args>(args)...) + , context_(std::move(context)) + { + } + + /** + * \copydoc socket_connector::connect + */ + void connect(connect_handler handler) override; +}; + +template <typename Protocol> +void tls_connector<Protocol>::connect(connect_handler handler) +{ + using boost::asio::ssl::stream_base; + using socket = typename Protocol::socket; + + assert(handler); + + const auto stream = std::make_shared<tls_stream<socket>>(this->get_io_service(), context_); + + socket_connector<Protocol>::do_connect(stream->get_socket().lowest_layer(), [this, handler, stream] (auto code) { + if (code) { + handler(code, nullptr); + return; + } + + stream->get_socket().async_handshake(stream_base::client, [handler, stream] (auto code) { + handler(detail::convert(code), code ? nullptr : std::move(stream)); + }); + }); +} + +} // !io + +} // !irccd + +#endif // !IRCCD_HAVE_SSL + +#endif // !IRCCD_COMMON_TLS_CONNECTOR_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/tls_stream.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,64 @@ +/* + * tls_stream.hpp -- TLS/SSL streams + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_COMMON_TLS_STREAM_HPP +#define IRCCD_COMMON_TLS_STREAM_HPP + +/** + * \file tls_stream.hpp + * \brief TLS/SSL streams. + */ + +#include <irccd/sysconfig.hpp> + +#if defined(IRCCD_HAVE_SSL) + +#include <boost/asio/ssl.hpp> + +#include "socket_stream.hpp" + +namespace irccd { + +namespace io { + +/** + * \brief TLS/SSL streams. + * \tparam Socket the Boost.Asio compatible socket. + */ +template <typename Socket = boost::asio::ip::tcp::socket> +class tls_stream : public socket_stream<boost::asio::ssl::stream<Socket>> { +public: + /** + * Constructor. + * + * \param args the arguments to boost::asio::ssl::stream<Socket> + */ + template <typename... Args> + inline tls_stream(Args&&... args) + : socket_stream<boost::asio::ssl::stream<Socket>>(std::forward<Args>(args)...) + { + } +}; + +} // !io + +} // !irccd + +#endif // !IRCCD_HAVE_SSL + +#endif // !IRCCD_COMMON_TLS_STREAM_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-core/irccd/xdg.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,194 @@ +/* + * xdg.hpp -- XDG directory specifications + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_XDG_HPP +#define IRCCD_XDG_HPP + +/** + * \file xdg.hpp + * \brief XDG directory specifications. + * \author David Demelier <markand@malikana.fr> + */ + +#include <cstdlib> +#include <sstream> +#include <stdexcept> +#include <string> +#include <vector> + +namespace irccd { + +/** + * \brief XDG directory specifications. + * + * Read and get XDG directories. + * + * This file should compiles on Windows to facilitate portability but its + * functions must not be used. + */ +class xdg { +private: + std::string config_home_; + std::string data_home_; + std::string cache_home_; + std::string runtime_dir_; + std::vector<std::string> config_dirs_; + std::vector<std::string> data_dirs_; + + inline bool is_absolute(const std::string& path) const noexcept + { + return path.length() > 0 && path[0] == '/'; + } + + std::vector<std::string> split(const std::string& arg) const + { + std::stringstream iss(arg); + std::string item; + std::vector<std::string> elems; + + while (std::getline(iss, item, ':')) { + if (is_absolute(item)) + elems.push_back(item); + } + + return elems; + } + + std::string env_or_home(const std::string& var, const std::string& repl) const + { + auto value = std::getenv(var.c_str()); + + if (value == nullptr || !is_absolute(value)) { + auto home = std::getenv("HOME"); + + if (home == nullptr) + throw std::runtime_error("could not get home directory"); + + return std::string(home) + "/" + repl; + } + + return value; + } + + std::vector<std::string> list_or_defaults(const std::string& var, + const std::vector<std::string>& list) const + { + auto value = std::getenv(var.c_str()); + + if (!value) + return list; + + // No valid item at all? Use defaults. + auto result = split(value); + + return (result.size() == 0) ? list : result; + } + +public: + /** + * Open an xdg instance and load directories. + * + * \throw std::runtime_error on failures + */ + xdg() + { + config_home_ = env_or_home("XDG_CONFIG_HOME", ".config"); + data_home_ = env_or_home("XDG_DATA_HOME", ".local/share"); + cache_home_ = env_or_home("XDG_CACHE_HOME", ".cache"); + + config_dirs_ = list_or_defaults("XDG_CONFIG_DIRS", { "/etc/xdg" }); + data_dirs_ = list_or_defaults("XDG_DATA_DIRS", { "/usr/local/share", "/usr/share" }); + + /* + * Runtime directory is a special case and does not have a replacement, + * the application should manage this by itself. + */ + auto runtime = std::getenv("XDG_RUNTIME_DIR"); + + if (runtime && is_absolute(runtime)) + runtime_dir_ = runtime; + } + + /** + * Get the config directory. ${XDG_CONFIG_HOME} or ${HOME}/.config + * + * \return the config directory + */ + inline const std::string& config_home() const noexcept + { + return config_home_; + } + + /** + * Get the data directory. ${XDG_DATA_HOME} or ${HOME}/.local/share + * + * \return the data directory + */ + inline const std::string& data_home() const noexcept + { + return data_home_; + } + + /** + * Get the cache directory. ${XDG_CACHE_HOME} or ${HOME}/.cache + * + * \return the cache directory + */ + inline const std::string& cache_home() const noexcept + { + return cache_home_; + } + + /** + * Get the runtime directory. + * + * There is no replacement for XDG_RUNTIME_DIR, if it is not set, an empty + * value is returned and the user is responsible of using something else. + * + * \return the runtime directory + */ + inline const std::string& runtime_dir() const noexcept + { + return runtime_dir_; + } + + /** + * Get the standard config directories. ${XDG_CONFIG_DIRS} or { "/etc/xdg" } + * + * \return the list of config directories + */ + inline const std::vector<std::string>& config_dirs() const noexcept + { + return config_dirs_; + } + + /** + * Get the data directories. ${XDG_DATA_DIRS} or { "/usr/local/share", + * "/usr/share" } + * + * \return the list of data directories + */ + inline const std::vector<std::string>& data_dirs() const noexcept + { + return data_dirs_; + } +}; + +} // !irccd + +#endif // !IRCCD_XDG_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-ctl/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,43 @@ +# +# CMakeLists.txt -- CMake build system for irccd +# +# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +project(libirccd-ctl) + +set( + HEADERS + ${libirccd-ctl_SOURCE_DIR}/irccd/ctl/controller.hpp +) + +set( + SOURCES + ${libirccd-ctl_SOURCE_DIR}/irccd/ctl/controller.cpp +) + +irccd_define_library( + TARGET libirccd-ctl + EXPORT + HEADERS ${HEADERS} + HEADERS_DIRECTORY irccd/ctl + SOURCES + ${libirccd-ctl_SOURCE_DIR}/CMakeLists.txt + ${SOURCES} + LIBRARIES + libirccd + PUBLIC_INCLUDES + $<BUILD_INTERFACE:${libirccd-ctl_SOURCE_DIR}> +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-ctl/irccd/ctl/controller.cpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,143 @@ +/* + * controller.cpp -- main irccdctl interface + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cassert> + +#include <irccd/sysconfig.hpp> +#include <irccd/json_util.hpp> + +#include <irccd/daemon/irccd.hpp> +#include <irccd/daemon/server.hpp> +#include <irccd/daemon/plugin.hpp> +#include <irccd/daemon/rule.hpp> + +#include "controller.hpp" + +namespace irccd { + +namespace ctl { + +void controller::authenticate(connect_handler handler, nlohmann::json info) +{ + const auto cmd = nlohmann::json::object({ + { "command", "auth" }, + { "password", password_ } + }); + + write(cmd, [handler, info, this] (auto code) { + if (code) { + handler(std::move(code), nullptr); + return; + } + + read([handler, info] (auto code, auto) { + handler(std::move(code), std::move(info)); + }); + }); +} + +void controller::verify(connect_handler handler) +{ + read([handler, this] (auto code, auto message) { + if (code) { + handler(std::move(code), std::move(message)); + return; + } + + const json_util::document doc(message); + const auto program = doc.get<std::string>("program"); + const auto major = doc.get<int>("major"); + + if (!program && *program != "irccd") + handler(irccd_error::not_irccd, std::move(message)); + else if (major && *major != IRCCD_VERSION_MAJOR) + handler(irccd_error::incompatible_version, std::move(message)); + else { + if (!password_.empty()) + authenticate(std::move(handler), message); + else + handler(code, std::move(message)); + } + }); +} + +void controller::connect(connect_handler handler) +{ + assert(handler); + + connector_->connect([handler, this] (auto code, auto stream) { + if (code) + handler(std::move(code), nullptr); + else { + stream_ = std::move(stream); + verify(std::move(handler)); + } + }); +} + +void controller::read(io::read_handler handler) +{ + assert(handler); + assert(stream_); + + auto stream = stream_; + + stream_->read([this, handler, stream] (auto code, auto msg) { + if (code) { + stream_ = nullptr; + handler(std::move(code), std::move(msg)); + return; + } + + const json_util::document doc(msg); + const auto e = doc.get<int>("error"); + const auto c = doc.get<std::string>("errorCategory"); + + if (e && c) { + if (*c == "irccd") + code = make_error_code(static_cast<irccd_error::error>(*e)); + else if (*c == "server") + code = make_error_code(static_cast<server_error::error>(*e)); + else if (*c == "plugin") + code = make_error_code(static_cast<plugin_error::error>(*e)); + else if (*c == "rule") + code = make_error_code(static_cast<rule_error::error>(*e)); + } + + handler(std::move(code), std::move(msg)); + }); +} + +void controller::write(nlohmann::json message, io::write_handler handler) +{ + assert(message.is_object()); + assert(stream_); + + auto stream = stream_; + + stream_->write(std::move(message), [this, stream, handler] (auto code) { + if (code) + stream_ = nullptr; + if (handler) + handler(std::move(code)); + }); +} + +} // !ctl + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd-ctl/irccd/ctl/controller.hpp Sat Jul 07 15:40:46 2018 +0200 @@ -0,0 +1,136 @@ +/* + * controller.hpp -- main irccdctl interface + * + * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_CTL_CONTROLLER_HPP +#define IRCCD_CTL_CONTROLLER_HPP + +/** + * \file controller.hpp + * \brief Main irccdctl interface. + */ + +#include <cassert> + +#include <irccd/connector.hpp> +#include <irccd/stream.hpp> + +namespace irccd { + +namespace ctl { + +/** + * \brief Main irccdctl interface. + * + * This class is an easy frontend to issue commands to irccd, it uses an + * independant connection to perform the requests. + * + * This class is responsible of doing initial connection, performing checks and + * optional authentication. + * + * It is implemented in mind that connection are asynchronous even though this + * is not necessary. + */ +class controller { +public: + /** + * Connection completion handler. + * + * This callback is called when connection has been completed or failed. In + * both case, the error code is set and the JSON object may contain the + * irccd program information. + */ + using connect_handler = std::function<void (std::error_code, nlohmann::json)>; + +private: + std::unique_ptr<io::connector> connector_; + std::shared_ptr<io::stream> stream_; + std::string password_; + + void authenticate(connect_handler, nlohmann::json); + void verify(connect_handler); + +public: + /** + * Construct the controller with its connection. + * + * \pre connector != nullptr + * \ + * \note no connect attempt is done + */ + inline controller(std::unique_ptr<io::connector> connector) noexcept + : connector_(std::move(connector)) + { + assert(connector_); + } + + /** + * Get the optional password set. + * + * \return the password + */ + inline const std::string& get_password() const noexcept + { + return password_; + } + + /** + * Set an optional password. + * + * An empty password means no authentication (default). + * + * \param password the password + * \note this must be called before connect + */ + inline void set_password(std::string password) noexcept + { + password_ = std::move(password); + } + + /** + * Attempt to connect to the irccd daemon. + * + * \pre handler != nullptr + * \param handler the handler + */ + void connect(connect_handler handler); + + /** + * Queue a receive operation, if receive operations are already running, it + * is queued and ran once ready. + * + * \pre handler != nullptr + * \param handler the recv handler + */ + void read(io::read_handler handler); + + /** + * Queue a send operation, if receive operations are already running, it is + * queued and ran once ready. + * + * \pre message.is_object() + * \param message the JSON message + * \param handler the optional completion handler + */ + void write(nlohmann::json message, io::write_handler handler = nullptr); +}; + +} // !ctl + +} // !irccd + +#endif // !IRCCD_CTL_CONTROLLER_HPP
--- a/libirccd-test/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/libirccd-test/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -53,7 +53,7 @@ LIBRARIES ${LIBRARIES} libirccd - libirccdctl + libirccd-ctl PUBLIC_INCLUDES $<BUILD_INTERFACE:${libirccd-test_SOURCE_DIR}> FLAGS
--- a/libirccd/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/libirccd/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -123,7 +123,7 @@ ${libirccd_SOURCE_DIR}/CMakeLists.txt ${SOURCES} LIBRARIES - libcommon + libirccd-core PUBLIC_INCLUDES $<BUILD_INTERFACE:${libirccd_SOURCE_DIR}> )
--- a/libirccdctl/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -# -# CMakeLists.txt -- CMake build system for irccd -# -# Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# - -project(libirccdctl) - -set( - HEADERS - ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.hpp -) - -set( - SOURCES - ${libirccdctl_SOURCE_DIR}/irccd/ctl/controller.cpp -) - -irccd_define_library( - TARGET libirccdctl - EXPORT - HEADERS ${HEADERS} - HEADERS_DIRECTORY irccd/ctl - SOURCES - ${libirccdctl_SOURCE_DIR}/CMakeLists.txt - ${SOURCES} - LIBRARIES - libirccd - PUBLIC_INCLUDES - $<BUILD_INTERFACE:${libirccdctl_SOURCE_DIR}> -)
--- a/libirccdctl/irccd/ctl/controller.cpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -/* - * controller.cpp -- main irccdctl interface - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <cassert> - -#include <irccd/sysconfig.hpp> -#include <irccd/json_util.hpp> - -#include <irccd/daemon/irccd.hpp> -#include <irccd/daemon/server.hpp> -#include <irccd/daemon/plugin.hpp> -#include <irccd/daemon/rule.hpp> - -#include "controller.hpp" - -namespace irccd { - -namespace ctl { - -void controller::authenticate(connect_handler handler, nlohmann::json info) -{ - const auto cmd = nlohmann::json::object({ - { "command", "auth" }, - { "password", password_ } - }); - - write(cmd, [handler, info, this] (auto code) { - if (code) { - handler(std::move(code), nullptr); - return; - } - - read([handler, info] (auto code, auto) { - handler(std::move(code), std::move(info)); - }); - }); -} - -void controller::verify(connect_handler handler) -{ - read([handler, this] (auto code, auto message) { - if (code) { - handler(std::move(code), std::move(message)); - return; - } - - const json_util::document doc(message); - const auto program = doc.get<std::string>("program"); - const auto major = doc.get<int>("major"); - - if (!program && *program != "irccd") - handler(irccd_error::not_irccd, std::move(message)); - else if (major && *major != IRCCD_VERSION_MAJOR) - handler(irccd_error::incompatible_version, std::move(message)); - else { - if (!password_.empty()) - authenticate(std::move(handler), message); - else - handler(code, std::move(message)); - } - }); -} - -void controller::connect(connect_handler handler) -{ - assert(handler); - - connector_->connect([handler, this] (auto code, auto stream) { - if (code) - handler(std::move(code), nullptr); - else { - stream_ = std::move(stream); - verify(std::move(handler)); - } - }); -} - -void controller::read(io::read_handler handler) -{ - assert(handler); - assert(stream_); - - auto stream = stream_; - - stream_->read([this, handler, stream] (auto code, auto msg) { - if (code) { - stream_ = nullptr; - handler(std::move(code), std::move(msg)); - return; - } - - const json_util::document doc(msg); - const auto e = doc.get<int>("error"); - const auto c = doc.get<std::string>("errorCategory"); - - if (e && c) { - if (*c == "irccd") - code = make_error_code(static_cast<irccd_error::error>(*e)); - else if (*c == "server") - code = make_error_code(static_cast<server_error::error>(*e)); - else if (*c == "plugin") - code = make_error_code(static_cast<plugin_error::error>(*e)); - else if (*c == "rule") - code = make_error_code(static_cast<rule_error::error>(*e)); - } - - handler(std::move(code), std::move(msg)); - }); -} - -void controller::write(nlohmann::json message, io::write_handler handler) -{ - assert(message.is_object()); - assert(stream_); - - auto stream = stream_; - - stream_->write(std::move(message), [this, stream, handler] (auto code) { - if (code) - stream_ = nullptr; - if (handler) - handler(std::move(code)); - }); -} - -} // !ctl - -} // !irccd
--- a/libirccdctl/irccd/ctl/controller.hpp Sat Jul 07 14:03:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -/* - * controller.hpp -- main irccdctl interface - * - * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef IRCCD_CTL_CONTROLLER_HPP -#define IRCCD_CTL_CONTROLLER_HPP - -/** - * \file controller.hpp - * \brief Main irccdctl interface. - */ - -#include <cassert> - -#include <irccd/connector.hpp> -#include <irccd/stream.hpp> - -namespace irccd { - -namespace ctl { - -/** - * \brief Main irccdctl interface. - * - * This class is an easy frontend to issue commands to irccd, it uses an - * independant connection to perform the requests. - * - * This class is responsible of doing initial connection, performing checks and - * optional authentication. - * - * It is implemented in mind that connection are asynchronous even though this - * is not necessary. - */ -class controller { -public: - /** - * Connection completion handler. - * - * This callback is called when connection has been completed or failed. In - * both case, the error code is set and the JSON object may contain the - * irccd program information. - */ - using connect_handler = std::function<void (std::error_code, nlohmann::json)>; - -private: - std::unique_ptr<io::connector> connector_; - std::shared_ptr<io::stream> stream_; - std::string password_; - - void authenticate(connect_handler, nlohmann::json); - void verify(connect_handler); - -public: - /** - * Construct the controller with its connection. - * - * \pre connector != nullptr - * \ - * \note no connect attempt is done - */ - inline controller(std::unique_ptr<io::connector> connector) noexcept - : connector_(std::move(connector)) - { - assert(connector_); - } - - /** - * Get the optional password set. - * - * \return the password - */ - inline const std::string& get_password() const noexcept - { - return password_; - } - - /** - * Set an optional password. - * - * An empty password means no authentication (default). - * - * \param password the password - * \note this must be called before connect - */ - inline void set_password(std::string password) noexcept - { - password_ = std::move(password); - } - - /** - * Attempt to connect to the irccd daemon. - * - * \pre handler != nullptr - * \param handler the handler - */ - void connect(connect_handler handler); - - /** - * Queue a receive operation, if receive operations are already running, it - * is queued and ran once ready. - * - * \pre handler != nullptr - * \param handler the recv handler - */ - void read(io::read_handler handler); - - /** - * Queue a send operation, if receive operations are already running, it is - * queued and ran once ready. - * - * \pre message.is_object() - * \param message the JSON message - * \param handler the optional completion handler - */ - void write(nlohmann::json message, io::write_handler handler = nullptr); -}; - -} // !ctl - -} // !irccd - -#endif // !IRCCD_CTL_CONTROLLER_HPP
--- a/tests/src/libirccd/command-plugin-config/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-plugin-config/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-plugin-config SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-plugin-info/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-plugin-info/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-plugin-info SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-plugin-list/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-plugin-list/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-plugin-list SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-plugin-load/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-plugin-load/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-plugin-load SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-plugin-reload/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-plugin-reload/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-plugin-reload SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-plugin-unload/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-plugin-unload/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-plugin-unload SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-rule-add/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-rule-add/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-rule-add SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-rule-edit/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-rule-edit/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-rule-edit SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-rule-info/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-rule-info/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-rule-info SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-rule-list/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-rule-list/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-rule-list SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-rule-move/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-rule-move/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-rule-move SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-rule-remove/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-rule-remove/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-rule-remove SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-connect/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-connect/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-connect SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-disconnect/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-disconnect/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-disconnect SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-info/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-info/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-info SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-invite/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-invite/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-invite SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-join/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-join/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-join SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-kick/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-kick/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-kick SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-list/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-list/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-list SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-me/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-me/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-me SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-message/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-message/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-message SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-mode/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-mode/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-mode SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-nick/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-nick/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-nick SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-notice/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-notice/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-notice SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-part/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-part/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-part SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-reconnect/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-reconnect/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-reconnect SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )
--- a/tests/src/libirccd/command-server-topic/CMakeLists.txt Sat Jul 07 14:03:04 2018 +0200 +++ b/tests/src/libirccd/command-server-topic/CMakeLists.txt Sat Jul 07 15:40:46 2018 +0200 @@ -19,5 +19,5 @@ irccd_define_test( NAME command-server-topic SOURCES main.cpp - LIBRARIES libirccd libirccdctl + LIBRARIES libirccd libirccd-ctl )