From 3b211d1af6c4a6e7c639c27aa3c77e0807ac0267 Mon Sep 17 00:00:00 2001 From: Mutzi Date: Tue, 23 Apr 2024 15:38:41 +0200 Subject: [PATCH] Replaced restbed with beast --- CMakeLists.txt | 76 +-- frontend/src/pages/ResetPassword.svelte | 3 +- include/botan_asio/asio_async_ops.h | 300 ---------- include/botan_asio/asio_context.h | 90 --- include/botan_asio/asio_error.h | 125 ---- include/botan_asio/asio_stream.h | 740 ------------------------ src/data/data.cxx | 4 +- src/data/data.hxx | 2 +- src/main.cxx | 211 ++++--- src/server/admin.cxx | 2 +- src/server/download.cxx | 299 +++++----- src/server/fs.cxx | 88 ++- src/server/mail.cxx | 24 +- src/server/mrpc/fileserver.cxx | 157 ++--- src/server/mrpc/fileserver.hxx | 32 +- src/server/server.cxx | 11 +- src/server/server.hxx | 15 +- src/server/server_internal.hxx | 3 +- src/server/upload.cxx | 113 ++-- src/util/boost.hxx | 66 +++ src/util/botan.hxx | 8 + src/util/logging.hxx | 60 -- src/util/miniz.cxx | 476 +++++++++++++++ src/util/miniz.hxx | 39 ++ src/util/timed_mutex.hxx | 30 - 25 files changed, 1104 insertions(+), 1870 deletions(-) delete mode 100644 include/botan_asio/asio_async_ops.h delete mode 100644 include/botan_asio/asio_context.h delete mode 100644 include/botan_asio/asio_error.h delete mode 100644 include/botan_asio/asio_stream.h create mode 100644 src/util/boost.hxx create mode 100644 src/util/botan.hxx delete mode 100644 src/util/logging.hxx create mode 100644 src/util/miniz.cxx create mode 100644 src/util/miniz.hxx delete mode 100644 src/util/timed_mutex.hxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a9aa18..65309ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,31 +5,43 @@ set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED YES) include(CPM.cmake) -CPMAddPackage("gh:richgel999/miniz#3.0.2") CPMAddPackage("gh:gabime/spdlog#v1.13.0") -CPMAddPackage( - NAME restbed - VERSION 4.8 - GITHUB_REPOSITORY Corvusoft/restbed - GIT_TAG 4.8 - DOWNLOAD_ONLY YES + +set(BOOST_LIBS + beast asio assert bind config container core endian + intrusive logic mp11 optional smart_ptr static_assert static_string + system throw_exception type_traits utility winapi + align context coroutine date_time move container_hash detail predef + variant2 io preprocessor pool exception algorithm lexical_cast numeric_conversion + range tokenizer describe integer tuple array concept_check + function regex unordered iterator mpl conversion function_types fusion typeof functional ) + +foreach(BOOST_LIB ${BOOST_LIBS}) + CPMAddPackage("gh:boostorg/${BOOST_LIB}#boost-1.85.0") +endforeach() + CPMAddPackage( - NAME asio - VERSION 1.29.0 - GITHUB_REPOSITORY chriskohlhoff/asio - GIT_TAG asio-1-29-0 + NAME uring + VERSION 2.5 + GITHUB_REPOSITORY axboe/liburing + GIT_TAG liburing-2.5 DOWNLOAD_ONLY YES ) -if(asio_ADDED AND restbed_ADDED) - file(GLOB_RECURSE restbed_SOURCE "${restbed_SOURCE_DIR}/source/corvusoft/restbed/*.cpp") - add_library(restbed-static STATIC ${restbed_SOURCE} "${asio_SOURCE_DIR}/asio/src/asio.cpp") - target_compile_definitions(restbed-static PUBLIC ASIO_SEPARATE_COMPILATION) - target_include_directories(restbed-static PUBLIC "${restbed_SOURCE_DIR}/source" "${asio_SOURCE_DIR}/asio/include") +if(uring_ADDED) + add_library(uring STATIC + ${uring_SOURCE_DIR}/src/setup.c + ${uring_SOURCE_DIR}/src/queue.c + ${uring_SOURCE_DIR}/src/register.c + ${uring_SOURCE_DIR}/src/syscall.c + ${uring_SOURCE_DIR}/src/version.c + ) + target_include_directories(uring PUBLIC ${uring_SOURCE_DIR}/src/include) + target_compile_definitions(uring PUBLIC BOOST_ASIO_HAS_IO_URING PRIVATE LIBURING_INTERNAL _LARGEFILE_SOURCE _FILE_OFFSET_BITS=64 _GNU_SOURCE) endif() -set(BOTAN_MODULES argon2fmt hotp base32 auto_rng system_rng tls13 certstor_system certstor_flatfile md5) +set(BOTAN_MODULES argon2fmt hotp base32 auto_rng system_rng tls13 certstor_system certstor_flatfile md5 asio) CPMAddPackage( NAME botan VERSION 3.4.0 @@ -48,25 +60,21 @@ if(botan_ADDED) --amalgamation --minimized-build --without-documentation + --with-boost --enable-modules=${BOTAN_MODULES_STR} ) add_library(botan STATIC ${CMAKE_CURRENT_BINARY_DIR}/botan_all.cpp ${CMAKE_CURRENT_BINARY_DIR}/botan_all.h) endif() -#FetchContent_Declare(botan GIT_REPOSITORY https://gitea.mattv.de/root/cmake-libraries.git GIT_TAG origin/botan) -#FetchContent_MakeAvailable(botan) - -#add_subdirectory(lib EXCLUDE_FROM_ALL) - find_package(Threads REQUIRED) -add_custom_command( - COMMAND ./mrpc ARGS -n src/server/mrpc/fileserver -s cpp fileserver.rs - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - MAIN_DEPENDENCY fileserver.rs - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/server/mrpc/fileserver.cxx ${CMAKE_CURRENT_SOURCE_DIR}/src/server/mrpc/fileserver.hxx -) +#add_custom_command( +# COMMAND ./mrpc ARGS -n src/server/mrpc/fileserver -s cpp fileserver.rs +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +# MAIN_DEPENDENCY fileserver.rs +# OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/server/mrpc/fileserver.cxx ${CMAKE_CURRENT_SOURCE_DIR}/src/server/mrpc/fileserver.hxx +#) add_custom_command( COMMAND sh ARGS -c 'xxd -i -n index_html ${CMAKE_CURRENT_SOURCE_DIR}/frontend/dist/index.html > index_html.h' @@ -84,10 +92,12 @@ add_executable(fileserver src/server/mrpc/fileserver.hxx src/server/mrpc/fileserver.cxx - src/util/logging.hxx src/util/crash.hxx - src/util/timed_mutex.hxx src/util/stb.cxx + src/util/miniz.hxx + src/util/miniz.cxx + src/util/boost.hxx + src/util/botan.hxx src/data/data.hxx src/data/data_internal.hxx @@ -114,13 +124,13 @@ add_executable(fileserver target_include_directories(fileserver PRIVATE include ${CMAKE_CURRENT_BINARY_DIR}) target_compile_options(fileserver PRIVATE -msse2) +target_compile_definitions(fileserver PRIVATE BOOST_BEAST_FILE_BUFFER_SIZE=65535) target_link_options(fileserver PRIVATE -static) target_link_libraries(fileserver PRIVATE spdlog::spdlog - restbed-static - #Botan::Botan + Boost::beast botan - miniz + uring Threads::Threads ) diff --git a/frontend/src/pages/ResetPassword.svelte b/frontend/src/pages/ResetPassword.svelte index 390beb2..74c815d 100644 --- a/frontend/src/pages/ResetPassword.svelte +++ b/frontend/src/pages/ResetPassword.svelte @@ -8,8 +8,7 @@ let username = '', key = '', password = '', password2 = ''; async function sendKey() { - if (await workingWrapper(() => rpc.Auth_send_recovery_key(username)) == null) - return; + await workingWrapper(() => rpc.Auth_send_recovery_key(username)); info_banner.set('A message has been sent'); enter_key = true; } diff --git a/include/botan_asio/asio_async_ops.h b/include/botan_asio/asio_async_ops.h deleted file mode 100644 index 1d98a9d..0000000 --- a/include/botan_asio/asio_async_ops.h +++ /dev/null @@ -1,300 +0,0 @@ -/* -* Helpers for TLS ASIO Stream -* (C) 2018-2020 Jack Lloyd -* 2018-2020 Hannes Rantzsch, Tim Oesterreich, Rene Meusel -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_ASIO_ASYNC_OPS_H_ -#define BOTAN_ASIO_ASYNC_OPS_H_ - -#include -#include -#include -#include "asio_error.h" - -namespace Botan::TLS::detail { - -/** - * Base class for asynchronous stream operations. - * - * Asynchronous operations, used for example to implement an interface for boost::asio::async_read_some and - * boost::asio::async_write_some, are based on boost::asio::coroutines. - * Derived operations should implement a call operator and invoke it with the correct parameters upon construction. The - * call operator needs to make sure that the user-provided handler is not called directly. Typically, yield / reenter is - * used for this in the following fashion: - * - * ``` - * void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) - * { - * reenter(this) - * { - * // operation specific logic, repeatedly interacting with the stream_core and the next_layer (socket) - * - * // make sure intermediate initiating function is called - * if(!isContinuation) - * { - * yield next_layer.async_operation(empty_buffer, this); - * } - * - * // call the completion handler - * complete_now(error_code, bytes_transferred); - * } - * } - * ``` - * - * Once the operation is completed and ready to call the completion handler it checks if an intermediate initiating - * function has been called using the `isContinuation` parameter. If not, it will call an asynchronous operation, such - * as `async_read_some`, with and empty buffer, set the object itself as the handler, and `yield`. As a result, the call - * operator will be invoked again, this time as a continuation, and will jump to the location where it yielded before - * using `reenter`. It is now safe to call the handler function via `complete_now`. - * - * \tparam Handler Type of the completion handler - * \tparam Executor1 Type of the asio executor (usually derived from the lower layer) - * \tparam Allocator Type of the allocator to be used - */ -template -class AsyncBase : public boost::asio::coroutine { - public: - using allocator_type = boost::asio::associated_allocator_t; - using executor_type = boost::asio::associated_executor_t; - - allocator_type get_allocator() const noexcept { return boost::asio::get_associated_allocator(m_handler); } - - executor_type get_executor() const noexcept { - return boost::asio::get_associated_executor(m_handler, m_work_guard_1.get_executor()); - } - - protected: - template - AsyncBase(HandlerT&& handler, const Executor1& executor) : - m_handler(std::forward(handler)), m_work_guard_1(executor) {} - - /** - * Call the completion handler. - * - * This function should only be called after an intermediate initiating function has been called. - * - * @param args Arguments forwarded to the completion handler function. - */ - template - void complete_now(Args&&... args) { - m_work_guard_1.reset(); - m_handler(std::forward(args)...); - } - - Handler m_handler; - boost::asio::executor_work_guard m_work_guard_1; -}; - -template > -class AsyncReadOperation : public AsyncBase { - public: - /** - * Construct and invoke an AsyncReadOperation. - * - * @param handler Handler function to be called upon completion. - * @param stream The stream from which the data will be read - * @param buffers The buffers into which the data will be read. - * @param ec Optional error code; used to report an error to the handler function. - */ - template - AsyncReadOperation(HandlerT&& handler, - Stream& stream, - const MutableBufferSequence& buffers, - const boost::system::error_code& ec = {}) : - AsyncBase(std::forward(handler), - stream.get_executor()), - m_stream(stream), - m_buffers(buffers), - m_decodedBytes(0) { - this->operator()(ec, std::size_t(0), false); - } - - AsyncReadOperation(AsyncReadOperation&&) = default; - - void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) { - reenter(this) { - if(bytes_transferred > 0 && !ec) { - // We have received encrypted data from the network, now hand it to TLS::Channel for decryption. - boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred}; - m_stream.process_encrypted_data(read_buffer, ec); - } - - if(m_stream.shutdown_received()) { - // we just received a 'close_notify' from the peer and don't expect any more data - ec = boost::asio::error::eof; - } else if(ec == boost::asio::error::eof) { - // we did not expect this disconnection from the peer - ec.assign(StreamError::StreamTruncated, std::generic_category()); - } - - if(!m_stream.has_received_data() && !ec && boost::asio::buffer_size(m_buffers) > 0) { - // The channel did not decrypt a complete record yet, we need more data from the socket. - m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this)); - return; - } - - if(m_stream.has_received_data() && !ec) { - // The channel has decrypted a TLS record, now copy it to the output buffers. - m_decodedBytes = m_stream.copy_received_data(m_buffers); - } - - if(!isContinuation) { - // Make sure the handler is not called without an intermediate initiating function. - // "Reading" into a zero-byte buffer will complete immediately. - m_ec = ec; - yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this)); - ec = m_ec; - } - - this->complete_now(ec, m_decodedBytes); - } - } - - private: - Stream& m_stream; - MutableBufferSequence m_buffers; - std::size_t m_decodedBytes; - boost::system::error_code m_ec; -}; - -template > -class AsyncWriteOperation : public AsyncBase { - public: - /** - * Construct and invoke an AsyncWriteOperation. - * - * @param handler Handler function to be called upon completion. - * @param stream The stream from which the data will be read - * @param plainBytesTransferred Number of bytes to be reported to the user-provided handler function as - * bytes_transferred. This needs to be provided since the amount of plaintext data - * consumed from the input buffer can differ from the amount of encrypted data written - * to the next layer. - * @param ec Optional error code; used to report an error to the handler function. - */ - template - AsyncWriteOperation(HandlerT&& handler, - Stream& stream, - std::size_t plainBytesTransferred, - const boost::system::error_code& ec = {}) : - AsyncBase(std::forward(handler), - stream.get_executor()), - m_stream(stream), - m_plainBytesTransferred(plainBytesTransferred) { - this->operator()(ec, std::size_t(0), false); - } - - AsyncWriteOperation(AsyncWriteOperation&&) = default; - - void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) { - reenter(this) { - // mark the number of encrypted bytes sent to the network as "consumed" - // Note: bytes_transferred will be zero on first call - m_stream.consume_send_buffer(bytes_transferred); - - if(m_stream.has_data_to_send() && !ec) { - m_stream.next_layer().async_write_some(m_stream.send_buffer(), std::move(*this)); - return; - } - - if(ec == boost::asio::error::eof && !m_stream.shutdown_received()) { - // transport layer was closed by peer without receiving 'close_notify' - ec.assign(StreamError::StreamTruncated, std::generic_category()); - } - - if(!isContinuation) { - // Make sure the handler is not called without an intermediate initiating function. - // "Writing" to a zero-byte buffer will complete immediately. - m_ec = ec; - yield m_stream.next_layer().async_write_some(boost::asio::const_buffer(), std::move(*this)); - ec = m_ec; - } - - // The size of the sent TLS record can differ from the size of the payload due to TLS encryption. We need to - // tell the handler how many bytes of the original data we already processed. - this->complete_now(ec, m_plainBytesTransferred); - } - } - - private: - Stream& m_stream; - std::size_t m_plainBytesTransferred; - boost::system::error_code m_ec; -}; - -template > -class AsyncHandshakeOperation : public AsyncBase { - public: - /** - * Construct and invoke an AsyncHandshakeOperation. - * - * @param handler Handler function to be called upon completion. - * @param stream The stream from which the data will be read - * @param ec Optional error code; used to report an error to the handler function. - */ - template - AsyncHandshakeOperation(HandlerT&& handler, Stream& stream, const boost::system::error_code& ec = {}) : - AsyncBase(std::forward(handler), - stream.get_executor()), - m_stream(stream) { - this->operator()(ec, std::size_t(0), false); - } - - AsyncHandshakeOperation(AsyncHandshakeOperation&&) = default; - - void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation = true) { - reenter(this) { - if(ec == boost::asio::error::eof) { - ec.assign(StreamError::StreamTruncated, std::generic_category()); - } - - if(bytesTransferred > 0 && !ec) { - // Provide encrypted TLS data received from the network to TLS::Channel for decryption - boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytesTransferred}; - m_stream.process_encrypted_data(read_buffer, ec); - } - - if(m_stream.has_data_to_send() && !ec) { - // Write encrypted TLS data provided by the TLS::Channel on the wire - - // Note: we construct `AsyncWriteOperation` with 0 as its last parameter (`plainBytesTransferred`). This - // operation will eventually call `*this` as its own handler, passing the 0 back to this call operator. - // This is necessary because the check of `bytesTransferred > 0` assumes that `bytesTransferred` bytes - // were just read and are available in input_buffer for further processing. - AsyncWriteOperation::type, Stream, Allocator>, - Stream, - Allocator> - op{std::move(*this), m_stream, 0}; - return; - } - - if(!m_stream.native_handle()->is_active() && !ec) { - // Read more encrypted TLS data from the network - m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this)); - return; - } - - if(!isContinuation) { - // Make sure the handler is not called without an intermediate initiating function. - // "Reading" into a zero-byte buffer will complete immediately. - m_ec = ec; - yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this)); - ec = m_ec; - } - - this->complete_now(ec); - } - } - - private: - Stream& m_stream; - boost::system::error_code m_ec; -}; - -} // namespace Botan::TLS::detail - - #include - -#endif // BOTAN_ASIO_ASYNC_OPS_H_ diff --git a/include/botan_asio/asio_context.h b/include/botan_asio/asio_context.h deleted file mode 100644 index 0c3b43e..0000000 --- a/include/botan_asio/asio_context.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * TLS Context - * (C) 2018-2020 Jack Lloyd - * 2018-2020 Hannes Rantzsch, Tim Oesterreich, Rene Meusel - * - * Botan is released under the Simplified BSD License (see license.txt) - */ - -#ifndef BOTAN_ASIO_TLS_CONTEXT_H_ -#define BOTAN_ASIO_TLS_CONTEXT_H_ - -#include -#include - -namespace Botan::TLS { - -namespace detail { -template -struct fn_signature_helper : public std::false_type {}; - -template -struct fn_signature_helper { - using type = std::function; -}; -} // namespace detail - -/** - * A helper class to initialize and configure Botan::TLS::Stream - */ -class Context { - public: - // statically extract the function signature type from Callbacks::tls_verify_cert_chain - // and reuse it as an std::function<> for the verify callback signature - /** - * The signature of the callback function should correspond to the signature of - * Callbacks::tls_verify_cert_chain - */ - using Verify_Callback = detail::fn_signature_helper::type; - - Context(std::shared_ptr credentials_manager, - std::shared_ptr rng, - std::shared_ptr session_manager, - std::shared_ptr policy, - Server_Information server_info = Server_Information()) : - m_credentials_manager(credentials_manager), - m_rng(rng), - m_session_manager(session_manager), - m_policy(policy), - m_server_info(std::move(server_info)) {} - - virtual ~Context() = default; - - Context(Context&&) = default; - Context(const Context&) = delete; - Context& operator=(const Context&) = delete; - Context& operator=(Context&&) = delete; - - /** - * @brief Override the tls_verify_cert_chain callback - * - * This changes the verify_callback in the stream's TLS::Context, and hence the tls_verify_cert_chain callback - * used in the handshake. - * Using this function is equivalent to setting the callback via @see Botan::TLS::Stream::set_verify_callback - * - * @note This function should only be called before initiating the TLS handshake - */ - void set_verify_callback(Verify_Callback callback) { m_verify_callback = std::move(callback); } - - bool has_verify_callback() const { return static_cast(m_verify_callback); } - - const Verify_Callback& get_verify_callback() const { return m_verify_callback; } - - void set_server_info(Server_Information server_info) { m_server_info = std::move(server_info); } - - protected: - template - friend class Stream; - - std::shared_ptr m_credentials_manager; - std::shared_ptr m_rng; - std::shared_ptr m_session_manager; - std::shared_ptr m_policy; - - Server_Information m_server_info; - Verify_Callback m_verify_callback; -}; - -} // namespace Botan::TLS - -#endif // BOTAN_ASIO_TLS_CONTEXT_H_ diff --git a/include/botan_asio/asio_error.h b/include/botan_asio/asio_error.h deleted file mode 100644 index cdafe65..0000000 --- a/include/botan_asio/asio_error.h +++ /dev/null @@ -1,125 +0,0 @@ -/* -* TLS Stream Errors -* (C) 2018-2020 Jack Lloyd -* 2018-2020 Hannes Rantzsch, Tim Oesterreich, Rene Meusel -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_ASIO_ERROR_H_ -#define BOTAN_ASIO_ERROR_H_ - -#include -#include - -namespace boost{ - namespace asio = ::asio; - namespace system { - template - struct is_error_code_enum { static const bool value = false; }; - - typedef asio::error_category error_category; - typedef asio::error_code error_code; - } - namespace beast { - using flat_buffer = asio::streambuf; - } -} - - -namespace Botan { -namespace TLS { - -enum StreamError { StreamTruncated = 1 }; - -//! @brief An error category for errors from the TLS::Stream -struct StreamCategory : public boost::system::error_category { - virtual ~StreamCategory() = default; - - const char* name() const noexcept override { return "Botan TLS Stream"; } - - std::string message(int value) const override { - if(value == StreamTruncated) { - return "stream truncated"; - } else { - return "generic error"; - } - } -}; - -inline const StreamCategory& botan_stream_category() { - static StreamCategory category; - return category; -} - -inline boost::system::error_code make_error_code(Botan::TLS::StreamError e) { - return boost::system::error_code(static_cast(e), Botan::TLS::botan_stream_category()); -} - -//! @brief An error category for TLS alerts -struct BotanAlertCategory : boost::system::error_category { - virtual ~BotanAlertCategory() = default; - - const char* name() const noexcept override { return "Botan TLS Alert"; } - - std::string message(int ev) const override { - Botan::TLS::Alert alert(static_cast(ev)); - return alert.type_string(); - } -}; - -inline const BotanAlertCategory& botan_alert_category() noexcept { - static BotanAlertCategory category; - return category; -} - -inline boost::system::error_code make_error_code(Botan::TLS::Alert::Type c) { - return boost::system::error_code(static_cast(c), Botan::TLS::botan_alert_category()); -} - -} // namespace TLS - -//! @brief An error category for errors from Botan (other than TLS alerts) -struct BotanErrorCategory : boost::system::error_category { - virtual ~BotanErrorCategory() = default; - - const char* name() const noexcept override { return "Botan"; } - - std::string message(int ev) const override { return Botan::to_string(static_cast(ev)); } -}; - -inline const BotanErrorCategory& botan_category() noexcept { - static BotanErrorCategory category; - return category; -} - -inline boost::system::error_code make_error_code(Botan::ErrorType e) { - return boost::system::error_code(static_cast(e), Botan::botan_category()); -} - -} // namespace Botan - -/* - * Add a template specialization of `is_error_code_enum` for each kind of error to allow automatic conversion to an - * error code. - */ -namespace boost::system { - -template <> -struct is_error_code_enum { - static const bool value = true; -}; - -template <> -struct is_error_code_enum { - static const bool value = true; -}; - -template <> -struct is_error_code_enum { - static const bool value = true; -}; - -} // namespace boost::system - -#endif // BOTAN_ASIO_ERROR_H_ diff --git a/include/botan_asio/asio_stream.h b/include/botan_asio/asio_stream.h deleted file mode 100644 index c11a20a..0000000 --- a/include/botan_asio/asio_stream.h +++ /dev/null @@ -1,740 +0,0 @@ -/* -* TLS ASIO Stream -* (C) 2018-2021 Jack Lloyd -* 2018-2021 Hannes Rantzsch, Tim Oesterreich, Rene Meusel -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_ASIO_STREAM_H_ -#define BOTAN_ASIO_STREAM_H_ - -#include -#include - -#include "asio_async_ops.h" -#include "asio_context.h" -#include "asio_error.h" - -#include -#include -#include - -namespace Botan::TLS { - -/** - * @brief boost::asio compatible SSL/TLS stream - * - * @tparam StreamLayer type of the next layer, usually a network socket - * @tparam ChannelT type of the native_handle, defaults to TLS::Channel, only needed for testing purposes - */ -template -class Stream { - public: - //! \name construction - //! @{ - - /** - * @brief Construct a new Stream - * - * @param context The context parameter is used to set up the underlying native handle. Using code is - * responsible for lifetime management of the context and must ensure that it is available for the - * lifetime of the stream. - * @param args Arguments to be forwarded to the construction of the next layer. - */ - template - explicit Stream(std::shared_ptr context, Args&&... args) : - m_context(context), - m_nextLayer(std::forward(args)...), - m_core(std::make_shared(context)), - m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0'), - m_input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size()) {} - - /** - * @brief Construct a new Stream - * - * Convenience overload for boost::asio::ssl::stream compatibility. - * - * @param arg This argument is forwarded to the construction of the next layer. - * @param context The context parameter is used to set up the underlying native handle. Using code is - * responsible for lifetime management of the context and must ensure that is available for the - * lifetime of the stream. - */ - template - explicit Stream(Arg&& arg, std::shared_ptr context) : - m_context(context), - m_nextLayer(std::forward(arg)), - m_core(std::make_shared(context)), - m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0'), - m_input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size()) {} - - virtual ~Stream() = default; - - Stream(Stream&& other) = default; - Stream& operator=(Stream&& other) = default; - - Stream(const Stream& other) = delete; - Stream& operator=(const Stream& other) = delete; - - //! @} - //! \name boost::asio accessor methods - //! @{ - - using next_layer_type = typename std::remove_reference::type; - - const next_layer_type& next_layer() const { return m_nextLayer; } - - next_layer_type& next_layer() { return m_nextLayer; } - - #if VERSION >= 107000 - /* - * From Boost 1.70 onwards Beast types no longer provide public access to the member function `lowest_layer()`. - * Instead, the new free-standing functions in Beast need to be used. - * See also: https://github.com/boostorg/beast/commit/6a658b5c3a36f8d58334f8b6582c01c3e87768ae - */ - using lowest_layer_type = typename boost::beast::lowest_layer_type; - - lowest_layer_type& lowest_layer() { return boost::beast::get_lowest_layer(m_nextLayer); } - - const lowest_layer_type& lowest_layer() const { return boost::beast::get_lowest_layer(m_nextLayer); } - #else - using lowest_layer_type = typename next_layer_type::lowest_layer_type; - - lowest_layer_type& lowest_layer() { return m_nextLayer.lowest_layer(); } - - const lowest_layer_type& lowest_layer() const { return m_nextLayer.lowest_layer(); } - #endif - - using executor_type = typename next_layer_type::executor_type; - - executor_type get_executor() noexcept { return m_nextLayer.get_executor(); } - - using native_handle_type = typename std::add_pointer::type; - - native_handle_type native_handle() { - if(m_native_handle == nullptr) { - throw Invalid_State("Invalid handshake state"); - } - return m_native_handle.get(); - } - - //! @} - //! \name configuration and callback setters - //! @{ - - /** - * @brief Override the tls_verify_cert_chain callback - * - * This changes the verify_callback in the stream's TLS::Context, and hence the tls_verify_cert_chain callback - * used in the handshake. - * Using this function is equivalent to setting the callback via @see Botan::TLS::Context::set_verify_callback - * - * @note This function should only be called before initiating the TLS handshake - */ - void set_verify_callback(Context::Verify_Callback callback) { - m_context->set_verify_callback(std::move(callback)); - } - - /** - * @brief Compatibility overload of @ref set_verify_callback - * - * @param callback the callback implementation - * @param ec This parameter is unused. - */ - void set_verify_callback(Context::Verify_Callback callback, boost::system::error_code& ec) { - BOTAN_UNUSED(ec); - m_context->set_verify_callback(std::move(callback)); - } - - //! @throws Not_Implemented - void set_verify_depth(int depth) { - BOTAN_UNUSED(depth); - throw Not_Implemented("set_verify_depth is not implemented"); - } - - /** - * Not Implemented. - * @param depth the desired verification depth - * @param ec Will be set to `Botan::ErrorType::NotImplemented` - */ - void set_verify_depth(int depth, boost::system::error_code& ec) { - BOTAN_UNUSED(depth); - ec.assign((int)ErrorType::NotImplemented, std::generic_category()); - } - - //! @throws Not_Implemented - template - void set_verify_mode(verify_mode v) { - BOTAN_UNUSED(v); - throw Not_Implemented("set_verify_mode is not implemented"); - } - - /** - * Not Implemented. - * @param v the desired verify mode - * @param ec Will be set to `Botan::ErrorType::NotImplemented` - */ - template - void set_verify_mode(verify_mode v, boost::system::error_code& ec) { - BOTAN_UNUSED(v); - ec.assign((int)ErrorType::NotImplemented, std::generic_category()); - } - - //! @} - //! \name handshake methods - //! @{ - - /** - * @brief Performs SSL handshaking. - * - * The function call will block until handshaking is complete or an error occurs. - * - * @param side The type of handshaking to be performed, i.e. as a client or as a server. - * @throws boost::system::system_error if error occured - */ - void handshake(Connection_Side side) { - boost::system::error_code ec; - handshake(side, ec); - boost::asio::detail::throw_error(ec, "handshake"); - } - - /** - * @brief Performs SSL handshaking. - * - * The function call will block until handshaking is complete or an error occurs. - * - * @param side The type of handshaking to be performed, i.e. as a client or as a server. - * @param ec Set to indicate what error occurred, if any. - */ - void handshake(Connection_Side side, boost::system::error_code& ec) { - setup_native_handle(side, ec); - - if(side == Connection_Side::Client) { - // send client hello, which was written to the send buffer on client instantiation - send_pending_encrypted_data(ec); - } - - while(!native_handle()->is_active() && !ec) { - boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)}; - if(ec) { - return; - } - - process_encrypted_data(read_buffer, ec); - - send_pending_encrypted_data(ec); - } - } - - /** - * @brief Starts an asynchronous SSL handshake. - * - * This function call always returns immediately. - * - * @param side The type of handshaking to be performed, i.e. as a client or as a server. - * @param completion_token The completion handler to be called when the handshake operation completes. - * The completion signature of the handler must be: void(boost::system::error_code). - */ - template - auto async_handshake(Botan::TLS::Connection_Side side, CompletionToken&& completion_token) { - return boost::asio::async_initiate( - [this](auto&& completion_handler, TLS::Connection_Side connection_side) { - using completion_handler_t = std::decay_t; - - ASIO_HANDSHAKE_HANDLER_CHECK(completion_handler_t, completion_handler) type_check; - - boost::system::error_code ec; - setup_native_handle(connection_side, ec); - - detail::AsyncHandshakeOperation op{ - std::forward(completion_handler), *this, ec}; - }, - completion_token, - side); - } - - //! @throws Not_Implemented - template - ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, void(boost::system::error_code, std::size_t)) - async_handshake(Connection_Side side, const ConstBufferSequence& buffers, BufferedHandshakeHandler&& handler) { - BOTAN_UNUSED(side, buffers, handler); - ASIO_HANDSHAKE_HANDLER_CHECK(BufferedHandshakeHandler, handler) type_check; - throw Not_Implemented("buffered async handshake is not implemented"); - } - - //! @} - //! \name shutdown methods - //! @{ - - /** - * @brief Shut down SSL on the stream. - * - * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down - * or an error occurs. Note that this will not close the lowest layer. - * - * Note that this can be used in reaction of a received shutdown alert from the peer. - * - * @param ec Set to indicate what error occured, if any. - */ - void shutdown(boost::system::error_code& ec) { - try_with_error_code([&] { native_handle()->close(); }, ec); - - send_pending_encrypted_data(ec); - } - - /** - * @brief Shut down SSL on the stream. - * - * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down - * or an error occurs. Note that this will not close the lowest layer. - * - * Note that this can be used in reaction of a received shutdown alert from the peer. - * - * @throws boost::system::system_error if error occured - */ - void shutdown() { - boost::system::error_code ec; - shutdown(ec); - boost::asio::detail::throw_error(ec, "shutdown"); - } - - private: - /** - * @brief Internal wrapper type to adapt the expected signature of `async_shutdown` to the completion handler - * signature of `AsyncWriteOperation`. - * - * This is boilerplate to ignore the `size_t` parameter that is passed to the completion handler of - * `AsyncWriteOperation`. Note that it needs to retain the wrapped handler's executor. - */ - template - struct Wrapper { - void operator()(boost::system::error_code ec, std::size_t) { handler(ec); } - - using executor_type = boost::asio::associated_executor_t; - - executor_type get_executor() const noexcept { - return boost::asio::get_associated_executor(handler, io_executor); - } - - using allocator_type = boost::asio::associated_allocator_t; - - allocator_type get_allocator() const noexcept { return boost::asio::get_associated_allocator(handler); } - - Handler handler; - Executor io_executor; - }; - - public: - /** - * @brief Asynchronously shut down SSL on the stream. - * - * This function call always returns immediately. - * - * Note that this can be used in reaction of a received shutdown alert from the peer. - * - * @param completion_token The completion handler to be called when the shutdown operation completes. - * The completion signature of the handler must be: void(boost::system::error_code). - */ - template - auto async_shutdown(CompletionToken&& completion_token) { - return boost::asio::async_initiate( - [this](auto&& completion_handler) { - using completion_handler_t = std::decay_t; - - ASIO_SHUTDOWN_HANDLER_CHECK(completion_handler_t, completion_handler) type_check; - - boost::system::error_code ec; - try_with_error_code([&] { native_handle()->close(); }, ec); - - using write_handler_t = Wrapper; - - TLS::detail::AsyncWriteOperation op{ - write_handler_t{std::forward(completion_handler), get_executor()}, - *this, - boost::asio::buffer_size(send_buffer()), - ec}; - }, - completion_token); - } - - //! @} - //! \name I/O methods - //! @{ - - /** - * @brief Read some data from the stream. - * - * The function call will block until one or more bytes of data has been read successfully, or until an error - * occurs. - * - * @param buffers The buffers into which the data will be read. - * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer - * has closed the connection but did not properly shut down the SSL connection. - * @return The number of bytes read. Returns 0 if an error occurred. - */ - template - std::size_t read_some(const MutableBufferSequence& buffers, boost::system::error_code& ec) { - if(has_received_data()) { - return copy_received_data(buffers); - } - - boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)}; - if(ec) { - return 0; - } - - process_encrypted_data(read_buffer, ec); - - if(ec) // something went wrong in process_encrypted_data() - { - return 0; - } - - if(shutdown_received()) { - // we just received a 'close_notify' from the peer and don't expect any more data - ec = boost::asio::error::eof; - } else if(ec == boost::asio::error::eof) { - // we did not expect this disconnection from the peer - ec.assign(StreamError::StreamTruncated, std::generic_category()); - } - - return !ec ? copy_received_data(buffers) : 0; - } - - /** - * @brief Read some data from the stream. - * - * The function call will block until one or more bytes of data has been read successfully, or until an error - * occurs. - * - * @param buffers The buffers into which the data will be read. - * @return The number of bytes read. Returns 0 if an error occurred. - * @throws boost::system::system_error if error occured - */ - template - std::size_t read_some(const MutableBufferSequence& buffers) { - boost::system::error_code ec; - const auto n = read_some(buffers, ec); - boost::asio::detail::throw_error(ec, "read_some"); - return n; - } - - /** - * @brief Write some data to the stream. - * - * The function call will block until one or more bytes of data has been written successfully, or until an error - * occurs. - * - * @param buffers The data to be written. - * @param ec Set to indicate what error occurred, if any. - * @return The number of bytes processed from the input buffers. - */ - template - std::size_t write_some(const ConstBufferSequence& buffers, boost::system::error_code& ec) { - tls_encrypt(buffers, ec); - send_pending_encrypted_data(ec); - return !ec ? boost::asio::buffer_size(buffers) : 0; - } - - /** - * @brief Write some data to the stream. - * - * The function call will block until one or more bytes of data has been written successfully, or until an error - * occurs. - * - * @param buffers The data to be written. - * @return The number of bytes written. - * @throws boost::system::system_error if error occured - */ - template - std::size_t write_some(const ConstBufferSequence& buffers) { - boost::system::error_code ec; - const auto n = write_some(buffers, ec); - boost::asio::detail::throw_error(ec, "write_some"); - return n; - } - - /** - * @brief Start an asynchronous write. The function call always returns immediately. - * - * @param buffers The data to be written. - * @param completion_token The completion handler to be called when the write operation completes. Copies of the - * handler will be made as required. The completion signature of the handler must be: - * void(boost::system::error_code, std::size_t). - */ - template - auto async_write_some(const ConstBufferSequence& buffers, CompletionToken&& completion_token) { - return boost::asio::async_initiate( - [this](auto&& completion_handler, const auto& bufs) { - using completion_handler_t = std::decay_t; - - ASIO_WRITE_HANDLER_CHECK(completion_handler_t, completion_handler) type_check; - - boost::system::error_code ec; - tls_encrypt(bufs, ec); - - if(ec) { - // we cannot be sure how many bytes were committed here so clear the send_buffer and let the - // AsyncWriteOperation call the handler with the error_code set - consume_send_buffer(m_core->send_buffer.size()); - } - - detail::AsyncWriteOperation op{ - std::forward(completion_handler), - *this, - ec ? 0 : boost::asio::buffer_size(bufs), - ec}; - }, - completion_token, - buffers); - } - - /** - * @brief Start an asynchronous read. The function call always returns immediately. - * - * @param buffers The buffers into which the data will be read. Although the buffers object may be copied as - * necessary, ownership of the underlying buffers is retained by the caller, which must guarantee - * that they remain valid until the handler is called. - * @param completion_token The completion handler to be called when the read operation completes. The completion - * signature of the handler must be: void(boost::system::error_code, std::size_t). - */ - template - auto async_read_some(const MutableBufferSequence& buffers, CompletionToken&& completion_token) { - return boost::asio::async_initiate( - [this](auto&& completion_handler, const auto& bufs) { - using completion_handler_t = std::decay_t; - - ASIO_READ_HANDLER_CHECK(completion_handler_t, completion_handler) type_check; - - detail::AsyncReadOperation op{ - std::forward(completion_handler), *this, bufs}; - }, - completion_token, - buffers); - } - - //! @} - - //! @brief Indicates whether a close_notify alert has been received from the peer. - //! - //! Note that we cannot m_core.is_closed_for_reading() because this wants to - //! explicitly check that the peer sent close_notify. - bool shutdown_received() const { return m_core->shutdown_received; } - - protected: - template - friend class detail::AsyncReadOperation; - template - friend class detail::AsyncWriteOperation; - template - friend class detail::AsyncHandshakeOperation; - - /** - * @brief Helper class that implements TLS::Callbacks - * - * This class is provided to the stream's native_handle (TLS::Channel) and implements the callback - * functions triggered by the native_handle. - */ - class StreamCore : public TLS::Callbacks { - public: - StreamCore(std::weak_ptr context) : shutdown_received(false), m_context(context) {} - - ~StreamCore() override = default; - - void tls_emit_data(std::span data) override { - send_buffer.commit(boost::asio::buffer_copy(send_buffer.prepare(data.size()), - boost::asio::buffer(data.data(), data.size()))); - } - - void tls_record_received(uint64_t, std::span data) override { - receive_buffer.commit(boost::asio::buffer_copy(receive_buffer.prepare(data.size()), - boost::asio::const_buffer(data.data(), data.size()))); - } - - bool tls_peer_closed_connection() override { - // Instruct the TLS implementation to reply with our close_notify to obtain - // the same behaviour for TLS 1.2 and TLS 1.3. - return true; - } - - void tls_alert(TLS::Alert alert) override { - if(alert.type() == TLS::AlertType::CloseNotify) { - shutdown_received = true; - // Channel::process_alert will automatically write the corresponding close_notify response to the - // send_buffer and close the native_handle after this function returns. - } - } - - std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const override { - return std::chrono::milliseconds(1000); - } - - void tls_verify_cert_chain(const std::vector& cert_chain, - const std::vector>& ocsp_responses, - const std::vector& trusted_roots, - Usage_Type usage, - std::string_view hostname, - const TLS::Policy& policy) override { - auto ctx = m_context.lock(); - - if(ctx && ctx->has_verify_callback()) { - ctx->get_verify_callback()(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy); - } else { - Callbacks::tls_verify_cert_chain(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy); - } - } - - bool shutdown_received; - boost::beast::flat_buffer receive_buffer; - boost::beast::flat_buffer send_buffer; - - private: - std::weak_ptr m_context; - }; - - const boost::asio::mutable_buffer& input_buffer() { return m_input_buffer; } - - boost::asio::const_buffer send_buffer() const { return m_core->send_buffer.data(); } - - //! @brief Check if decrypted data is available in the receive buffer - bool has_received_data() const { return m_core->receive_buffer.size() > 0; } - - //! @brief Copy decrypted data into the user-provided buffer - template - std::size_t copy_received_data(MutableBufferSequence buffers) { - // Note: It would be nice to avoid this buffer copy. This could be achieved by equipping the StreamCore with - // the user's desired target buffer once a read is started, and reading directly into that buffer in tls_record - // received. However, we need to deal with the case that the receive buffer provided by the caller is smaller - // than the decrypted record, so this optimization might not be worth the additional complexity. - const auto copiedBytes = boost::asio::buffer_copy(buffers, m_core->receive_buffer.data()); - m_core->receive_buffer.consume(copiedBytes); - return copiedBytes; - } - - //! @brief Check if encrypted data is available in the send buffer - bool has_data_to_send() const { return m_core->send_buffer.size() > 0; } - - //! @brief Mark bytes in the send buffer as consumed, removing them from the buffer - void consume_send_buffer(std::size_t bytesConsumed) { m_core->send_buffer.consume(bytesConsumed); } - - /** - * @brief Create the native handle. - * - * Depending on the desired connection side, this function will create a TLS::Client or a - * TLS::Server. - * - * @param side The desired connection side (client or server) - * @param ec Set to indicate what error occurred, if any. - */ - void setup_native_handle(Connection_Side side, boost::system::error_code& ec) { - BOTAN_UNUSED(side); // workaround: GCC 9 produces a warning claiming side is unused - - // Do not attempt to instantiate the native_handle when a custom (mocked) channel type template parameter has - // been specified. This allows mocking the native_handle in test code. - if constexpr(std::is_same::value) { - try_with_error_code( - [&] { - if(side == Connection_Side::Client) { - m_native_handle = std::unique_ptr( - new Client(m_core, - m_context->m_session_manager, - m_context->m_credentials_manager, - m_context->m_policy, - m_context->m_rng, - m_context->m_server_info, - m_context->m_policy->latest_supported_version(false /* no DTLS */))); - } else { - m_native_handle = std::unique_ptr(new Server(m_core, - m_context->m_session_manager, - m_context->m_credentials_manager, - m_context->m_policy, - m_context->m_rng, - false /* no DTLS */)); - } - }, - ec); - } - } - - /** @brief Synchronously write encrypted data from the send buffer to the next layer. - * - * If this function is called with an error code other than 'Success', it will do nothing and return 0. - * - * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer - * has closed the connection but did not properly shut down the SSL connection. - * @return The number of bytes written. - */ - size_t send_pending_encrypted_data(boost::system::error_code& ec) { - if(ec) { - return 0; - } - - auto writtenBytes = boost::asio::write(m_nextLayer, send_buffer(), ec); - consume_send_buffer(writtenBytes); - - if(ec == boost::asio::error::eof && !shutdown_received()) { - // transport layer was closed by peer without receiving 'close_notify' - ec.assign(StreamError::StreamTruncated, std::generic_category()); - } - - return writtenBytes; - } - - /** - * @brief Pass plaintext data to the native handle for processing. - * - * The native handle will then create TLS records and hand them back to the Stream via the tls_emit_data callback. - */ - template - void tls_encrypt(const ConstBufferSequence& buffers, boost::system::error_code& ec) { - // NOTE: This is not asynchronous: it encrypts the data synchronously. - // The data encrypted by native_handle()->send() is synchronously stored in the send_buffer of m_core, - // but is not actually written to the wire, yet. - for(auto it = boost::asio::buffer_sequence_begin(buffers); - !ec && it != boost::asio::buffer_sequence_end(buffers); - it++) { - const boost::asio::const_buffer buffer = *it; - try_with_error_code( - [&] { - native_handle()->send({static_cast(buffer.data()), buffer.size()}); - }, - ec); - } - } - - /** - * @brief Pass encrypted data to the native handle for processing. - * - * If an exception occurs while processing the data, an error code will be set. - * - * @param read_buffer Input buffer containing the encrypted data. - * @param ec Set to indicate what error occurred, if any. - */ - void process_encrypted_data(const boost::asio::const_buffer& read_buffer, boost::system::error_code& ec) { - try_with_error_code( - [&] { - native_handle()->received_data({static_cast(read_buffer.data()), read_buffer.size()}); - }, - ec); - } - - //! @brief Catch exceptions and set an error_code - template - void try_with_error_code(Fun f, boost::system::error_code& ec) { - f(); - } - - std::shared_ptr m_context; - StreamLayer m_nextLayer; - - std::shared_ptr m_core; - std::unique_ptr m_native_handle; - - // Buffer space used to read input intended for the core - std::vector m_input_buffer_space; - const boost::asio::mutable_buffer m_input_buffer; -}; - -} // namespace Botan::TLS - -#endif // BOTAN_ASIO_STREAM_H_ diff --git a/src/data/data.cxx b/src/data/data.cxx index 70a9085..719875c 100644 --- a/src/data/data.cxx +++ b/src/data/data.cxx @@ -35,7 +35,8 @@ void Data::load_config() { inipp::Ini ini; if (!std::filesystem::exists(ini_name)) { ini.sections["web"] = { - {"port", "80"} + {"port", "80"}, + {"threads", "6"} }; ini.sections["smtp"] = { {"host", "127.0.0.1"}, @@ -68,6 +69,7 @@ void Data::load_config() { std::string entry; #define get_entry(section, key) if (!ini.sections[#section].contains(#key)) {data_logger->critical("Missing " #section ":" #key); crash();} entry = ini.sections[#section][#key] get_entry(web, port); config.server_port = std::stoul(entry); + get_entry(web, threads); config.threads = std::stoul(entry); get_entry(smtp, host); config.smtp_host = entry; get_entry(smtp, port); config.smtp_port = std::stoul(entry); get_entry(smtp, user); config.smtp_user = entry; diff --git a/src/data/data.hxx b/src/data/data.hxx index 967abb3..ad58c7d 100644 --- a/src/data/data.hxx +++ b/src/data/data.hxx @@ -49,7 +49,7 @@ struct Token { struct Config { std::string smtp_host, smtp_user, smtp_pass, smtp_from, admin_mail; - std::uint16_t smtp_port, server_port; + std::uint16_t smtp_port, server_port, threads; }; struct Data { diff --git a/src/main.cxx b/src/main.cxx index bb27351..4243324 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -1,36 +1,122 @@ #include #include -#include -#include -#include -#include -#include -#include #include -#include "util/logging.hxx" +#include +#include #include "server/server.hxx" +#include "util/botan.hxx" #include "index_html.h" #include "favicon_svg.h" -std::shared_ptr g_service = nullptr; +using tcp = boost::asio::ip::tcp; -const static restbed::Bytes index_html_bytes{index_html, index_html + index_html_len}; -const static restbed::Bytes favicon_bytes{favicon_svg, favicon_svg + favicon_svg_len}; +std::function g_stop_service; +std::shared_ptr err_handler::logger{}; -void signal_shutdown(const int) { - spdlog::info("Received stop signal"); - g_service->stop(); +Server server{}; +std::string index_etag{}; + +net::awaitable do_session(tcp_stream stream) { + tcp_buffer buffer; + beast::error_code ec; + stream.expires_after(std::chrono::seconds(30)); + + try { + while (true) { + http::request_parser req_base_parser; + req_base_parser.body_limit(boost::none); + co_await http::async_read_header(stream, buffer, req_base_parser); + + auto &req_base = req_base_parser.get(); + bool keep_alive = req_base.keep_alive(); + + auto addr = stream.socket().remote_endpoint().address().to_string(); + if (!req_base["x-forwarded-for"].empty()) + addr = std::string{req_base["x-forwarded-for"]} + "' via '" + addr; + spdlog::info("Request: '{} {}' from '{}'", req_base.method_string(), req_base.target(), addr); + + if (req_base.method() == http::verb::post && req_base.target() == "/mrpc") { + req_base_parser.body_limit(1024*1024*8); // 8 MB + http::request_parser req{std::move(req_base_parser)}; + co_await http::async_read(stream, buffer, req); + co_await server.msg_handler(stream, req.get()); + } else if (req_base.method() == http::verb::get && req_base.target() == "/") { + co_await http::async_read(stream, buffer, req_base_parser); + if (req_base["If-None-Match"] == index_etag) { + auto res = create_response(req_base); + res.set(http::field::cache_control, "no-cache"); + res.set(http::field::etag, index_etag); + co_await http::async_write(stream, res, net::use_awaitable); + } else { + auto res = create_response(req_base); + res.set(http::field::content_type, "text/html"); + res.content_length(index_html_len); + res.set(http::field::cache_control, "no-cache"); + res.set(http::field::etag, index_etag); + res.body().data = index_html; + res.body().size = index_html_len; + res.body().more = false; + co_await http::async_write(stream, res, net::use_awaitable); + } + } else if (req_base.method() == http::verb::post && req_base.target() == "/download") { + req_base_parser.body_limit(1024*1024*8); + http::request_parser req{std::move(req_base_parser)}; + co_await http::async_read(stream, buffer, req); + co_await server.download(stream, req.get()); + } else if (req_base.method() == http::verb::post && req_base.target() == "/upload") { + http::request_parser req_parser{std::move(req_base_parser)}; + co_await server.upload(stream, buffer, req_parser); + keep_alive = false; + } else if (req_base.method() == http::verb::post && req_base.target() == "/download_multi") { + req_base_parser.body_limit(1024*1024*8); + http::request_parser req{std::move(req_base_parser)}; + co_await http::async_read(stream, buffer, req); + co_await server.download_multi(stream, req.get()); + } else if (req_base.method() == http::verb::get && req_base.target() == "/favicon.svg") { + co_await http::async_read(stream, buffer, req_base_parser); + auto res = create_response(req_base); + res.set(http::field::content_type, "image/svg+xml"); + res.content_length(favicon_svg_len); + res.body().data = favicon_svg; + res.body().size = favicon_svg_len; + res.body().more = false; + co_await http::async_write(stream, res, net::use_awaitable); + } else { + http::response res{http::status::not_found, req_base.version()}; + res.set(http::field::content_type, "text/plain"); + res.keep_alive(false); + keep_alive = false; + res.body() = "404 not found"; + res.prepare_payload(); + co_await http::async_write(stream, res, net::use_awaitable); + } + + if (!keep_alive) + break; + } + } catch (beast::system_error &se) { + if (se.code() != http::error::end_of_stream) + throw; + } + + ec = stream.socket().shutdown(tcp::socket::shutdown_send, ec); } -void error_handler(const int code, const std::exception& ex, const std::shared_ptr session) { - std::stringstream ss; - ss << "Encountered error with code '" << std::to_string(code) << "'"; - if (session != nullptr) - ss << " in session from '" << session->get_origin() << "'"; - ss << ": " << ex.what(); - spdlog::error(ss.str()); - if (session != nullptr) - session->close(code, ex.what()); +net::awaitable do_listen(tcp::endpoint endpoint) { + auto acceptor = net::use_awaitable.as_default_on(tcp::acceptor(co_await net::this_coro::executor)); + + acceptor.open(endpoint.protocol()); + acceptor.set_option(net::socket_base::reuse_address(true)); + acceptor.bind(endpoint); + acceptor.listen(net::socket_base::max_listen_connections); + + while (true) { + net::co_spawn( + acceptor.get_executor(), + do_session(tcp_stream(co_await acceptor.async_accept())), + err_handler{} + ); + } } int main() { @@ -39,83 +125,28 @@ int main() { spdlog::default_logger()->sinks().push_back(file_sink); spdlog::set_level(spdlog::level::trace); - std::string index_etag; { auto md5_hash = Botan::HashFunction::create_or_throw("MD5"); - md5_hash->update(index_html_bytes); + md5_hash->update(index_html, index_html_len); index_etag = "\"" + Botan::hex_encode(md5_hash->final()) + "\""; } - auto mrpc_resource = std::make_shared(); - mrpc_resource->set_path("/mrpc"); - Server server{mrpc_resource}; + net::io_context ioc{server.config.threads}; + net::co_spawn(ioc, do_listen(tcp::endpoint{net::ip::address_v4::any(), server.config.server_port}), err_handler{}); -#define mk_res(url) auto url##_resource = std::make_shared(); \ - url##_resource->set_path("/" #url); \ - url##_resource->set_method_handler("POST", [&server](auto s){ server.url(s); }) + net::signal_set signals{ioc, SIGINT, SIGTERM}; + signals.async_wait([&ioc](const beast::error_code&, int){ ioc.stop(); }); + g_stop_service = [&ioc] { ioc.stop(); }; - mk_res(download); - mk_res(download_multi); - mk_res(upload); -#undef mk_res + spdlog::info("Listening on :{}", server.config.server_port); - auto index_resource = std::make_shared(); - index_resource->set_path("/"); - index_resource->set_method_handler("GET", [&index_etag](const std::shared_ptr& s){ - auto req = s->get_request(); - if (req->get_header("If-None-Match", "") == index_etag) { - s->yield( - 304, - "", - std::multimap{ - {"Cache-Control", "no-cache"}, - {"ETag", index_etag} - } - ); - } else { - s->yield( - 200, - index_html_bytes, - std::multimap{ - {"Content-Type", "text/html"}, - {"Content-Length", std::to_string(index_html_len)}, - {"Cache-Control", "no-cache"}, - {"ETag", index_etag} - } - ); - } - }); + std::vector v; + for (std::uint16_t i = server.config.threads-1; i > 0; --i) + v.emplace_back([&ioc] { ioc.run(); }); + ioc.run(); - auto favicon_resource = std::make_shared(); - favicon_resource->set_path("/favicon.svg"); - favicon_resource->set_method_handler("GET", [](const std::shared_ptr& s){ - s->yield( - 200, - favicon_bytes, - std::multimap{ - {"Content-Type", "image/svg+xml"}, - {"Content-Length", std::to_string(favicon_svg_len)} - } - ); - }); - - auto settings = std::make_shared(); - settings->set_port(server.config.server_port); - settings->set_default_header("Connection", "keep-alive"); - - g_service = std::make_shared(); - g_service->set_error_handler(error_handler); - g_service->set_logger(std::make_shared()); - g_service->set_signal_handler(SIGINT, signal_shutdown); - g_service->set_signal_handler(SIGTERM, signal_shutdown); - g_service->publish(mrpc_resource); - g_service->publish(download_resource); - g_service->publish(download_multi_resource); - g_service->publish(upload_resource); - g_service->publish(index_resource); - g_service->publish(favicon_resource); - g_service->start(settings); - g_service.reset(); + for (auto &t : v) + t.join(); return 0; } diff --git a/src/server/admin.cxx b/src/server/admin.cxx index d8dacd7..62ccb82 100644 --- a/src/server/admin.cxx +++ b/src/server/admin.cxx @@ -97,6 +97,6 @@ std::optional Server::Admin_unsudo(std::string &&token) { std::optional Server::Admin_shutdown(std::string &&token) { check_admin_optional(); spdlog::info("Received rpc shutdown request from admin user {}", user->name); - g_service->stop(); + g_stop_service(); return std::nullopt; } diff --git a/src/server/download.cxx b/src/server/download.cxx index ce28db0..d0afb07 100644 --- a/src/server/download.cxx +++ b/src/server/download.cxx @@ -4,181 +4,146 @@ #include #include #include -#include -#include -#include -#include +#include "../util/miniz.hxx" #include "server_internal.hxx" -void Server::download(const std::shared_ptr &s) { - const auto req = s->get_request(); - std::size_t body_len = req->get_header("Content-Length", 0); - s->fetch(body_len, [this](const std::shared_ptr &s, const restbed::Bytes &b){ - std::string body{b.cbegin(), b.cend()}; - if (body.empty()) - return s->close(400, "empty body"); - std::string node_str, token; - for (const auto part : std::views::split(body, '&')) { - std::string_view part_view{part}; - auto equal_pos = part_view.find_first_of('='); - auto key = part_view.substr(0, equal_pos); - if (key == "node") - node_str = part_view.substr(equal_pos+1); - else if (key == "token") - token = part_view.substr(equal_pos+1); - } - if (node_str.empty()) - return s->close(400, "Missing node"); - if (token.empty()) - return s->close(400, "Missing token"); +net::awaitable Server::download(tcp_stream &s, const http::request &req) { + auto body = req.body(); + if (body.empty()) { co_await send_error(s, req, "Empty body"); co_return; } - std::uint64_t node_id; - auto res = std::from_chars(node_str.data(), node_str.data() + node_str.size(), node_id); - if (res.ec != std::errc{}) - return s->close(400, "Invalid node"); - - check_user() return s->close(400, "Invalid user"); - { - std::shared_lock lock{user->node_lock}; - auto node = get_node(user, node_id); - if (!node) return s->close(400, "Invalid node"); - - auto mime = get_mime_type(node->name); - - s->yield( - 200, - "", - std::multimap{ - {"Content-Type", mime}, - {"Content-Length", std::to_string(node->size)}, - {"Content-Disposition", "attachment; filename=\"" + node->name + "\""} - }, - [user=user, node=node](const std::shared_ptr& s) { - std::shared_lock lock{user->node_lock}; - restbed::Bytes buf(1024*1024*4, 0); - std::ifstream f{user->user_dir / std::to_string(node->id)}; - while (!f.eof()) { - buf.resize(buf.capacity()); - f.read((char*)buf.data(), buf.size()); - buf.resize(f.gcount()); - s->yield(buf); - } - s->close(); - } - ); - } - }); -} - -size_t zip_write_func(void *pOpaque, mz_uint64 _file_ofs, const void *pBuf, size_t n) { - auto s = (restbed::Session*)pOpaque; - if (n > 0) { - restbed::Bytes buf(n, 0); - std::memcpy(buf.data(), pBuf, n); - std::stringstream ss; - ss << std::hex << n; - s->yield(ss.str() + "\r\n"); - s->yield(buf); - s->yield("\r\n"); + std::string node_str, token; + for (const auto part : std::views::split(body, '&')) { + std::string_view part_view{part}; + auto equal_pos = part_view.find_first_of('='); + auto key = part_view.substr(0, equal_pos); + if (key == "node") + node_str = part_view.substr(equal_pos+1); + else if (key == "token") + token = part_view.substr(equal_pos+1); } - return n; + + if (node_str.empty()) { co_await send_error(s, req, "Missing node"); co_return; } + if (token.empty()) { co_await send_error(s, req, "Missing token"); co_return; } + + std::uint64_t node_id; + auto fc_res = std::from_chars(node_str.data(), node_str.data() + node_str.size(), node_id); + if (fc_res.ec != std::errc{}) { co_await send_error(s, req, "Invalid node"); co_return; } + + check_user() { co_await send_error(s, req, "Invalid user"); co_return; } + + std::shared_lock lock{user->node_lock}; + auto node = get_node(user, node_id); + if (!node) { co_await send_error(s, req, "Invalid node"); co_return; } + + beast::error_code ec; + auto mime = get_mime_type(node->name); + auto res = create_response(req); + res.content_length(node->size); + res.set(http::field::content_type, mime); + res.set(http::field::content_disposition, "attachment; filename=\"" + node->name + "\""); + res.body().open((user->user_dir / std::to_string(node->id)).c_str(), beast::file_mode::read, ec); + co_await http::async_write(s, res, net::use_awaitable); } -void Server::download_multi(const std::shared_ptr &s) { - const auto req = s->get_request(); - const auto body_len = req->get_header("Content-Length", 0); - s->fetch(body_len, [this](const std::shared_ptr &s, const restbed::Bytes &b){ - std::string body{b.cbegin(), b.cend()}; - if (body.empty()) - return s->close(400, "empty body"); - std::string nodes_str, token; - for (const auto part : std::views::split(body, '&')) { - std::string_view part_view{part}; - auto equal_pos = part_view.find_first_of('='); - auto key = part_view.substr(0, equal_pos); - if (key == "nodes") - nodes_str = part_view.substr(equal_pos+1); - else if (key == "token") - token = part_view.substr(equal_pos+1); - } - if (nodes_str.empty()) - return s->close(400, "Missing nodes"); - if (token.empty()) - return s->close(400, "Missing token"); +struct Zip : public ZipArchive { + tcp_stream *s; + http::response *body; + http::response_serializer *sr; - std::vector node_ids; - for (const auto part : std::views::split(nodes_str, '.')) { - std::uint64_t node_id; - auto res = std::from_chars(part.data(), part.data() + part.size(), node_id); - if (res.ec != std::errc{}) - return s->close(400, "Invalid node " + std::string{std::string_view{part}}); - node_ids.push_back(node_id); - } + Zip(tcp_stream *s, http::response *body, http::response_serializer *sr) + : s(s), body(body), sr(sr) {} - check_user() return s->close(400, "Invalid user"); - { - std::shared_lock lock{user->node_lock}; - std::vector> nodes; - for (auto node_id : node_ids) { - auto node = get_node(user, node_id); - if (!node) return s->close(400, "Invalid node " + std::to_string(node_id)); - nodes.push_back(node); +protected: + net::awaitable write(const void *pVoid, size_t n) override { + if (n == 0) + co_return; + + body->body().data = (void*)pVoid; + body->body().size = n; + body->body().more = true; + + try { + co_await http::async_write(*s, *sr, net::use_awaitable); + } catch (beast::system_error &se) { + if (se.code() != http::error::need_buffer) + throw; + } + } +}; + +net::awaitable Server::download_multi(tcp_stream &s, const http::request &req) { + auto body = req.body(); + if (body.empty()) { co_await send_error(s, req, "Empty body"); co_return; } + + std::string nodes_str, token; + for (const auto part : std::views::split(body, '&')) { + std::string_view part_view{part}; + auto equal_pos = part_view.find_first_of('='); + auto key = part_view.substr(0, equal_pos); + if (key == "nodes") + nodes_str = part_view.substr(equal_pos+1); + else if (key == "token") + token = part_view.substr(equal_pos+1); + } + + if (nodes_str.empty()) { co_await send_error(s, req, "Missing nodes"); co_return; } + if (token.empty()) { co_await send_error(s, req, "Missing token"); co_return; } + + std::vector node_ids; + for (const auto part : std::views::split(nodes_str, '.')) { + std::uint64_t node_id; + auto res = std::from_chars(part.data(), part.data() + part.size(), node_id); + if (res.ec != std::errc{}) { co_await send_error(s, req, "Invalid node " + std::string{std::string_view{part}}); co_return; } + node_ids.push_back(node_id); + } + + check_user() { co_await send_error(s, req, "Invalid user"); co_return; } + + std::shared_lock lock{user->node_lock}; + std::vector> nodes; + for (auto node_id : node_ids) { + auto node = get_node(user, node_id); + if (!node) { co_await send_error(s, req, "Invalid node " + std::to_string(node_id)); co_return; } + nodes.push_back(node); + } + + auto res = create_response(req); + res.chunked(true); + res.set(http::field::content_type, "application/zip"); + res.set(http::field::content_disposition, "attachment; filename=\"files.zip\""); + res.body().data = nullptr; + res.body().more = true; + + http::response_serializer sr{res}; + + co_await http::async_write_header(s, sr, net::use_awaitable); + + Zip zip{&s, &res, &sr}; + + std::deque, std::filesystem::path>> todo; + for (const auto &node : nodes) + todo.emplace_back(node, std::filesystem::path{}); + + while (!todo.empty()) { + const auto &node = todo.front(); + if (node.first->file) { + auto path = node.second / node.first->name; + auto real_path = user->user_dir / std::to_string(node.first->id); + co_await zip.add_file(real_path, path); + } else { + auto path = node.second / node.first->name; + auto dir_path = path.string() + "/"; + co_await zip.add_dir(dir_path); + for (const auto &child : node.first->children) { + auto p = std::make_pair(child, path); + todo.push_back(p); } - - s->yield( - 200, - "", - std::multimap{ - {"Content-Type", "application/zip"}, - {"Content-Disposition", "attachment; filename=\"files.zip\""}, - {"Transfer-Encoding", "chunked"} - } - ); - std::thread zip_thread{[nodes = nodes, user = user, s = s] { - std::shared_lock lock{user->node_lock}; - - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - archive.m_pWrite = zip_write_func; - archive.m_pIO_opaque = s.get(); - - mz_zip_writer_init_v2(&archive, 0, MZ_ZIP_FLAG_WRITE_ZIP64); - - std::deque, std::filesystem::path>> todo; - for (const auto &node : nodes) - todo.emplace_back(node, std::filesystem::path{}); - - auto handle_file = [&user, &archive](const std::pair, std::filesystem::path> &i) { - auto path = i.second / i.first->name; - auto real_path = user->user_dir / std::to_string(i.first->id); - mz_zip_writer_add_file(&archive, path.c_str(), real_path.c_str(), nullptr, 0, MZ_DEFAULT_COMPRESSION); - }; - - while (!todo.empty()) { - const auto &node = todo.front(); - if (node.first->file) { - handle_file(node); - } else { - auto path = node.second / node.first->name; - auto dir_path = path.string() + "/"; - mz_zip_writer_add_mem(&archive, dir_path.c_str(), nullptr, 0, 0); - for (const auto &child : node.first->children) { - auto p = std::make_pair(child, path); - if (child->file) - handle_file(p); - else - todo.push_back(p); - } - } - todo.pop_front(); - } - - mz_zip_writer_finalize_archive(&archive); - mz_zip_writer_end(&archive); - - s->close("0\r\n\r\n"); - }}; - zip_thread.detach(); } - }); + todo.pop_front(); + } + co_await zip.end(); + + res.body().data = nullptr; + res.body().more = false; + co_await http::async_write(s, sr, net::use_awaitable); } diff --git a/src/server/fs.cxx b/src/server/fs.cxx index cb22148..8de7d2f 100644 --- a/src/server/fs.cxx +++ b/src/server/fs.cxx @@ -34,43 +34,6 @@ std::string get_path(std::shared_ptr node) { return ret.empty() ? "/" : ret; } -void Server::delete_node(const std::shared_ptr &user, std::uint64_t id, const std::function& log) { - std::unique_lock lock{user->node_lock}; - std::stack> todo; - { - auto start = user->nodes.find(id); - if (start == user->nodes.end()) return; - todo.push(start->second); - } - while (!todo.empty()) { - auto node = todo.top(); - auto log_path = get_path(node); - - if (!node->children.empty()) { - log("Entering " + log_path + "\n"); - for (const auto &child : node->children) - todo.push(child); - node->children.clear(); - continue; - } - - log("Deleting " + log_path + "..."); - if (node->file) { - auto path = user->user_dir / std::to_string(node->id); - std::filesystem::remove(path); - if (node->preview) - std::filesystem::remove(path.replace_extension("png")); - } - if (node->parent) - node->parent->children.remove(node); - node->parent.reset(); - user->nodes.erase(node->id); - log(" Done\n"); - - todo.pop(); - } -} - std::uint64_t Server::nodes_size(const std::shared_ptr &user, const std::vector &ids) { std::uint64_t total = 0; std::deque todo; @@ -231,22 +194,53 @@ std::optional Server::FS_move_nodes(std::string &&token, std::vecto return std::nullopt; } -void Server::FS_delete_nodes(std::string &&token, std::vector &&nodes, mrpc::MRPCStream &&stream) { +void Server::FS_delete_nodes(std::string &&token, std::vector &&nodes, mrpc::MRPCStream &stream) { check_user() { stream.close(); return; } - std::thread deleter{[this, nodes = std::move(nodes), user = std::move(user), stream = std::move(stream)](){ - for (const auto& node : nodes) { - if (node == 0) - continue; - delete_node(user, node, [&stream](const std::string &log){ stream.send(log); }); + std::unique_lock lock{user->node_lock}; + std::stack> todo; + for (const auto &node_id: nodes) { + if (node_id == 0) + continue; + auto node = user->nodes.find(node_id); + if (node == user->nodes.end()) + continue; + todo.push(node->second); + } + while (!todo.empty()) { + auto node = todo.top(); + auto log_path = get_path(node); + + if (!node->children.empty()) { + stream.send("Entering " + log_path + "\n"); + for (const auto &child: node->children) + todo.push(child); + node->children.clear(); + continue; } - stream.close(); - save(); - }}; - deleter.detach(); + + stream.send("Deleting " + log_path + "..."); + if (node->file) { + auto path = user->user_dir / std::to_string(node->id); + std::filesystem::remove(path); + if (node->preview) + std::filesystem::remove(path.replace_extension("png")); + } + if (node->parent) + node->parent->children.remove(node); + node->parent.reset(); + user->nodes.erase(node->id); + stream.send(" Done\n"); + + todo.pop(); + std::this_thread::sleep_for(std::chrono::seconds{1}); + } + + stream.close(); + save(); } mrpc::Response Server::FS_download_preview(std::string &&token, std::uint64_t &&node_id) { diff --git a/src/server/mail.cxx b/src/server/mail.cxx index a2e0140..45eab7a 100644 --- a/src/server/mail.cxx +++ b/src/server/mail.cxx @@ -1,10 +1,10 @@ -#include -#include +#include #include #include "server_internal.hxx" +#include "../util/boost.hxx" struct SocketData { - asio::streambuf socket_buf; + net::streambuf socket_buf; std::istream socket_istream{&socket_buf}; std::string last_send; }; @@ -20,7 +20,7 @@ void expect_code(SocketData &data, Socket &s, const std::string& code) { std::string line; do { std::string buf; - asio::read_until(s, data.socket_buf, "\n"); + net::read_until(s, data.socket_buf, "\n"); std::getline(data.socket_istream, line, '\n'); } while (line[3] == '-'); if (std::string_view{line}.substr(0, 3) != code) @@ -31,7 +31,7 @@ template void send(SocketData &data, Socket &s, std::string l) { data.last_send = l.substr(0, l.find_first_of('\r')); l += "\r\n"; - asio::write(s, asio::buffer(l, l.size())); + net::write(s, net::buffer(l, l.size())); } std::string get_date() { @@ -44,7 +44,7 @@ std::string get_date() { std::string get_hostname() { try { - return asio::ip::host_name(); + return net::ip::host_name(); } catch (std::exception &_) { return ""; } @@ -58,7 +58,7 @@ struct CredMan : public Botan::Credentials_Manager { } }; -struct Policy : public Botan::TLS::Strict_Policy { +struct Policy : public Botan::TLS::Policy { [[nodiscard]] bool require_cert_revocation_info() const override { return false; } }; @@ -76,7 +76,7 @@ void Server::send_mail(const std::string &email, const std::string &title, const get_date(), email, config.smtp_from, title, body ); - asio::io_service ctx; + net::io_service ctx; auto ssl_ctx = std::make_shared( std::make_shared(), #if defined(BOTAN_HAS_SYSTEM_RNG) @@ -88,12 +88,12 @@ void Server::send_mail(const std::string &email, const std::string &title, const std::make_shared() ); - asio::ip::tcp::socket s{ctx}; - asio::ip::tcp::resolver res{ctx}; + net::ip::tcp::socket s{ctx}; + net::ip::tcp::resolver res{ctx}; SocketData data; - asio::connect(s, res.resolve(config.smtp_host, std::to_string(config.smtp_port))); + net::connect(s, res.resolve(config.smtp_host, std::to_string(config.smtp_port))); expect_code(data, s, "220"); send(data, s, "EHLO " + host_name); @@ -103,7 +103,7 @@ void Server::send_mail(const std::string &email, const std::string &title, const expect_code(data, s, "220"); // switch_to_ssl - Botan::TLS::Stream ss(std::move(s), ssl_ctx); + Botan::TLS::Stream ss(std::move(s), ssl_ctx); ss.handshake(Botan::TLS::Connection_Side::Client); send(data, ss, "EHLO " + host_name); diff --git a/src/server/mrpc/fileserver.cxx b/src/server/mrpc/fileserver.cxx index 5382e26..fb341c2 100644 --- a/src/server/mrpc/fileserver.cxx +++ b/src/server/mrpc/fileserver.cxx @@ -1,8 +1,8 @@ #include "fileserver.hxx" -#include -#include -#include +#include +#include +using namespace boost::asio::experimental::awaitable_operators; using namespace mrpc; @@ -356,67 +356,68 @@ PathSegment& PathSegment::operator<<(const rapidjson::Value &__j) { template -void send_msg(const std::shared_ptr &c, const T &v) { - if (c->is_closed()) - return; +net::awaitable send_msg(tcp_stream &c, const http::request &req, const T &v) { rapidjson::StringBuffer s; mrpc::MRPCJWriter writer{s}; v >> writer; - const auto body_ptr = s.GetString(); - const auto body = restbed::Bytes{body_ptr, body_ptr+s.GetLength()}; - c->yield( - 200, - body, - std::multimap{ - {"Content-Type", "application/json"}, - {"Content-Length", std::to_string(body.size())} - } - ); + auto res = create_response(req); + res.set(http::field::content_type, "application/json"); + res.body() = s.GetString(); + res.prepare_payload(); + co_await http::async_write(c, res, net::use_awaitable); } template -void send_sse_msg(const std::shared_ptr &c, const T &v) { - if (c->is_closed()) - return; +net::awaitable send_sse_msg(tcp_stream *c, const T &v) { rapidjson::StringBuffer s; std::memcpy(s.Push(5), "data:", 5); mrpc::MRPCJWriter writer{s}; v >> writer; std::memcpy(s.Push(2), "\n\n", 2); - const auto body_ptr = s.GetString(); - const auto body = restbed::Bytes{body_ptr, body_ptr+s.GetLength()}; - c->yield(body); + co_await net::async_write(*c, net::buffer(s.GetString(), s.GetLength()), net::use_awaitable); } -mrpc::MRPCStreamImpl::MRPCStreamImpl(const std::shared_ptr &conn) : conn(conn) { - conn->yield( - 200, - std::multimap{ - {"Cache-Control", "no-cache"}, - {"Content-Type", "text/event-stream"} +template +net::awaitable process_channel(tcp_stream *conn, std::shared_ptr> chan) { + http::response res{http::status::ok, 11}; + res.keep_alive(false); + res.set(http::field::cache_control, "no-cache"); + res.set(http::field::content_type, "text/event-stream"); + co_await http::async_write(*conn, res, net::use_awaitable); + try { + while (true) { + auto v = co_await chan->async_receive(net::use_awaitable); + co_await send_sse_msg(conn, v); } + } catch (beast::system_error &se) { + if (se.code() != http::error::end_of_stream) + throw; + } + co_await net::async_write(*conn, net::buffer("data:null\n\n"), net::use_awaitable); +} + +template<> void mrpc::MRPCStream::close() noexcept { + if (!chan->try_send(http::error::end_of_stream, "")) { + std::this_thread::sleep_for(std::chrono::seconds{1}); + chan->try_send(http::error::end_of_stream, ""); + } + chan->close(); +} + +template<> MRPCStream::MRPCStream(tcp_stream *conn) { + chan = std::make_shared>(conn->get_executor(), 32); + net::co_spawn( + conn->get_executor(), + process_channel(conn, chan), + err_handler{} ); } -void mrpc::MRPCStreamImpl::close() const noexcept { conn->close("data:null\n\n"); } -bool mrpc::MRPCStreamImpl::is_open() const noexcept { return conn->is_open(); } -template<> void MRPCStream::send(const std::string &v) const noexcept { send_sse_msg(conn, v); } +template<> void MRPCStream::send(const std::string &v) noexcept { chan->try_send(beast::error_code{}, v); } - -mrpc::MRPCServer::MRPCServer(std::shared_ptr &r) { - r->set_method_handler("POST", [this](const std::shared_ptr& s) { - const auto req = s->get_request(); - const auto body_len = req->get_header("Content-Length", 0); - s->fetch(body_len, [this](const std::shared_ptr &s, auto &&body) { - try { msg_handler(s, body); } - catch (const std::exception &_) { s->close(400); } - }); - }); -} - -void mrpc::MRPCServer::msg_handler(const std::shared_ptr __c, const restbed::Bytes &__msg) { +net::awaitable mrpc::MRPCServer::msg_handler(tcp_stream &__c, const http::request &__req) { rapidjson::Document __j; - __j.Parse((const char*)__msg.data(), __msg.size()); + __j.Parse(__req.body().data(), __req.body().size()); if (__j.HasParseError()) throw std::exception{}; std::string __service, __method; @@ -431,74 +432,76 @@ void mrpc::MRPCServer::msg_handler(const std::shared_ptr __c, std::string username; username << json_get(__data, "username"); std::string password; password << json_get(__data, "password"); - - send_msg(__c, Auth_signup(std::move(username), std::move(password))); + + co_await send_msg(__c, __req, Auth_signup(std::move(username), std::move(password))); } else if (__method == "login") { std::string username; username << json_get(__data, "username"); std::string password; password << json_get(__data, "password"); std::optional otp; otp << json_get(__data, "otp"); - send_msg(__c, Auth_login(std::move(username), std::move(password), std::move(otp))); + co_await send_msg(__c, __req, Auth_login(std::move(username), std::move(password), std::move(otp))); } else if (__method == "send_recovery_key") { std::string username; username << json_get(__data, "username"); - Auth_send_recovery_key(std::move(username)); send_msg(__c, nullptr); + Auth_send_recovery_key(std::move(username)); + co_await send_msg(__c, __req, nullptr); } else if (__method == "reset_password") { std::string key; key << json_get(__data, "key"); std::string password; password << json_get(__data, "password"); - send_msg(__c, Auth_reset_password(std::move(key), std::move(password))); + co_await send_msg(__c, __req, Auth_reset_password(std::move(key), std::move(password))); } else if (__method == "change_password") { std::string token; token << json_get(__data, "token"); std::string old_pw; old_pw << json_get(__data, "old_pw"); std::string new_pw; new_pw << json_get(__data, "new_pw"); - send_msg(__c, Auth_change_password(std::move(token), std::move(old_pw), std::move(new_pw))); + co_await send_msg(__c, __req, Auth_change_password(std::move(token), std::move(old_pw), std::move(new_pw))); } else if (__method == "logout") { std::string token; token << json_get(__data, "token"); - Auth_logout(std::move(token)); send_msg(__c, nullptr); + Auth_logout(std::move(token)); + co_await send_msg(__c, __req, nullptr); } else if (__method == "logout_all") { std::string token; token << json_get(__data, "token"); - send_msg(__c, Auth_logout_all(std::move(token))); + co_await send_msg(__c, __req, Auth_logout_all(std::move(token))); } else if (__method == "tfa_setup_mail") { std::string token; token << json_get(__data, "token"); - send_msg(__c, Auth_tfa_setup_mail(std::move(token))); + co_await send_msg(__c, __req, Auth_tfa_setup_mail(std::move(token))); } else if (__method == "tfa_setup_totp") { std::string token; token << json_get(__data, "token"); - send_msg(__c, Auth_tfa_setup_totp(std::move(token))); + co_await send_msg(__c, __req, Auth_tfa_setup_totp(std::move(token))); } else if (__method == "tfa_complete") { std::string token; token << json_get(__data, "token"); std::string otp; otp << json_get(__data, "otp"); - send_msg(__c, Auth_tfa_complete(std::move(token), std::move(otp))); + co_await send_msg(__c, __req, Auth_tfa_complete(std::move(token), std::move(otp))); } else if (__method == "tfa_disable") { std::string token; token << json_get(__data, "token"); - send_msg(__c, Auth_tfa_disable(std::move(token))); + co_await send_msg(__c, __req, Auth_tfa_disable(std::move(token))); } else if (__method == "delete_user") { std::string token; token << json_get(__data, "token"); - send_msg(__c, Auth_delete_user(std::move(token))); + co_await send_msg(__c, __req, Auth_delete_user(std::move(token))); } else if (__method == "session_info") { std::string token; token << json_get(__data, "token"); - send_msg(__c, Auth_session_info(std::move(token))); + co_await send_msg(__c, __req, Auth_session_info(std::move(token))); } else { throw std::exception{}; } } else if (__service == "Admin") { @@ -506,55 +509,55 @@ void mrpc::MRPCServer::msg_handler(const std::shared_ptr __c, std::string token; token << json_get(__data, "token"); - send_msg(__c, Admin_list_users(std::move(token))); + co_await send_msg(__c, __req, Admin_list_users(std::move(token))); } else if (__method == "delete_user") { std::string token; token << json_get(__data, "token"); std::uint64_t user; user << json_get(__data, "user"); - send_msg(__c, Admin_delete_user(std::move(token), std::move(user))); + co_await send_msg(__c, __req, Admin_delete_user(std::move(token), std::move(user))); } else if (__method == "logout") { std::string token; token << json_get(__data, "token"); std::uint64_t user; user << json_get(__data, "user"); - send_msg(__c, Admin_logout(std::move(token), std::move(user))); + co_await send_msg(__c, __req, Admin_logout(std::move(token), std::move(user))); } else if (__method == "disable_tfa") { std::string token; token << json_get(__data, "token"); std::uint64_t user; user << json_get(__data, "user"); - send_msg(__c, Admin_disable_tfa(std::move(token), std::move(user))); + co_await send_msg(__c, __req, Admin_disable_tfa(std::move(token), std::move(user))); } else if (__method == "set_admin") { std::string token; token << json_get(__data, "token"); std::uint64_t user; user << json_get(__data, "user"); bool admin; admin << json_get(__data, "admin"); - send_msg(__c, Admin_set_admin(std::move(token), std::move(user), std::move(admin))); + co_await send_msg(__c, __req, Admin_set_admin(std::move(token), std::move(user), std::move(admin))); } else if (__method == "set_enabled") { std::string token; token << json_get(__data, "token"); std::uint64_t user; user << json_get(__data, "user"); bool enabled; enabled << json_get(__data, "enabled"); - send_msg(__c, Admin_set_enabled(std::move(token), std::move(user), std::move(enabled))); + co_await send_msg(__c, __req, Admin_set_enabled(std::move(token), std::move(user), std::move(enabled))); } else if (__method == "sudo") { std::string token; token << json_get(__data, "token"); std::uint64_t user; user << json_get(__data, "user"); - send_msg(__c, Admin_sudo(std::move(token), std::move(user))); + co_await send_msg(__c, __req, Admin_sudo(std::move(token), std::move(user))); } else if (__method == "unsudo") { std::string token; token << json_get(__data, "token"); - send_msg(__c, Admin_unsudo(std::move(token))); + co_await send_msg(__c, __req, Admin_unsudo(std::move(token))); } else if (__method == "shutdown") { std::string token; token << json_get(__data, "token"); - send_msg(__c, Admin_shutdown(std::move(token))); + co_await send_msg(__c, __req, Admin_shutdown(std::move(token))); } else { throw std::exception{}; } } else if (__service == "FS") { @@ -563,19 +566,19 @@ void mrpc::MRPCServer::msg_handler(const std::shared_ptr __c, std::string token; token << json_get(__data, "token"); std::uint64_t node; node << json_get(__data, "node"); - send_msg(__c, FS_get_node(std::move(token), std::move(node))); + co_await send_msg(__c, __req, FS_get_node(std::move(token), std::move(node))); } else if (__method == "get_path") { std::string token; token << json_get(__data, "token"); std::uint64_t node; node << json_get(__data, "node"); - send_msg(__c, FS_get_path(std::move(token), std::move(node))); + co_await send_msg(__c, __req, FS_get_path(std::move(token), std::move(node))); } else if (__method == "get_nodes_size") { std::string token; token << json_get(__data, "token"); std::vector nodes; nodes << json_get(__data, "nodes"); - send_msg(__c, FS_get_nodes_size(std::move(token), std::move(nodes))); + co_await send_msg(__c, __req, FS_get_nodes_size(std::move(token), std::move(nodes))); } else if (__method == "create_node") { std::string token; token << json_get(__data, "token"); @@ -583,33 +586,33 @@ void mrpc::MRPCServer::msg_handler(const std::shared_ptr __c, std::uint64_t parent; parent << json_get(__data, "parent"); std::string name; name << json_get(__data, "name"); - send_msg(__c, FS_create_node(std::move(token), std::move(file), std::move(parent), std::move(name))); + co_await send_msg(__c, __req, FS_create_node(std::move(token), std::move(file), std::move(parent), std::move(name))); } else if (__method == "move_nodes") { std::string token; token << json_get(__data, "token"); std::vector nodes; nodes << json_get(__data, "nodes"); std::uint64_t parent; parent << json_get(__data, "parent"); - send_msg(__c, FS_move_nodes(std::move(token), std::move(nodes), std::move(parent))); + co_await send_msg(__c, __req, FS_move_nodes(std::move(token), std::move(nodes), std::move(parent))); } else if (__method == "delete_nodes") { - auto __stream = MRPCStream{__c}; + MRPCStream __stream{&__c}; std::string token; token << json_get(__data, "token"); std::vector nodes; nodes << json_get(__data, "nodes"); - FS_delete_nodes(std::move(token), std::move(nodes), std::move(__stream)); + FS_delete_nodes(std::move(token), std::move(nodes), __stream); } else if (__method == "download_preview") { std::string token; token << json_get(__data, "token"); std::uint64_t node; node << json_get(__data, "node"); - send_msg(__c, FS_download_preview(std::move(token), std::move(node))); + co_await send_msg(__c, __req, FS_download_preview(std::move(token), std::move(node))); } else if (__method == "get_mime") { std::string token; token << json_get(__data, "token"); std::uint64_t node; node << json_get(__data, "node"); - send_msg(__c, FS_get_mime(std::move(token), std::move(node))); + co_await send_msg(__c, __req, FS_get_mime(std::move(token), std::move(node))); } else { throw std::exception{}; } } diff --git a/src/server/mrpc/fileserver.hxx b/src/server/mrpc/fileserver.hxx index 52d081c..0c73917 100644 --- a/src/server/mrpc/fileserver.hxx +++ b/src/server/mrpc/fileserver.hxx @@ -8,16 +8,11 @@ #include #include #include -#include #define RAPIDJSON_HAS_STDSTRING 1 #include #include #include - -namespace restbed { - class Resource; - class Session; -} +#include "../../util/boost.hxx" namespace mrpc { using MRPCJWriter = rapidjson::Writer; @@ -103,25 +98,20 @@ struct PathSegment { PathSegment& operator <<(const rapidjson::Value&); }; -struct MRPCStreamImpl { - void close() const noexcept; - bool is_open() const noexcept; -protected: - explicit MRPCStreamImpl(const std::shared_ptr &conn); - std::shared_ptr conn; -}; - template -struct MRPCStream final : MRPCStreamImpl { - explicit MRPCStream(const std::shared_ptr &conn) : MRPCStreamImpl(conn) {} - void send(const T &v) const noexcept; +struct MRPCStream final { + explicit MRPCStream(tcp_stream *conn); + void send(const T &v) noexcept; + void close() noexcept; +private: + std::shared_ptr> chan; }; template struct MRPCStream; struct MRPCServer { - MRPCServer() = delete; - explicit MRPCServer(std::shared_ptr&); + net::awaitable msg_handler(tcp_stream&, const http::request&); + private: virtual std::optional Auth_signup(std::string &&username, std::string &&password) = 0; virtual Response Auth_login(std::string &&username, std::string &&password, std::optional &&otp) = 0; @@ -150,11 +140,9 @@ private: virtual Response FS_get_nodes_size(std::string &&token, std::vector &&nodes) = 0; virtual Response FS_create_node(std::string &&token, bool &&file, std::uint64_t &&parent, std::string &&name) = 0; virtual std::optional FS_move_nodes(std::string &&token, std::vector &&nodes, std::uint64_t &&parent) = 0; - virtual void FS_delete_nodes(std::string &&token, std::vector &&nodes, MRPCStream&&) = 0; + virtual void FS_delete_nodes(std::string &&token, std::vector &&nodes, MRPCStream&) = 0; virtual Response FS_download_preview(std::string &&token, std::uint64_t &&node) = 0; virtual Response FS_get_mime(std::string &&token, std::uint64_t &&node) = 0; - - virtual void msg_handler(std::shared_ptr, const restbed::Bytes&) final; }; } diff --git a/src/server/server.cxx b/src/server/server.cxx index 33864c7..61d37c3 100644 --- a/src/server/server.cxx +++ b/src/server/server.cxx @@ -44,7 +44,16 @@ void Server::logout_user(std::uint64_t id) { void Server::delete_user(const std::shared_ptr &user) { std::unique_lock lock{user_lock}; logout_user(user->id); - delete_node(user, 0, [](const std::string&){}); + for (const auto &[node_id, node] : user->nodes) { + node->parent.reset(); + node->children.clear(); + if (node->file) { + auto path = user->user_dir / std::to_string(node->id); + std::filesystem::remove(path); + if (node->preview) + std::filesystem::remove(path.replace_extension("png")); + } + } users.erase(user->id); } diff --git a/src/server/server.hxx b/src/server/server.hxx index 79ac4d9..29eee68 100644 --- a/src/server/server.hxx +++ b/src/server/server.hxx @@ -1,20 +1,18 @@ #ifndef FILESERVER_SERVER_HXX #define FILESERVER_SERVER_HXX -#include #include "mrpc/fileserver.hxx" #include "../data/data.hxx" -extern std::shared_ptr g_service; +extern std::function g_stop_service; struct Server final : public mrpc::MRPCServer, public Data { - explicit Server(std::shared_ptr &ptr) : MRPCServer(ptr), Data() {} + explicit Server() : MRPCServer(), Data() {} std::shared_ptr get_token(const std::string&); std::shared_ptr is_token_valid(const std::string&); std::shared_ptr get_user(std::uint64_t id); - static void delete_node(const std::shared_ptr &user, std::uint64_t id, const std::function& log); void logout_user(std::uint64_t id); void delete_user(const std::shared_ptr &user); void send_tfa_mail(const std::shared_ptr &user); @@ -23,10 +21,9 @@ struct Server final : public mrpc::MRPCServer, public Data { void send_mail(const std::string& email, const std::string& title, const std::string& body); std::uint64_t nodes_size(const std::shared_ptr &user, const std::vector &ids); - void download(const std::shared_ptr&); - void download_multi(const std::shared_ptr&); - void upload(const std::shared_ptr&); - + net::awaitable download(tcp_stream&, const http::request&); + net::awaitable download_multi(tcp_stream&, const http::request&); + net::awaitable upload(tcp_stream&, tcp_buffer&, http::request_parser&); private: std::optional Auth_signup(std::string &&username, std::string &&password) override; @@ -58,7 +55,7 @@ private: mrpc::Response FS_get_nodes_size(std::string &&token, std::vector &&nodes) override; mrpc::Response FS_create_node(std::string &&token, bool &&file, std::uint64_t &&parent, std::string &&name) override; std::optional FS_move_nodes(std::string &&token, std::vector &&nodes, std::uint64_t &&parent) override; - void FS_delete_nodes(std::string &&token, std::vector &&nodes, mrpc::MRPCStream &&stream) override; + void FS_delete_nodes(std::string &&token, std::vector &&nodes, mrpc::MRPCStream &stream) override; mrpc::Response FS_download_preview(std::string &&token, std::uint64_t &&node) override; mrpc::Response FS_get_mime(std::string &&token, std::uint64_t &&node) override; }; diff --git a/src/server/server_internal.hxx b/src/server/server_internal.hxx index f07b965..b0dfcf7 100644 --- a/src/server/server_internal.hxx +++ b/src/server/server_internal.hxx @@ -1,7 +1,6 @@ #ifndef FILESERVER_SERVER_INTERNAL_HXX #define FILESERVER_SERVER_INTERNAL_HXX - -#include +#include "../util/botan.hxx" #include "server.hxx" // TODO log user action with __FUNC__ diff --git a/src/server/upload.cxx b/src/server/upload.cxx index 57c6918..0931e23 100644 --- a/src/server/upload.cxx +++ b/src/server/upload.cxx @@ -1,7 +1,4 @@ #include -#include -#include -#include #include #include #include @@ -20,10 +17,10 @@ struct UploadInfo { std::shared_ptr node; }; -void make_preview(const std::shared_ptr& info) { +void make_preview(std::filesystem::path &path, const std::shared_ptr &node) { int x, y, channels; auto img = std::unique_ptr - {stbi_load(info->path.c_str(), &x, &y, &channels, 0), &free}; + {stbi_load(path.c_str(), &x, &y, &channels, 0), &free}; if (!img) return; @@ -44,70 +41,66 @@ void make_preview(const std::shared_ptr& info) { if (!rimg) return; - auto png_path = info->path.replace_extension("png"); + auto png_path = path.replace_extension("png"); if (!stbi_write_png(png_path.c_str(), new_x, new_y, channels, rimg.get(), 0)) return; - info->node->preview = true; + node->preview = true; } -void fetch_handler(const std::shared_ptr &s, const restbed::Bytes &bytes) { - std::shared_ptr info = s->get("upload"); +net::awaitable Server::upload(tcp_stream &s, tcp_buffer &buf, http::request_parser &req) { + auto &body = req.get(); - std::size_t read = bytes.size(); - info->to_read -= std::min(read, info->to_read); - info->file.write((char*)bytes.data(), bytes.size()); - if (info->to_read > 0) - return s->fetch(std::min(info->to_read, chunk_size), fetch_handler); - info->file.close(); - s->close(200); + if (body["X-Node"].empty()) { co_await send_error(s, body, "Missing node"); co_return; } + if (body["X-Token"].empty()) { co_await send_error(s, body, "Missing token"); co_return; } - std::size_t real_size = std::filesystem::file_size(info->path); - info->node->size = real_size; - auto ext = std::filesystem::path{info->node->name}.extension().string(); - if (real_size < max_image_size && image_extension.contains(ext)) - make_preview(info); - - info->node_lock.unlock(); - info->server->save(); -} - -void Server::upload(const std::shared_ptr &s) { - const auto req = s->get_request(); - if (!req->has_header("X-Node")) - return s->close(400, "Missing node"); - if (!req->has_header("X-Token")) - return s->close(400, "Missing token"); - - if (req->get_header("Transfer-Encoding") == "chunked") { - spdlog::error("Encountered a chunked upload!"); - return s->close(500, "Sorry but your browser is not supported yet"); + if (body[http::field::transfer_encoding] == "chunked") { + spdlog::critical("Encountered a chunked upload!"); + co_await send_error(s, body, "Sorry but your browser is not supported yet"); co_return; } - std::uint64_t node_id = req->get_header("X-Node", 0); - std::string token = req->get_header("X-Token"); + auto node_str = body["X-Node"]; + std::uint64_t node_id = 0; + std::from_chars(node_str.data(), node_str.data() + node_str.size(), node_id); + std::string token = body["X-Token"]; - check_user() return s->close(400, "Invalid user"); - { - std::shared_lock lock{user->node_lock}; - auto node = get_node(user, node_id); - if (!node) return s->close(400, "Invalid node"); - if (!node->file) return s->close(400, "Can't upload to a directory"); - auto to_read = req->get_header("Content-Length", 0); - auto path = user->user_dir / std::to_string(node->id); - if (node->preview) { - node->preview = false; - std::filesystem::remove(path.replace_extension("png")); - } - std::shared_ptr info{new UploadInfo{ - .server = this, - .node_lock = std::shared_lock{user->node_lock}, - .to_read = to_read, - .path = path, - .file = std::ofstream{path, std::ios_base::out|std::ios_base::trunc|std::ios_base::binary}, - .node = node - }}; - s->set("upload", info); - s->fetch(std::min(to_read, chunk_size), fetch_handler); + check_user() { co_await send_error(s, body, "Invalid user"); co_return; } + + auto node = get_node(user, node_id); + if (!node) { co_await send_error(s, body, "Invalid node"); co_return; } + if (!node->file) { co_await send_error(s, body, "Can't upload to a directory"); co_return; } + + auto path = user->user_dir / std::to_string(node->id); + if (node->preview) { + node->preview = false; + std::filesystem::remove(path.replace_extension("png")); } + + std::exception_ptr ex_ptr; + beast::error_code ec; + body.body().open(path.c_str(), beast::file_mode::write, ec); + try { + co_await http::async_read(s, buf, req); + } catch (beast::system_error &se) { + ex_ptr = make_exception_ptr(se); + } + body.body().close(); + + if (ex_ptr) + std::filesystem::resize_file(path, 0); + + std::size_t real_size = std::filesystem::file_size(path); + node->size = real_size; + auto ext = std::filesystem::path{node->name}.extension().string(); + if (real_size > 0 && real_size < max_image_size && image_extension.contains(ext)) + make_preview(path, node); + + save(); + + auto res = create_response(body); + res.keep_alive(false); + co_await http::async_write(s, res, net::use_awaitable); + + if (ex_ptr) + rethrow_exception(ex_ptr); } diff --git a/src/util/boost.hxx b/src/util/boost.hxx new file mode 100644 index 0000000..1ebae1d --- /dev/null +++ b/src/util/boost.hxx @@ -0,0 +1,66 @@ +#ifndef FILESERVER_BOOST_HXX +#define FILESERVER_BOOST_HXX + +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; +namespace http = beast::http; +namespace net = boost::asio; + +using tcp_stream = typename beast::tcp_stream::rebind_executor::executor_with_default>::other; +using tcp_buffer = beast::flat_static_buffer<65535>; + +template +http::response create_response(const http::request &req) { + http::response res{Status, req.version()}; + res.keep_alive(req.keep_alive()); + return std::move(res); +} + +template +net::awaitable send_error(tcp_stream &s, const http::request &req, std::string &&body) { + auto res = create_response(req); + res.body() = body; + co_await http::async_write(s, res, net::use_awaitable); +} + +struct err_handler { + std::source_location loc; + + explicit err_handler(std::source_location loc = std::source_location::current()) : loc(loc) {} + + void operator()(const std::exception_ptr& e) const { + if (!e) return; + try { + std::rethrow_exception(e); + } catch (beast::system_error &se) { + log(se.what()); + } catch (std::exception &e1) { + log(e1.what()); + //std::cerr << "Error in " << loc.file_name() << ":" << loc.line() << " `" << loc.function_name() << "`\n" << e1.what() << "\n"; + } + } + +private: + static std::shared_ptr logger; + + template + void log(MsgType msg) const { + if (!logger) + logger = spdlog::default_logger()->clone("err_handler"); + spdlog::source_loc spd_loc; + spd_loc.filename = loc.file_name(); + spd_loc.line = (int)loc.line(); + spd_loc.funcname = loc.function_name(); + spdlog::log(spd_loc, spdlog::level::err, msg); + } +}; + + +#endif //FILESERVER_BOOST_HXX diff --git a/src/util/botan.hxx b/src/util/botan.hxx new file mode 100644 index 0000000..f4672e7 --- /dev/null +++ b/src/util/botan.hxx @@ -0,0 +1,8 @@ +#ifndef FILESERVER_BOTAN_HXX +#define FILESERVER_BOTAN_HXX + +#include +#undef BLOCK_SIZE +#include + +#endif //FILESERVER_BOTAN_HXX diff --git a/src/util/logging.hxx b/src/util/logging.hxx deleted file mode 100644 index cc11a5f..0000000 --- a/src/util/logging.hxx +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef FILESERVER_LOGGING_HXX -#define FILESERVER_LOGGING_HXX - -#include -#include -#include - -namespace { -} - -namespace logging { - struct RestbedLogger : public restbed::Logger { - void stop() override {} - void start(const std::shared_ptr&) override { - logger = spdlog::default_logger()->clone("restbed"); - } - void log(Level level, const char *format, ...) override { - std::va_list args; - va_start(args, format); - restbed_log(level, format, args); - va_end(args); - } - void log_if(bool expression, Level level, const char *format, ...) override { - if (expression) { - va_list args; - va_start(args, format); - restbed_log(level, format, args); - va_end(args); - } - } - - private: - std::shared_ptr logger; - void restbed_log(const restbed::Logger::Level restbed_level, const char* format, va_list args) { - spdlog::level::level_enum level; - switch (restbed_level) { - case restbed::Logger::DEBUG: level = spdlog::level::level_enum::debug; break; - case restbed::Logger::INFO: level = spdlog::level::level_enum::info; break; - case restbed::Logger::WARNING: level = spdlog::level::level_enum::warn; break; - case restbed::Logger::ERROR: level = spdlog::level::level_enum::err; break; - case restbed::Logger::SECURITY: - case restbed::Logger::FATAL: level = spdlog::level::level_enum::critical; break; - } - std::string buf; - buf.resize(1024); - int written = vsnprintf(buf.data(), 1024, format, args); - //if (std::string_view{buf.cbegin(), buf.cbegin()+10} == "Incoming '") - // return; - if (written >= 1024) { - buf.resize(written + 10); - written = vsnprintf(buf.data(), written + 10, format, args); - } - buf.resize(written); - logger->log(level, buf); - } - }; -} - - -#endif //FILESERVER_LOGGING_HXX diff --git a/src/util/miniz.cxx b/src/util/miniz.cxx new file mode 100644 index 0000000..56df76d --- /dev/null +++ b/src/util/miniz.cxx @@ -0,0 +1,476 @@ +#include +#include +#include +#include +#include "miniz.hxx" + +namespace asio = boost::asio; + +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + +#ifdef _MSC_VER +#define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__)) +#else +#define MZ_FORCEINLINE inline +#endif + +enum +{ + /* ZIP archive identifiers and record sizes */ + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + /* Central directory header record offsets */ + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + + /* Local directory header offsets */ + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, + + /* End of central directory offsets */ + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + + +static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) { + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) { + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) { + mz_write_le32(p, (mz_uint32)v); + mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); +} + + +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) +#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) + +#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) +static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, const mz_uint64 *pUncomp_size, const mz_uint64 *pComp_size, const mz_uint64 *pLocal_header_ofs) { + mz_uint8 *pDst = pBuf; + mz_uint32 field_size = 0; + + pDst[0] = MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID; + memset(pDst+1, 0, 3); + + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + MZ_WRITE_LE64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pComp_size) + { + MZ_WRITE_LE64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + MZ_WRITE_LE64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + MZ_WRITE_LE16(pBuf + 2, field_size); + + return (mz_uint32)(pDst - pBuf); +} + +typedef unsigned long mz_ulong; +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { + static const mz_uint32 s_crc_table[256] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, + 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, + 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, + 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, + 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, + 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, + 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, + 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, + 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, + 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, + 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, + 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, + 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, + 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, + 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, + 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, + 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, + 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, + 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, + 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; + const auto* pByte_buf = (const mz_uint8 *)ptr; + + while (buf_len >= 4) { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; + pByte_buf += 4; + buf_len -= 4; + } + + while (buf_len) { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + ++pByte_buf; + --buf_len; + } + + return ~crc32; +} + +static void +mz_zip_writer_create_local_dir_header(mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 bit_flags) { + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + //MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + //MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, 0); + //MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, 0); + //MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, 0); + //MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, 0); + //MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, 0); + //MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); +} + +static void mz_zip_writer_create_central_dir_header(mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 bit_flags, mz_uint64 local_header_ofs, + mz_uint32 ext_attributes) { + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + //MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + //MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, 0); + //MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, 0); + //MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, 0); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + //MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, 0); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); +} + +void ZipArchive::add_to_central_dir(const char *pFilename, mz_uint16 filename_size, + const char *pExtra, mz_uint16 extra_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 bit_flags, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { + auto central_dir_ofs = (mz_uint32)m_central_dir.size(); + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + mz_zip_writer_create_central_dir_header(central_dir_header, filename_size, extra_size, uncomp_size, comp_size, + uncomp_crc32, bit_flags, local_header_ofs, + ext_attributes); + + m_central_dir.insert(m_central_dir.end(), central_dir_header, central_dir_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + m_central_dir.insert(m_central_dir.end(), pFilename, pFilename+filename_size); + if (extra_size) + m_central_dir.insert(m_central_dir.end(), pExtra, pExtra + extra_size); + //m_pState.m_central_dir.write((const char*) nullptr, 0); + //m_pState.m_central_dir.write((const char*) nullptr, 0); + m_central_dir_offsets.push_back(central_dir_ofs); +} + + +#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) +asio::awaitable ZipArchive::add_dir(const std::string &path) { + const char * const pArchive_name = path.c_str(); + + const mz_uint64 uncomp_size = 0; + const mz_uint32 uncomp_crc32 = 0; + + mz_uint ext_attributes = MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; + mz_uint64 local_dir_header_ofs = m_archive_size, comp_size = 0; + const size_t archive_name_size = path.size(); + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_uint16 bit_flags = MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + if (local_dir_header_ofs >= MZ_UINT32_MAX) { + extra_size = mz_zip_writer_create_zip64_extra_data( + extra_data, + nullptr, + nullptr, + (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : nullptr + ); + } + + mz_zip_writer_create_local_dir_header(local_dir_header, (mz_uint16) archive_name_size, + (mz_uint16) extra_size, + bit_flags); + + co_await write(local_dir_header, sizeof(local_dir_header)); + m_archive_size += sizeof(local_dir_header); + + co_await write(pArchive_name, archive_name_size); + m_archive_size += archive_name_size; + + if (extra_size) { + co_await write(extra_data, extra_size); + m_archive_size += extra_size; + } + + add_to_central_dir(pArchive_name, (mz_uint16) archive_name_size, (char*)extra_data, + (mz_uint16) extra_size, uncomp_size, comp_size, uncomp_crc32, bit_flags, + local_dir_header_ofs, ext_attributes); + + m_total_files++; +} + +asio::awaitable ZipArchive::add_file(const std::string &file_path, const std::string &archive_path) { + asio::stream_file file{co_await asio::this_coro::executor, file_path, asio::file_base::read_only}; + + mz_uint64 max_size = file.seek(0, asio::file_base::seek_end); + file.seek(0, asio::file_base::seek_set); + + const char * const pArchive_name = archive_path.c_str(); + + mz_uint16 gen_flags; + mz_uint uncomp_crc32 = 0; + mz_uint16 ext_attributes = 0; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = m_archive_size, uncomp_size = 0, comp_size = 0; + const size_t archive_name_size = archive_path.size(); + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_uint64 file_ofs = 0; + + gen_flags = MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + local_dir_header_ofs = cur_archive_file_ofs; + + + if (max_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, nullptr, nullptr, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : nullptr); + } + + mz_zip_writer_create_local_dir_header(local_dir_header, (mz_uint16) archive_name_size, (mz_uint16) extra_size, + gen_flags); + + co_await write(local_dir_header, sizeof(local_dir_header)); + cur_archive_file_ofs += sizeof(local_dir_header); + + co_await write(pArchive_name, archive_name_size); + cur_archive_file_ofs += archive_name_size; + + if (extra_size) { + co_await write(extra_data, extra_size); + cur_archive_file_ofs += extra_size; + } + + + if (max_size > 0) { + std::array read_buf{}; + boost::asio::mutable_buffer mut_buf{read_buf.data(), read_buf.size()}; // NOLINT(*-static-accessed-through-instance) + + try { + while (true) { + auto n = co_await file.async_read_some(mut_buf, asio::use_awaitable); + if (n == 0) + break; + + co_await write(read_buf.data(), n); + file_ofs += n; + uncomp_crc32 = (mz_uint32) mz_crc32(uncomp_crc32, (const mz_uint8 *) read_buf.data(), n); + cur_archive_file_ofs += n; + } + } catch (boost::system::system_error &e) { + if (e.code() != asio::stream_errc::eof) + throw; + } + + uncomp_size = file_ofs; + comp_size = uncomp_size; + } + + + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (extra_size == 0) { + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } else { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + co_await write(local_dir_footer, local_dir_footer_size); + cur_archive_file_ofs += local_dir_footer_size; + + if (extra_size) { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, + (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : nullptr, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : nullptr, + (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : nullptr); + } + + add_to_central_dir(pArchive_name, archive_name_size, (char*)extra_data, extra_size, + uncomp_size, comp_size, uncomp_crc32, gen_flags, + local_dir_header_ofs, ext_attributes); + + m_total_files++; + m_archive_size = cur_archive_file_ofs; +} + +boost::asio::awaitable ZipArchive::end() { + mz_uint64 central_dir_ofs = 0, central_dir_size = 0; + mz_uint8 hdr[256]; + + if (m_total_files > 0) { + /* Write central directory */ + central_dir_ofs = m_archive_size; + central_dir_size = m_central_dir.size(); + co_await write(m_central_dir.data(), central_dir_size); + m_archive_size += central_dir_size; + } + + mz_uint64 rel_ofs_to_zip64_ecdr = m_archive_size; + + memset(hdr, 0, sizeof(hdr)); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); + co_await write(hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE); + m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; + + /* Write zip64 end of central directory locator */ + memset(hdr, 0, sizeof(hdr)); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); + co_await write(hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE); + m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; + + /* Write end of central directory record */ + memset(hdr, 0, sizeof(hdr)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, std::min(MZ_UINT16_MAX, m_total_files)); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, std::min(MZ_UINT16_MAX, m_total_files)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, std::min(MZ_UINT32_MAX, central_dir_size)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, std::min(MZ_UINT32_MAX, central_dir_ofs)); + co_await write(hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE); + m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; +} diff --git a/src/util/miniz.hxx b/src/util/miniz.hxx new file mode 100644 index 0000000..a4b016e --- /dev/null +++ b/src/util/miniz.hxx @@ -0,0 +1,39 @@ +#ifndef TEMP_MINIZ_HXX +#define TEMP_MINIZ_HXX + +#include +#include +#include + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef uint64_t mz_uint64; +typedef bool mz_bool; + +struct ZipArchive { + boost::asio::awaitable add_dir(const std::string& path); + boost::asio::awaitable add_file(const std::string& file_path, const std::string& archive_path); + boost::asio::awaitable end(); + +protected: + virtual boost::asio::awaitable write(const void *, size_t n) = 0; + +private: + mz_uint64 m_archive_size = 0; + mz_uint32 m_total_files = 0; + + std::vector m_central_dir; + std::vector m_central_dir_offsets; + + void add_to_central_dir(const char *pFilename, mz_uint16 filename_size, + const char *pExtra, mz_uint16 extra_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 bit_flags, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes); +}; + + +#endif //TEMP_MINIZ_HXX diff --git a/src/util/timed_mutex.hxx b/src/util/timed_mutex.hxx deleted file mode 100644 index f72f866..0000000 --- a/src/util/timed_mutex.hxx +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef FILESERVER_TIMED_MUTEX_HXX -#define FILESERVER_TIMED_MUTEX_HXX - -#include -#include - -struct TimedSharedMutex { - std::shared_mutex m; - - using clock = std::chrono::high_resolution_clock; - - void lock_shared() { - auto start = clock::now(); - m.lock_shared(); - auto end = clock::now(); - auto d = std::chrono::duration_cast(end - start); - spdlog::info("Lock s took {} ms", d.count()); - } - void unlock_shared() { m.unlock_shared(); spdlog::info("Unlock s"); } - void lock() { - auto start = clock::now(); - m.lock(); - auto end = clock::now(); - auto d = std::chrono::duration_cast(end - start); - spdlog::info("Lock took {} ms", d.count()); - } - void unlock() { m.unlock(); spdlog::info("Unlock"); } -}; - -#endif //FILESERVER_TIMED_MUTEX_HXX