From d2051dcb28a84c75f3db6625dfebb57c4d00be7d Mon Sep 17 00:00:00 2001 From: Peter Sykora Date: Fri, 20 Aug 2021 17:58:25 +0200 Subject: [PATCH] Initial import --- .gitignore | 1 + CMakeLists.txt | 17 + CMakeProject1/CCServer.cpp | 857 ++++++++++++++++++++++++++++++++ CMakeProject1/CCServer.h | 27 + CMakeProject1/CMakeLists.txt | 30 ++ CMakeProject1/CMakeProject1.cpp | 62 +++ CMakeProject1/CMakeProject1.h | 8 + CMakeProject1/CryptoUtils.cpp | 92 ++++ CMakeProject1/CryptoUtils.h | 10 + CMakeProject1/Makefile | 180 +++++++ CMakeProject1/conanfile.txt | 10 + CMakeSettings.json | 88 ++++ 12 files changed, 1382 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CMakeProject1/CCServer.cpp create mode 100644 CMakeProject1/CCServer.h create mode 100644 CMakeProject1/CMakeLists.txt create mode 100644 CMakeProject1/CMakeProject1.cpp create mode 100644 CMakeProject1/CMakeProject1.h create mode 100644 CMakeProject1/CryptoUtils.cpp create mode 100644 CMakeProject1/CryptoUtils.h create mode 100644 CMakeProject1/Makefile create mode 100644 CMakeProject1/conanfile.txt create mode 100644 CMakeSettings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a3417b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/out/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c87f5f9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +# CMakeList.txt : Top-level CMake project file, do global configuration +# and include sub-projects here. +# +cmake_minimum_required (VERSION 3.8) + +if (${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows") + set (CMAKE_SYSTEM_VERSION 8.1 CACHE TYPE INTERNAL FORCE) #Force 8.1 SDK, to keep it compatible with win7 +endif() + +project ("CMakeProject1") + +IF(WIN32) +ADD_DEFINITIONS(/bigobj) +ENDIF(WIN32) + +# Include sub-projects. +add_subdirectory ("CMakeProject1") diff --git a/CMakeProject1/CCServer.cpp b/CMakeProject1/CCServer.cpp new file mode 100644 index 0000000..f58c2f2 --- /dev/null +++ b/CMakeProject1/CCServer.cpp @@ -0,0 +1,857 @@ +#include "CCServer.h" + +#include "CryptoUtils.h" +//------------------------------------------------------------------------------ +// +// Based on Beast example: Advanced server +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = boost::filesystem; // from + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace net = boost::asio; // from +using tcp = boost::asio::ip::tcp; // from + +static constexpr auto APIS_UINT8_MIN = std::numeric_limits::min(); +static constexpr auto APIS_UINT8_MAX = std::numeric_limits::max(); + +#define MAXHESLO 225 +#define MAX_DEKODHESLO (MAXHESLO-3) + +#define KodcharToChar(znak2,poloha,posun) static_cast(-(password[poloha])+(~(znak2))-(posun)) + +namespace +{ + +static const std::string cryptFileHeader("##CAPISF## Copyright (c) 2003 APIS spol. s r.o. SLOVAKIA~"); +//static const std::string passReal("flckdscwjregfvfgbujvbnqajmadmgfjmhfmghplgghesrrhsufservvysfidgfysdnmsybsaftoprecofoplyrmnfsvcapridssedalenttrmfusposvresalbostreqnnapswolnfrdweneplmnzcavsfgusqootrpqaressnvclpacfdrgesklojphsdqtyczvxcbfhtryntinokusgkhjgchtx"); +static const uint8_t passHidden2[] = { 0x89, 0xdb, 0x2c, 0x79, 0xd6, 0xa5, 0x7a, 0x7b, 0x6d, 0xae, 0x67, 0xee, 0xb2, 0x9a, 0x2c, 0xbe, 0xb7, 0xac, 0x6a, 0x56, 0xe8, 0xb2, 0xda, 0xde, 0xaa, 0x79, 0xda, 0xa6, 0xcc, 0x28, 0x96, 0x77, 0xeb, 0x77, 0x07, 0xa7, 0x7a, 0x99, 0x66, 0x9f, 0x37, 0x1a, 0xbe, 0xc7, 0xe0, 0xba, 0xca, 0xa8, 0xa2, 0xda, 0xe9, 0xa9, 0xaa, 0xde, 0xb2, 0xc9, 0xef, 0x72, 0x5a, 0x5a, 0x71, 0xf7, 0x6b, 0x81, 0xeb, 0x24, 0x96, 0x88, 0xe9, 0x86, 0xc7, 0x6a, 0xb7, 0x27, 0x33, 0xbf, 0x17, 0x1b, 0x7e, 0x1b, 0x6b, 0xca, 0x7b, 0x62, 0x9e, 0x89, 0x2e, 0xb2, 0x09, 0x21, 0x8e, 0x07, 0x21, 0xb7, 0x10, 0x34 }; + +class apis_read_filter +{ +public: + typedef char char_type; + explicit apis_read_filter(size_t initialPosition) + : m_position(initialPosition) + { } + + struct category + : boost::iostreams::input + , boost::iostreams::filter_tag + , boost::iostreams::multichar_tag + , boost::iostreams::optimally_buffered_tag + {}; + + std::streamsize optimal_buffer_size() const { return 0; } + + template + std::streamsize read(Source& src, char_type* s, std::streamsize n) + { + std::streamsize result = boost::iostreams::read(src, s, n); + if (result == -1) + { + return -1; + } + + int dwPacketPos = ((m_position / MAX_DEKODHESLO) % 29) + 2; + int iPoloha = m_position % MAX_DEKODHESLO; + + m_position += static_cast(result); + + for (int i = 0; i < result; i++) + { + if (iPoloha == MAX_DEKODHESLO) + { + iPoloha = 0; + dwPacketPos++; + } + if (dwPacketPos >= 31) + { + dwPacketPos = 2; + } + + static const uint8_t passHidden[] = { 0x7e, 0x57, 0x24, 0x76, 0xc7, 0x30, 0x8e, 0xb7, 0xa0, 0x7e, 0xf7, 0xe0, 0x6e, 0xe8, 0xef, 0x6e, 0x7a, 0x9a, 0x8e, 0x66, 0x9d, 0x9a, 0x07, 0xe3, 0x9a, 0x17, 0xe6, 0x82, 0x1a, 0x65, 0x82, 0x08, 0x5e, 0xb2, 0xba, 0xe1, 0xb2, 0xe7, 0xec, 0x7a, 0xbb, 0xef, 0xca, 0xc7, 0xe2, 0x76, 0x07, 0xf2, 0xb1, 0xd9, 0xe6, 0xb3, 0x26, 0xec, 0x69, 0xfb, 0x68, 0xa6, 0xb7, 0x9c, 0xa1, 0xfa, 0x29, 0x97, 0x2a, 0xe6, 0x9d, 0xfb, 0x2f, 0x71, 0xaa, 0x6b }; + if (iPoloha < sizeof(passHidden) * 4 / 3) + { + static const auto password = base64Encode(passHidden, sizeof(passHidden)); + s[i] = KodcharToChar(s[i], iPoloha, iPoloha + dwPacketPos); + } + else + { + static const auto password = base64Encode(passHidden2, sizeof(passHidden2)); + s[i] = KodcharToChar(s[i], iPoloha - sizeof(passHidden) * 4 / 3, iPoloha + dwPacketPos); + } + ++iPoloha; + } + + return result; + } +private: + size_t m_position; +}; + +void decryptBuffer(std::vector& buffer) +{ + bool encrypted = false; + if (buffer.size() >= cryptFileHeader.size()) + { + encrypted = std::equal(cryptFileHeader.begin(), cryptFileHeader.end(), buffer.begin()); + } + if (!encrypted) + { + return; + } + + boost::iostreams::array_source src{ (const char*)buffer.data() + cryptFileHeader.size(), buffer.size() - cryptFileHeader.size()}; + boost::iostreams::filtering_istream is; + is.push(apis_read_filter({ cryptFileHeader.size() })); + is.push(src); + + std::vector decrypted(buffer.size() - cryptFileHeader.size()); + is.read((char*)decrypted.data(), decrypted.size()); + if (decrypted.size() > sizeof(uint32_t)) + { + auto headerLen = reinterpret_cast(decrypted.data())[0]; + if (decrypted.size() > headerLen) + { + decrypted.erase(decrypted.begin(), decrypted.begin() + headerLen); + } + } + buffer = decrypted; +} + +class etag_cache +{ +public: + void insert(boost::beast::string_view path, boost::beast::string_view etag) + { + std::unique_lock lock(mutex_); + map_.emplace(path, etag); + } + + bool check_path(boost::beast::string_view path, boost::beast::string_view etag) const + { + std::shared_lock lock(mutex_); + auto it = map_.find(static_cast(path)); + if (it != map_.end() && it->second == etag) + { + return true; + } + + return false; + } + + void clear() + { + std::unique_lock lock(mutex_); + map_.clear(); + } + +private: + mutable std::shared_mutex mutex_; + std::unordered_map map_; +}; + +// Return a reasonable mime type based on the extension of a file. +beast::string_view +mime_type(beast::string_view path) +{ + using beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if (pos == beast::string_view::npos) + return beast::string_view{}; + return path.substr(pos); + }(); + if (iequals(ext, ".htm")) return "text/html"; + if (iequals(ext, ".html")) return "text/html"; + if (iequals(ext, ".php")) return "text/html"; + if (iequals(ext, ".css")) return "text/css"; + if (iequals(ext, ".txt")) return "text/plain"; + if (iequals(ext, ".js")) return "application/javascript"; + if (iequals(ext, ".json")) return "application/json"; + if (iequals(ext, ".xml")) return "application/xml"; + if (iequals(ext, ".swf")) return "application/x-shockwave-flash"; + if (iequals(ext, ".flv")) return "video/x-flv"; + if (iequals(ext, ".png")) return "image/png"; + if (iequals(ext, ".jpe")) return "image/jpeg"; + if (iequals(ext, ".jpeg")) return "image/jpeg"; + if (iequals(ext, ".jpg")) return "image/jpeg"; + if (iequals(ext, ".gif")) return "image/gif"; + if (iequals(ext, ".bmp")) return "image/bmp"; + if (iequals(ext, ".ico")) return "image/vnd.microsoft.icon"; + if (iequals(ext, ".tiff")) return "image/tiff"; + if (iequals(ext, ".tif")) return "image/tiff"; + if (iequals(ext, ".svg")) return "image/svg+xml"; + if (iequals(ext, ".svgz")) return "image/svg+xml"; + return "application/text"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + beast::string_view base, + beast::string_view path) +{ + if (base.empty()) + return std::string(path); + std::string result(base); +#ifdef BOOST_MSVC + char constexpr path_separator = '\\'; + if (result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for (auto& c : result) + if (c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if (result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> + void + handle_request( + beast::string_view doc_root, + etag_cache& cache, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](beast::string_view why) + { + http::response res{ http::status::bad_request, req.version() }; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = std::string(why); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](beast::string_view target) + { + http::response res{ http::status::not_found, req.version() }; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = "The resource '" + std::string(target) + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](beast::string_view what) + { + http::response res{ http::status::internal_server_error, req.version() }; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = "An error occurred: '" + std::string(what) + "'"; + res.prepare_payload(); + return res; + }; + + // Returns a not modified response + auto const not_modified = + [&req](boost::beast::string_view etag) + { + http::response res{ http::status::not_modified, req.version() }; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::etag, etag); + res.keep_alive(req.keep_alive()); + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if (req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if (req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + auto it = req.find(http::field::if_none_match); + if (it != req.end()) + { + auto etags = std::string(it->value()); + + typedef boost::tokenizer> Tokenizer; + boost::char_separator sep(", "); + Tokenizer tok{ etags, sep }; + for (Tokenizer::iterator tokIt = tok.begin(); tokIt != tok.end(); ++tokIt) + { + std::string etag(*tokIt); + boost::string_view checksum(etag); + if (checksum.front() == '"') + { + checksum.remove_prefix(1); + checksum.remove_suffix(1); + } + + if (cache.check_path(req.target(), checksum)) + { + return send(not_modified(etag)); + } + } + } + + // Build the path to the requested file + beast::string_view targetPath = req.target(); + auto pos = targetPath.find_first_of('?'); + if (pos != std::string::npos) + { + targetPath = targetPath.substr(0, pos); + } + std::string path = path_cat(doc_root, targetPath); + if (targetPath.back() == '/') + path.append("index.htm"); + + // Attempt to open the file + if (!boost::filesystem::is_regular_file(path)) + { + return send(not_found(req.target())); + } + + std::ifstream body(path, std::ifstream::binary); + + body.seekg(0, std::ios::end); + auto size = static_cast(body.tellg()); + std::vector buffer(size, 0); + body.seekg(0); + body.read((char*)buffer.data(), size); + body.close(); + + decryptBuffer(buffer); + size = buffer.size(); + + boost::crc_32_type result; + result.process_bytes(buffer.data(), buffer.size()); + std::ostringstream checksum; + checksum << std::hex << result.checksum(); + + auto etag = std::string("\"") + checksum.str() + "\""; + cache.insert(req.target(), checksum.str()); + + // Respond to HEAD request + if (req.method() == http::verb::head) + { + http::response res{ http::status::ok, req.version() }; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(size); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to GET request + // TODO: replace this by decryption + http::response res{ http::status::ok, req.version() }; + res.set(http::field::server, "Based on " BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.set(http::field::etag, etag); + res.body().data = buffer.data(); + res.body().size = buffer.size(); + res.body().more = false; + res.content_length(size); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(beast::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + + +// Handles an HTTP server connection +class http_session : public std::enable_shared_from_this +{ + // This queue is used for HTTP pipelining. + class queue + { + enum + { + // Maximum number of responses we will queue + limit = 8 + }; + + // The type-erased, saved work item + struct work + { + virtual ~work() = default; + virtual void operator()() = 0; + }; + + http_session& self_; + std::vector> items_; + + public: + explicit + queue(http_session& self) + : self_(self) + { + static_assert(limit > 0, "queue limit must be positive"); + items_.reserve(limit); + } + + // Returns `true` if we have reached the queue limit + bool + is_full() const + { + return items_.size() >= limit; + } + + // Called when a message finishes sending + // Returns `true` if the caller should initiate a read + bool + on_write() + { + BOOST_ASSERT(!items_.empty()); + auto const was_full = is_full(); + items_.erase(items_.begin()); + if (!items_.empty()) + (*items_.front())(); + return was_full; + } + + // Called by the HTTP handler to send a response. + template + void + operator()(http::message&& msg) + { + // This holds a work item + struct work_impl : work + { + http_session& self_; + http::message msg_; + + work_impl( + http_session& self, + http::message&& msg) + : self_(self) + , msg_(std::move(msg)) + { + } + + void + operator()() + { + http::async_write( + self_.stream_, + msg_, + beast::bind_front_handler( + &http_session::on_write, + self_.shared_from_this(), + msg_.need_eof())); + } + }; + + // Allocate and store the work + items_.push_back( + boost::make_unique(self_, std::move(msg))); + + // If there was no previous work, start this one + if (items_.size() == 1) + (*items_.front())(); + } + }; + + beast::tcp_stream stream_; + beast::flat_buffer buffer_; + std::shared_ptr doc_root_; + etag_cache& etag_cache_; + queue queue_; + + // The parser is stored in an optional container so we can + // construct it from scratch it at the beginning of each new message. + boost::optional> parser_; + +public: + // Take ownership of the socket + http_session( + tcp::socket&& socket, + std::shared_ptr const& doc_root, + etag_cache& etag_cache) + : stream_(std::move(socket)) + , doc_root_(doc_root) + , etag_cache_(etag_cache) + , queue_(*this) + { + } + + // Start the session + void + run() + { + // We need to be executing within a strand to perform async operations + // on the I/O objects in this session. Although not strictly necessary + // for single-threaded contexts, this example code is written to be + // thread-safe by default. + net::dispatch( + stream_.get_executor(), + beast::bind_front_handler( + &http_session::do_read, + this->shared_from_this())); + } + + +private: + void + do_read() + { + // Construct a new parser for each message + parser_.emplace(); + + // Apply a reasonable limit to the allowed size + // of the body in bytes to prevent abuse. + parser_->body_limit(10000); + + // Set the timeout. + stream_.expires_after(std::chrono::seconds(30)); + + // Read a request using the parser-oriented interface + http::async_read( + stream_, + buffer_, + *parser_, + beast::bind_front_handler( + &http_session::on_read, + shared_from_this())); + } + + void + on_read(beast::error_code ec, std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if (ec == beast::error::timeout) + return; + + // This means they closed the connection + if (ec == http::error::end_of_stream) + return do_close(); + + if (ec) + return fail(ec, "read"); + + // Send the response + handle_request(*doc_root_, etag_cache_, parser_->release(), queue_); + + // If we aren't at the queue limit, try to pipeline another request + if (!queue_.is_full()) + do_read(); + } + + void + on_write(bool close, beast::error_code ec, std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if (ec == beast::error::timeout) + return; + + if (ec) + return fail(ec, "write"); + + if (close) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + return do_close(); + } + + // Inform the queue that a write completed + if (queue_.on_write()) + { + // Read another request + do_read(); + } + } + + void + do_close() + { + // Send a TCP shutdown + beast::error_code ec; + stream_.socket().shutdown(tcp::socket::shutdown_send, ec); + + // At this point the connection is closed gracefully + } +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener : public std::enable_shared_from_this +{ + net::io_context& ioc_; + tcp::acceptor acceptor_; + std::shared_ptr doc_root_; + etag_cache& etag_cache_; + +public: + listener( + net::io_context& ioc, + tcp::endpoint endpoint, + std::shared_ptr const& doc_root, + etag_cache& etagCache) + : ioc_(ioc) + , acceptor_(net::make_strand(ioc)) + , doc_root_(doc_root) + , etag_cache_(etagCache) + { + beast::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if (ec) + { + fail(ec, "open"); + return; + } + +// // Allow address reuse +// acceptor_.set_option(net::socket_base::reuse_address(true), ec); +// if (ec) +// { +// fail(ec, "set_option"); +// return; +// } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if (ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + net::socket_base::max_listen_connections, ec); + if (ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + // We need to be executing within a strand to perform async operations + // on the I/O objects in this session. Although not strictly necessary + // for single-threaded contexts, this example code is written to be + // thread-safe by default. + net::dispatch( + acceptor_.get_executor(), + beast::bind_front_handler( + &listener::do_accept, + this->shared_from_this())); + } + +private: + void + do_accept() + { + // The new connection gets its own strand + acceptor_.async_accept( + net::make_strand(ioc_), + beast::bind_front_handler( + &listener::on_accept, + shared_from_this())); + } + + void + on_accept(beast::error_code ec, tcp::socket socket) + { + if (ec) + { + fail(ec, "accept"); + } + else + { + // Create the http session and run it + std::make_shared( + std::move(socket), + doc_root_, + etag_cache_)->run(); + } + + // Accept another connection + do_accept(); + } +}; + +} // anonymous namespace + +namespace detail +{ + +class CCServerImpl final +{ +public: + CCServerImpl(const std::string& addressToListen, uint16_t portToListen, const std::string& docRoot, int threads) + : m_maxThreads{std::max(1, threads)} + , m_docRoot{ docRoot } + , m_etagCache{} + , m_endpoint{ boost::asio::ip::make_address(addressToListen), portToListen } + , m_ioc{ m_maxThreads } + , m_threads{} + { + } + + ~CCServerImpl() + { + shutdown(); + } + +public: + void run() + { + std::lock_guard scopeGuard(m_mutex); + // Create and launch a listening port + std::make_shared( + m_ioc, + m_endpoint, + std::make_shared(m_docRoot), + m_etagCache)->run(); + + // Run the I/O service on the requested number of threads + m_threads.reserve(m_maxThreads); + for (auto i = m_maxThreads - 1; i >= 0; --i) + { + m_threads.emplace_back([&]() + { + m_ioc.run(); + if (i == 0) + { + m_onStopped(); + } + }); + } + } + + void shutdown() + { + std::lock_guard scopeGuard(m_mutex); + m_ioc.stop(); + for (auto& t : m_threads) + { + t.join(); + } + m_threads.clear(); + } + + bool isRunning() const + { + return m_ioc.stopped(); + } + + void clearCache() + { + m_etagCache.clear(); + } + + boost::signals2::connection connectOnStopped(const boost::signals2::signal::slot_type& slot) + { + return m_onStopped.connect(slot); + } + +private: + mutable std::mutex m_mutex; + int m_maxThreads; + std::string m_docRoot; + etag_cache m_etagCache; + tcp::endpoint m_endpoint; + boost::asio::io_context m_ioc; + std::vector m_threads; + boost::signals2::signal m_onStopped; +}; + +} + +CCServer::CCServer(const std::string& addressToListen, uint16_t portToListen, const std::string& docRoot, int threads) +: m_impl(std::make_unique(addressToListen, portToListen, docRoot, threads)) +{} + + +CCServer::~CCServer() +{} + +void CCServer::shutdown() +{ + m_impl->shutdown(); +} + +void CCServer::clearCache() +{ + m_impl->clearCache(); +} + +boost::signals2::connection CCServer::connectOnStopped(const boost::signals2::signal::slot_type & slot) +{ + return m_impl->connectOnStopped(slot); +} + +void CCServer::run() +{ + m_impl->run(); +} + +bool CCServer::isRunning() const +{ + return m_impl->isRunning(); +} diff --git a/CMakeProject1/CCServer.h b/CMakeProject1/CCServer.h new file mode 100644 index 0000000..ed0322e --- /dev/null +++ b/CMakeProject1/CCServer.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +namespace detail +{ + class CCServerImpl; +} + +class CCServer final +{ +public: + CCServer(const std::string& addressToListen, uint16_t portToListen, const std::string& docRoot, int threads); + ~CCServer(); + +public: + void run(); + bool isRunning() const; + void shutdown(); + void clearCache(); + boost::signals2::connection connectOnStopped(const boost::signals2::signal::slot_type& slot); + +private: + std::unique_ptr m_impl; +}; diff --git a/CMakeProject1/CMakeLists.txt b/CMakeProject1/CMakeLists.txt new file mode 100644 index 0000000..12b637d --- /dev/null +++ b/CMakeProject1/CMakeLists.txt @@ -0,0 +1,30 @@ +# CMakeList.txt : CMake project for CMakeProject1, include source and define +# project specific logic here. +# +cmake_minimum_required (VERSION 3.8) + +# Download automatically, you can also just copy the conan.cmake file +if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") + message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") + file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/v0.16.1/conan.cmake" + "${CMAKE_BINARY_DIR}/conan.cmake" + EXPECTED_HASH SHA256=396e16d0f5eabdc6a14afddbcfff62a54a7ee75c6da23f32f7a31bc85db23484 + TLS_VERIFY ON) +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +include(${CMAKE_BINARY_DIR}/conan.cmake) + +conan_cmake_run(CONANFILE conanfile.txt + BUILD missing + BASIC_SETUP) + +# Add source to this project's executable. +add_executable (CMakeProject1 "CCServer.cpp" "CCServer.h" "CryptoUtils.cpp" "CryptoUtils.h" "CMakeProject1.cpp" "CMakeProject1.h") + +target_link_libraries(CMakeProject1 ${CONAN_LIBS}) + +# TODO: Add tests and install targets if needed. diff --git a/CMakeProject1/CMakeProject1.cpp b/CMakeProject1/CMakeProject1.cpp new file mode 100644 index 0000000..a348fa1 --- /dev/null +++ b/CMakeProject1/CMakeProject1.cpp @@ -0,0 +1,62 @@ +#include "CCServer.h" + +#include +#include +#include +#include +#include +#include + +namespace po = boost::program_options; +namespace net = boost::asio; // from + +volatile bool signalCaught = false; + +void signalHandler(int signum) { + std::cout << "Interrupt signal (" << signum << ") received.\n"; + + signalCaught = true; +} + +int main(int argc, const char* argv[]) +{ + // Declare the supported options. + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "produce help message") + ("bind_ip", po::value()->default_value("0.0.0.0"), "ip address to listen for connections") + ("port", po::value()->default_value(8080), "port") + ("data_root", po::value()->default_value("."), "root directory with data") + ("threads", po::value()->default_value(4), "port") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) { + std::cout << desc << "\n"; + return 1; + } + + if (vm.count("port")) { + std::cout << "Port " + << vm["port"].as() << ".\n"; + } + + auto addressToListen = vm["bind_ip"].as(); + auto portToListen = vm["port"].as(); + auto docRoot = vm["data_root"].as(); + auto threads = vm["threads"].as(); + + + auto ccServer = std::make_unique(addressToListen, portToListen, docRoot, threads); + ccServer->run(); + while (!signalCaught) + { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + ccServer->shutdown(); + + return 0; +} diff --git a/CMakeProject1/CMakeProject1.h b/CMakeProject1/CMakeProject1.h new file mode 100644 index 0000000..48103f7 --- /dev/null +++ b/CMakeProject1/CMakeProject1.h @@ -0,0 +1,8 @@ +// CMakeProject1.h : Include file for standard system include files, +// or project specific include files. + +#pragma once + +#include + +// TODO: Reference additional headers your program requires here. diff --git a/CMakeProject1/CryptoUtils.cpp b/CMakeProject1/CryptoUtils.cpp new file mode 100644 index 0000000..9cb2027 --- /dev/null +++ b/CMakeProject1/CryptoUtils.cpp @@ -0,0 +1,92 @@ +#include "CryptoUtils.h" + +#include +#include + +using CryptoPP::Base64Decoder; +using CryptoPP::Base64Encoder; +using CryptoPP::Base32Decoder; +using CryptoPP::Base32Encoder; +using CryptoPP::StringSource; +using CryptoPP::StringSink; +using CryptoPP::byte; + +namespace +{ + static const byte base32Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +} + +std::string base64Decode(const std::string& encoded) +{ + std::string decoded; + + StringSource ss(encoded, true, + new Base64Decoder( + new StringSink(decoded) + ) + ); + + return decoded; +} + +std::string base64Encode(const std::string& source) +{ + std::string encoded; + + StringSource ss(source, true, + new Base64Encoder( + new StringSink(encoded), + false /* no line breaks */ + ) + ); + + return encoded; +} + +std::unique_ptr base64Encode(const uint8_t* source, size_t sourceSize) +{ + auto encoded = std::make_unique((sourceSize*4/3 + 3) & ~3); + + CryptoPP::ArraySource ss(source, sourceSize, true, + new Base64Encoder( + new CryptoPP::ArraySink(encoded.get(), sourceSize * 4 / 3), + false /* no line breaks */ + ) + ); + + return encoded; +} + +std::string base32Decode(const std::string& encoded) +{ + // Decoder + int lookup[256] = { 0 }; + Base64Decoder::InitializeDecodingLookupArray(lookup, base32Alphabet, 32, true); + + Base32Decoder decoder; + CryptoPP::AlgorithmParameters params = CryptoPP::MakeParameters(CryptoPP::Name::DecodingLookupArray(), (const int *)lookup); + decoder.IsolatedInitialize(params); + std::string decoded; + + decoder.Attach(new StringSink(decoded)); + decoder.Put((const byte*)encoded.data(), encoded.size()); + decoder.MessageEnd(); + + return decoded; +} + +std::string base32Encode(const std::string& data) +{ + // Encoder + Base32Encoder encoder; + CryptoPP::AlgorithmParameters params = CryptoPP::MakeParameters(CryptoPP::Name::EncodingLookupArray(), (const byte *)base32Alphabet); + encoder.IsolatedInitialize(params); + + std::string encoded; + + encoder.Attach(new StringSink(encoded)); + encoder.Put((const byte*)data.data(), data.size()); + encoder.MessageEnd(); + + return encoded; +} diff --git a/CMakeProject1/CryptoUtils.h b/CMakeProject1/CryptoUtils.h new file mode 100644 index 0000000..6407260 --- /dev/null +++ b/CMakeProject1/CryptoUtils.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +std::string base64Decode(const std::string& encoded); +std::string base64Encode(const std::string& source); +std::unique_ptr base64Encode(const uint8_t * source, size_t sourceSize); +std::string base32Decode(const std::string& encoded); +std::string base32Encode(const std::string& data); diff --git a/CMakeProject1/Makefile b/CMakeProject1/Makefile new file mode 100644 index 0000000..565c7e3 --- /dev/null +++ b/CMakeProject1/Makefile @@ -0,0 +1,180 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 3.16 + +# Default target executed when no arguments are given to make. +default_target: all + +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + + +# Remove some rules from gmake that .SUFFIXES does not remove. +SUFFIXES = + +.SUFFIXES: .hpux_make_needs_suffix_list + + +# Suppress display of executed commands. +$(VERBOSE).SILENT: + + +# A target that is always out of date. +cmake_force: + +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /usr/bin/cmake + +# The command to remove a file. +RM = /usr/bin/cmake -E remove -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /mnt/c/Users/petos/source/repos/CMakeProject1 + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /mnt/c/Users/petos/source/repos/CMakeProject1 + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." + /usr/bin/cmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache + +.PHONY : rebuild_cache/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..." + /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available. +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache + +.PHONY : edit_cache/fast + +# The main all target +all: cmake_check_build_system + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(CMAKE_COMMAND) -E cmake_progress_start /mnt/c/Users/petos/source/repos/CMakeProject1/CMakeFiles /mnt/c/Users/petos/source/repos/CMakeProject1/CMakeProject1/CMakeFiles/progress.marks + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(MAKE) -f CMakeFiles/Makefile2 CMakeProject1/all + $(CMAKE_COMMAND) -E cmake_progress_start /mnt/c/Users/petos/source/repos/CMakeProject1/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(MAKE) -f CMakeFiles/Makefile2 CMakeProject1/clean +.PHONY : clean + +# The main clean target +clean/fast: clean + +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(MAKE) -f CMakeFiles/Makefile2 CMakeProject1/preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(MAKE) -f CMakeFiles/Makefile2 CMakeProject1/preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +# Convenience name for target. +CMakeProject1/CMakeFiles/CMakeProject1.dir/rule: + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(MAKE) -f CMakeFiles/Makefile2 CMakeProject1/CMakeFiles/CMakeProject1.dir/rule +.PHONY : CMakeProject1/CMakeFiles/CMakeProject1.dir/rule + +# Convenience name for target. +CMakeProject1: CMakeProject1/CMakeFiles/CMakeProject1.dir/rule + +.PHONY : CMakeProject1 + +# fast build rule for target. +CMakeProject1/fast: + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(MAKE) -f CMakeProject1/CMakeFiles/CMakeProject1.dir/build.make CMakeProject1/CMakeFiles/CMakeProject1.dir/build +.PHONY : CMakeProject1/fast + +CMakeProject1.o: CMakeProject1.cpp.o + +.PHONY : CMakeProject1.o + +# target to build an object file +CMakeProject1.cpp.o: + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(MAKE) -f CMakeProject1/CMakeFiles/CMakeProject1.dir/build.make CMakeProject1/CMakeFiles/CMakeProject1.dir/CMakeProject1.cpp.o +.PHONY : CMakeProject1.cpp.o + +CMakeProject1.i: CMakeProject1.cpp.i + +.PHONY : CMakeProject1.i + +# target to preprocess a source file +CMakeProject1.cpp.i: + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(MAKE) -f CMakeProject1/CMakeFiles/CMakeProject1.dir/build.make CMakeProject1/CMakeFiles/CMakeProject1.dir/CMakeProject1.cpp.i +.PHONY : CMakeProject1.cpp.i + +CMakeProject1.s: CMakeProject1.cpp.s + +.PHONY : CMakeProject1.s + +# target to generate assembly for a file +CMakeProject1.cpp.s: + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(MAKE) -f CMakeProject1/CMakeFiles/CMakeProject1.dir/build.make CMakeProject1/CMakeFiles/CMakeProject1.dir/CMakeProject1.cpp.s +.PHONY : CMakeProject1.cpp.s + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... rebuild_cache" + @echo "... edit_cache" + @echo "... CMakeProject1" + @echo "... CMakeProject1.o" + @echo "... CMakeProject1.i" + @echo "... CMakeProject1.s" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + cd /mnt/c/Users/petos/source/repos/CMakeProject1 && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/CMakeProject1/conanfile.txt b/CMakeProject1/conanfile.txt new file mode 100644 index 0000000..4354a98 --- /dev/null +++ b/CMakeProject1/conanfile.txt @@ -0,0 +1,10 @@ +[requires] +boost/1.76.0 +cryptopp/8.5.0 + +[generators] +cmake + +[options] +boost:shared=False +cryptopp:shared=False diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000..532fd69 --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,88 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "variables": [ + { + "name": "CMAKE_SYSTEM_VERSION", + "value": "8.1", + "type": "STRING" + } + ] + }, + { + "name": "WSL-GCC-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeExecutable": "cmake", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "linux_x64" ], + "wslPath": "${defaultWSLPath}" + }, + { + "name": "WSL-GCC-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeExecutable": "cmake", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "linux_x64" ], + "wslPath": "${defaultWSLPath}", + "variables": [ + { + "name": "CMAKE_CXX_FLAGS_RELEASE", + "value": "-O3 -DNDEBUG -DBOOST_EXCEPTION_DISABLE=1 -DBOOST_NO_RTTI=1 -flto", + "type": "STRING" + }, + { + "name": "CMAKE_EXE_LINKER_FLAGS_RELEASE", + "value": "-s -flto -Wl,-rpath=.", + "type": "STRING" + } + ] + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [ + { + "name": "CMAKE_SYSTEM_VERSION", + "value": "8.1", + "type": "STRING" + }, + { + "name": "CMAKE_EXE_LINKER_FLAGS_RELEASE", + "value": "/INCREMENTAL:NO /ASSEMBLYDEBUG:DISABLE /OPT:REF /DEBUG:NONE /PDBSTRIPPED:YES", + "type": "STRING" + }, + { + "name": "CMAKE_CXX_FLAGS_RELEASE", + "value": "/MD /O2 /Ob2 /DNDEBUG /DBOOST_EXCEPTION_DISABLE=1", + "type": "STRING" + } + ] + } + ] +} \ No newline at end of file