Replaced restbed with beast
Some checks failed
/ Build the server (push) Failing after 1m33s

This commit is contained in:
Mutzi 2024-04-23 15:38:41 +02:00
parent 4c954b1d98
commit 3b211d1af6
Signed by: root
GPG Key ID: 2437494E09F13876
25 changed files with 1104 additions and 1870 deletions

View File

@ -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
)

View File

@ -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;
}

View File

@ -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 <botan_all.h>
#include <asio.hpp>
#include <asio/yield.hpp>
#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 Handler, class Executor1, class Allocator>
class AsyncBase : public boost::asio::coroutine {
public:
using allocator_type = boost::asio::associated_allocator_t<Handler, Allocator>;
using executor_type = boost::asio::associated_executor_t<Handler, Executor1>;
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 <class HandlerT>
AsyncBase(HandlerT&& handler, const Executor1& executor) :
m_handler(std::forward<HandlerT>(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 <class... Args>
void complete_now(Args&&... args) {
m_work_guard_1.reset();
m_handler(std::forward<Args>(args)...);
}
Handler m_handler;
boost::asio::executor_work_guard<Executor1> m_work_guard_1;
};
template <class Handler, class Stream, class MutableBufferSequence, class Allocator = std::allocator<void>>
class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> {
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 <class HandlerT>
AsyncReadOperation(HandlerT&& handler,
Stream& stream,
const MutableBufferSequence& buffers,
const boost::system::error_code& ec = {}) :
AsyncBase<Handler, typename Stream::executor_type, Allocator>(std::forward<HandlerT>(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 <typename Handler, class Stream, class Allocator = std::allocator<void>>
class AsyncWriteOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> {
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 <class HandlerT>
AsyncWriteOperation(HandlerT&& handler,
Stream& stream,
std::size_t plainBytesTransferred,
const boost::system::error_code& ec = {}) :
AsyncBase<Handler, typename Stream::executor_type, Allocator>(std::forward<HandlerT>(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 Handler, class Stream, class Allocator = std::allocator<void>>
class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator> {
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 <class HandlerT>
AsyncHandshakeOperation(HandlerT&& handler, Stream& stream, const boost::system::error_code& ec = {}) :
AsyncBase<Handler, typename Stream::executor_type, Allocator>(std::forward<HandlerT>(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<AsyncHandshakeOperation<typename std::decay<Handler>::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 <asio/unyield.hpp>
#endif // BOTAN_ASIO_ASYNC_OPS_H_

View File

@ -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 <botan_all.h>
#include <functional>
namespace Botan::TLS {
namespace detail {
template <typename FunT>
struct fn_signature_helper : public std::false_type {};
template <typename R, typename D, typename... Args>
struct fn_signature_helper<R (D::*)(Args...)> {
using type = std::function<R(Args...)>;
};
} // 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<decltype(&Callbacks::tls_verify_cert_chain)>::type;
Context(std::shared_ptr<Credentials_Manager> credentials_manager,
std::shared_ptr<RandomNumberGenerator> rng,
std::shared_ptr<Session_Manager> session_manager,
std::shared_ptr<const Policy> 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<bool>(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 <class S, class C>
friend class Stream;
std::shared_ptr<Credentials_Manager> m_credentials_manager;
std::shared_ptr<RandomNumberGenerator> m_rng;
std::shared_ptr<Session_Manager> m_session_manager;
std::shared_ptr<const Policy> m_policy;
Server_Information m_server_info;
Verify_Callback m_verify_callback;
};
} // namespace Botan::TLS
#endif // BOTAN_ASIO_TLS_CONTEXT_H_

View File

@ -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 <botan_all.h>
#include <asio/error_code.hpp>
namespace boost{
namespace asio = ::asio;
namespace system {
template <typename T>
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<int>(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<Botan::TLS::Alert::Type>(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<int>(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<Botan::ErrorType>(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<int>(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<Botan::TLS::Alert::Type> {
static const bool value = true;
};
template <>
struct is_error_code_enum<Botan::TLS::StreamError> {
static const bool value = true;
};
template <>
struct is_error_code_enum<Botan::ErrorType> {
static const bool value = true;
};
} // namespace boost::system
#endif // BOTAN_ASIO_ERROR_H_

View File

@ -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 <botan_all.h>
#include <asio.hpp>
#include "asio_async_ops.h"
#include "asio_context.h"
#include "asio_error.h"
#include <algorithm>
#include <memory>
#include <type_traits>
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 StreamLayer, class ChannelT = Channel>
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 <typename... Args>
explicit Stream(std::shared_ptr<Context> context, Args&&... args) :
m_context(context),
m_nextLayer(std::forward<Args>(args)...),
m_core(std::make_shared<StreamCore>(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 <typename Arg>
explicit Stream(Arg&& arg, std::shared_ptr<Context> context) :
m_context(context),
m_nextLayer(std::forward<Arg>(arg)),
m_core(std::make_shared<StreamCore>(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<StreamLayer>::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<StreamLayer>;
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<ChannelT>::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 <typename verify_mode>
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 <typename verify_mode>
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 <typename CompletionToken>
auto async_handshake(Botan::TLS::Connection_Side side, CompletionToken&& completion_token) {
return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
[this](auto&& completion_handler, TLS::Connection_Side connection_side) {
using completion_handler_t = std::decay_t<decltype(completion_handler)>;
ASIO_HANDSHAKE_HANDLER_CHECK(completion_handler_t, completion_handler) type_check;
boost::system::error_code ec;
setup_native_handle(connection_side, ec);
detail::AsyncHandshakeOperation<completion_handler_t, Stream> op{
std::forward<completion_handler_t>(completion_handler), *this, ec};
},
completion_token,
side);
}
//! @throws Not_Implemented
template <typename ConstBufferSequence, typename BufferedHandshakeHandler>
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 <typename Handler, typename Executor>
struct Wrapper {
void operator()(boost::system::error_code ec, std::size_t) { handler(ec); }
using executor_type = boost::asio::associated_executor_t<Handler, Executor>;
executor_type get_executor() const noexcept {
return boost::asio::get_associated_executor(handler, io_executor);
}
using allocator_type = boost::asio::associated_allocator_t<Handler>;
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 <typename CompletionToken>
auto async_shutdown(CompletionToken&& completion_token) {
return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
[this](auto&& completion_handler) {
using completion_handler_t = std::decay_t<decltype(completion_handler)>;
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<completion_handler_t, typename Stream::executor_type>;
TLS::detail::AsyncWriteOperation<write_handler_t, Stream> op{
write_handler_t{std::forward<completion_handler_t>(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 <typename MutableBufferSequence>
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 <typename MutableBufferSequence>
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 <typename ConstBufferSequence>
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 <typename ConstBufferSequence>
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 <typename ConstBufferSequence, typename CompletionToken>
auto async_write_some(const ConstBufferSequence& buffers, CompletionToken&& completion_token) {
return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
[this](auto&& completion_handler, const auto& bufs) {
using completion_handler_t = std::decay_t<decltype(completion_handler)>;
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<completion_handler_t, Stream> op{
std::forward<completion_handler_t>(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 <typename MutableBufferSequence, typename CompletionToken>
auto async_read_some(const MutableBufferSequence& buffers, CompletionToken&& completion_token) {
return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
[this](auto&& completion_handler, const auto& bufs) {
using completion_handler_t = std::decay_t<decltype(completion_handler)>;
ASIO_READ_HANDLER_CHECK(completion_handler_t, completion_handler) type_check;
detail::AsyncReadOperation<completion_handler_t, Stream, MutableBufferSequence> op{
std::forward<completion_handler_t>(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 <class H, class S, class M, class A>
friend class detail::AsyncReadOperation;
template <class H, class S, class A>
friend class detail::AsyncWriteOperation;
template <class H, class S, class A>
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<Botan::TLS::Context> context) : shutdown_received(false), m_context(context) {}
~StreamCore() override = default;
void tls_emit_data(std::span<const uint8_t> 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<const uint8_t> 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<X509_Certificate>& cert_chain,
const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
const std::vector<Certificate_Store*>& 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<TLS::Context> 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 <typename MutableBufferSequence>
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<ChannelT, Channel>::value) {
try_with_error_code(
[&] {
if(side == Connection_Side::Client) {
m_native_handle = std::unique_ptr<Client>(
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<Server>(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 <typename ConstBufferSequence>
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<const uint8_t*>(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<const uint8_t*>(read_buffer.data()), read_buffer.size()});
},
ec);
}
//! @brief Catch exceptions and set an error_code
template <typename Fun>
void try_with_error_code(Fun f, boost::system::error_code& ec) {
f();
}
std::shared_ptr<Context> m_context;
StreamLayer m_nextLayer;
std::shared_ptr<StreamCore> m_core;
std::unique_ptr<ChannelT> m_native_handle;
// Buffer space used to read input intended for the core
std::vector<uint8_t> m_input_buffer_space;
const boost::asio::mutable_buffer m_input_buffer;
};
} // namespace Botan::TLS
#endif // BOTAN_ASIO_STREAM_H_

View File

@ -35,7 +35,8 @@ void Data::load_config() {
inipp::Ini<char> 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;

View File

@ -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 {

View File

@ -1,36 +1,122 @@
#include <memory>
#include <csignal>
#include <botan_all.h>
#include <corvusoft/restbed/request.hpp>
#include <corvusoft/restbed/resource.hpp>
#include <corvusoft/restbed/session.hpp>
#include <corvusoft/restbed/settings.hpp>
#include <corvusoft/restbed/service.hpp>
#include <spdlog/sinks/basic_file_sink.h>
#include "util/logging.hxx"
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/signal_set.hpp>
#include "server/server.hxx"
#include "util/botan.hxx"
#include "index_html.h"
#include "favicon_svg.h"
std::shared_ptr<restbed::Service> 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<void()> g_stop_service;
std::shared_ptr<spdlog::logger> err_handler::logger{};
void signal_shutdown(const int) {
spdlog::info("Received stop signal");
g_service->stop();
Server server{};
std::string index_etag{};
net::awaitable<void> 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<http::empty_body> 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<http::string_body> 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<http::status::not_modified, http::empty_body, http::empty_body>(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<http::status::ok, http::buffer_body, http::empty_body>(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<http::string_body> 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<http::file_body> 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<http::string_body> 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<http::status::ok, http::buffer_body, http::empty_body>(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<http::string_body> 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);
}
void error_handler(const int code, const std::exception& ex, const std::shared_ptr<restbed::Session> 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());
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);
}
net::awaitable<void> 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<restbed::Resource>();
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<restbed::Resource>(); \
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<restbed::Resource>();
index_resource->set_path("/");
index_resource->set_method_handler("GET", [&index_etag](const std::shared_ptr<restbed::Session>& s){
auto req = s->get_request();
if (req->get_header("If-None-Match", "") == index_etag) {
s->yield(
304,
"",
std::multimap<std::string, std::string>{
{"Cache-Control", "no-cache"},
{"ETag", index_etag}
}
);
} else {
s->yield(
200,
index_html_bytes,
std::multimap<std::string, std::string>{
{"Content-Type", "text/html"},
{"Content-Length", std::to_string(index_html_len)},
{"Cache-Control", "no-cache"},
{"ETag", index_etag}
}
);
}
});
std::vector<std::thread> 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<restbed::Resource>();
favicon_resource->set_path("/favicon.svg");
favicon_resource->set_method_handler("GET", [](const std::shared_ptr<restbed::Session>& s){
s->yield(
200,
favicon_bytes,
std::multimap<std::string, std::string>{
{"Content-Type", "image/svg+xml"},
{"Content-Length", std::to_string(favicon_svg_len)}
}
);
});
auto settings = std::make_shared<restbed::Settings>();
settings->set_port(server.config.server_port);
settings->set_default_header("Connection", "keep-alive");
g_service = std::make_shared<restbed::Service>();
g_service->set_error_handler(error_handler);
g_service->set_logger(std::make_shared<logging::RestbedLogger>());
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;
}

View File

@ -97,6 +97,6 @@ std::optional<std::string> Server::Admin_unsudo(std::string &&token) {
std::optional<std::string> 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;
}

View File

@ -4,19 +4,13 @@
#include <fstream>
#include <deque>
#include <charconv>
#include <miniz.h>
#include <corvusoft/restbed/session.hpp>
#include <corvusoft/restbed/request.hpp>
#include <corvusoft/restbed/response.hpp>
#include "../util/miniz.hxx"
#include "server_internal.hxx"
void Server::download(const std::shared_ptr<restbed::Session> &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<restbed::Session> &s, const restbed::Bytes &b){
std::string body{b.cbegin(), b.cend()};
if (body.empty())
return s->close(400, "empty body");
net::awaitable<void> Server::download(tcp_stream &s, const http::request<http::string_body> &req) {
auto body = req.body();
if (body.empty()) { co_await send_error(s, req, "Empty body"); co_return; }
std::string node_str, token;
for (const auto part : std::views::split(body, '&')) {
std::string_view part_view{part};
@ -27,70 +21,60 @@ void Server::download(const std::shared_ptr<restbed::Session> &s) {
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");
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 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");
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; }
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) { co_await send_error(s, req, "Invalid node"); co_return; }
beast::error_code ec;
auto mime = get_mime_type(node->name);
s->yield(
200,
"",
std::multimap<std::string, std::string>{
{"Content-Type", mime},
{"Content-Length", std::to_string(node->size)},
{"Content-Disposition", "attachment; filename=\"" + node->name + "\""}
},
[user=user, node=node](const std::shared_ptr<restbed::Session>& 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();
}
);
}
});
auto res = create_response<http::status::ok, http::file_body, http::string_body>(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);
}
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");
}
return n;
}
struct Zip : public ZipArchive {
tcp_stream *s;
http::response<http::buffer_body> *body;
http::response_serializer<http::buffer_body> *sr;
Zip(tcp_stream *s, http::response<http::buffer_body> *body, http::response_serializer<http::buffer_body> *sr)
: s(s), body(body), sr(sr) {}
protected:
net::awaitable<void> 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<void> Server::download_multi(tcp_stream &s, const http::request<http::string_body> &req) {
auto body = req.body();
if (body.empty()) { co_await send_error(s, req, "Empty body"); co_return; }
void Server::download_multi(const std::shared_ptr<restbed::Session> &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<restbed::Session> &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};
@ -101,84 +85,65 @@ void Server::download_multi(const std::shared_ptr<restbed::Session> &s) {
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");
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<std::uint64_t> 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}});
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() return s->close(400, "Invalid user");
{
check_user() { co_await send_error(s, req, "Invalid user"); co_return; }
std::shared_lock lock{user->node_lock};
std::vector<std::shared_ptr<Node>> 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));
if (!node) { co_await send_error(s, req, "Invalid node " + std::to_string(node_id)); co_return; }
nodes.push_back(node);
}
s->yield(
200,
"",
std::multimap<std::string, std::string>{
{"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};
auto res = create_response<http::status::ok, http::buffer_body, http::string_body>(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;
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
archive.m_pWrite = zip_write_func;
archive.m_pIO_opaque = s.get();
http::response_serializer<http::buffer_body> sr{res};
mz_zip_writer_init_v2(&archive, 0, MZ_ZIP_FLAG_WRITE_ZIP64);
co_await http::async_write_header(s, sr, net::use_awaitable);
Zip zip{&s, &res, &sr};
std::deque<std::pair<std::shared_ptr<Node>, 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::shared_ptr<Node>, 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);
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() + "/";
mz_zip_writer_add_mem(&archive, dir_path.c_str(), nullptr, 0, 0);
co_await zip.add_dir(dir_path);
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();
}
co_await zip.end();
mz_zip_writer_finalize_archive(&archive);
mz_zip_writer_end(&archive);
s->close("0\r\n\r\n");
}};
zip_thread.detach();
}
});
res.body().data = nullptr;
res.body().more = false;
co_await http::async_write(s, sr, net::use_awaitable);
}

View File

@ -34,43 +34,6 @@ std::string get_path(std::shared_ptr<Node> node) {
return ret.empty() ? "/" : ret;
}
void Server::delete_node(const std::shared_ptr<User> &user, std::uint64_t id, const std::function<void(std::string)>& log) {
std::unique_lock lock{user->node_lock};
std::stack<std::shared_ptr<Node>> 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> &user, const std::vector<std::uint64_t> &ids) {
std::uint64_t total = 0;
std::deque<Node*> todo;
@ -231,22 +194,53 @@ std::optional<std::string> Server::FS_move_nodes(std::string &&token, std::vecto
return std::nullopt;
}
void Server::FS_delete_nodes(std::string &&token, std::vector<std::uint64_t> &&nodes, mrpc::MRPCStream<std::string> &&stream) {
void Server::FS_delete_nodes(std::string &&token, std::vector<std::uint64_t> &&nodes, mrpc::MRPCStream<std::string> &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)
std::unique_lock lock{user->node_lock};
std::stack<std::shared_ptr<Node>> todo;
for (const auto &node_id: nodes) {
if (node_id == 0)
continue;
delete_node(user, node, [&stream](const std::string &log){ stream.send(log); });
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.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();
}};
deleter.detach();
}
mrpc::Response<std::string> Server::FS_download_preview(std::string &&token, std::uint64_t &&node_id) {

View File

@ -1,10 +1,10 @@
#include <asio.hpp>
#include <botan_asio/asio_stream.h>
#include <boost/asio/streambuf.hpp>
#include <spdlog/spdlog.h>
#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<typename Socket>
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<Botan::TLS::Context>(
std::make_shared<CredMan>(),
#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<Policy>()
);
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<asio::ip::tcp::socket> ss(std::move(s), ssl_ctx);
Botan::TLS::Stream<net::ip::tcp::socket> ss(std::move(s), ssl_ctx);
ss.handshake(Botan::TLS::Connection_Side::Client);
send(data, ss, "EHLO " + host_name);

View File

@ -1,8 +1,8 @@
#include "fileserver.hxx"
#include <corvusoft/restbed/session.hpp>
#include <corvusoft/restbed/resource.hpp>
#include <corvusoft/restbed/request.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
using namespace boost::asio::experimental::awaitable_operators;
using namespace mrpc;
@ -356,67 +356,68 @@ PathSegment& PathSegment::operator<<(const rapidjson::Value &__j) {
template<typename T>
void send_msg(const std::shared_ptr<restbed::Session> &c, const T &v) {
if (c->is_closed())
return;
net::awaitable<void> send_msg(tcp_stream &c, const http::request<http::string_body> &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<std::string, std::string>{
{"Content-Type", "application/json"},
{"Content-Length", std::to_string(body.size())}
}
);
auto res = create_response<http::status::ok, http::string_body, http::string_body>(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<typename T>
void send_sse_msg(const std::shared_ptr<restbed::Session> &c, const T &v) {
if (c->is_closed())
return;
net::awaitable<void> 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<restbed::Session> &conn) : conn(conn) {
conn->yield(
200,
std::multimap<std::string, std::string>{
{"Cache-Control", "no-cache"},
{"Content-Type", "text/event-stream"}
template<typename T>
net::awaitable<void> process_channel(tcp_stream *conn, std::shared_ptr<net::experimental::concurrent_channel<void(boost::system::error_code, T)>> chan) {
http::response<http::empty_body> 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<std::string>::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<std::string>::MRPCStream(tcp_stream *conn) {
chan = std::make_shared<net::experimental::concurrent_channel<void(boost::system::error_code, std::string)>>(conn->get_executor(), 32);
net::co_spawn(
conn->get_executor(),
process_channel<std::string>(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<std::string>::send(const std::string &v) const noexcept { send_sse_msg(conn, v); }
template<> void MRPCStream<std::string>::send(const std::string &v) noexcept { chan->try_send(beast::error_code{}, v); }
mrpc::MRPCServer::MRPCServer(std::shared_ptr<restbed::Resource> &r) {
r->set_method_handler("POST", [this](const std::shared_ptr<restbed::Session>& 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<restbed::Session> &s, auto &&body) {
try { msg_handler(s, body); }
catch (const std::exception &_) { s->close(400); }
});
});
}
void mrpc::MRPCServer::msg_handler(const std::shared_ptr<restbed::Session> __c, const restbed::Bytes &__msg) {
net::awaitable<void> mrpc::MRPCServer::msg_handler(tcp_stream &__c, const http::request<http::string_body> &__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;
@ -432,73 +433,75 @@ void mrpc::MRPCServer::msg_handler(const std::shared_ptr<restbed::Session> __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<std::string> 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<restbed::Session> __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<restbed::Session> __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<std::uint64_t> 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<restbed::Session> __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<std::uint64_t> 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<std::string>{__c};
MRPCStream<std::string> __stream{&__c};
std::string token; token << json_get(__data, "token");
std::vector<std::uint64_t> 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{}; }
}

View File

@ -8,16 +8,11 @@
#include <optional>
#include <cstdint>
#include <cmath>
#include <corvusoft/restbed/byte.hpp>
#define RAPIDJSON_HAS_STDSTRING 1
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <rapidjson/document.h>
namespace restbed {
class Resource;
class Session;
}
#include "../../util/boost.hxx"
namespace mrpc {
using MRPCJWriter = rapidjson::Writer<rapidjson::StringBuffer>;
@ -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<restbed::Session> &conn);
std::shared_ptr<restbed::Session> conn;
};
template<typename T>
struct MRPCStream final : MRPCStreamImpl {
explicit MRPCStream(const std::shared_ptr<restbed::Session> &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<net::experimental::concurrent_channel<void(boost::system::error_code, T)>> chan;
};
template struct MRPCStream<std::string>;
struct MRPCServer {
MRPCServer() = delete;
explicit MRPCServer(std::shared_ptr<restbed::Resource>&);
net::awaitable<void> msg_handler(tcp_stream&, const http::request<http::string_body>&);
private:
virtual std::optional<std::string> Auth_signup(std::string &&username, std::string &&password) = 0;
virtual Response<LoginResponse> Auth_login(std::string &&username, std::string &&password, std::optional<std::string> &&otp) = 0;
@ -150,11 +140,9 @@ private:
virtual Response<std::uint64_t> FS_get_nodes_size(std::string &&token, std::vector<std::uint64_t> &&nodes) = 0;
virtual Response<CreateNodeInfo> FS_create_node(std::string &&token, bool &&file, std::uint64_t &&parent, std::string &&name) = 0;
virtual std::optional<std::string> FS_move_nodes(std::string &&token, std::vector<std::uint64_t> &&nodes, std::uint64_t &&parent) = 0;
virtual void FS_delete_nodes(std::string &&token, std::vector<std::uint64_t> &&nodes, MRPCStream<std::string>&&) = 0;
virtual void FS_delete_nodes(std::string &&token, std::vector<std::uint64_t> &&nodes, MRPCStream<std::string>&) = 0;
virtual Response<std::string> FS_download_preview(std::string &&token, std::uint64_t &&node) = 0;
virtual Response<std::string> FS_get_mime(std::string &&token, std::uint64_t &&node) = 0;
virtual void msg_handler(std::shared_ptr<restbed::Session>, const restbed::Bytes&) final;
};
}

View File

@ -44,7 +44,16 @@ void Server::logout_user(std::uint64_t id) {
void Server::delete_user(const std::shared_ptr<User> &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);
}

View File

@ -1,20 +1,18 @@
#ifndef FILESERVER_SERVER_HXX
#define FILESERVER_SERVER_HXX
#include <corvusoft/restbed/service.hpp>
#include "mrpc/fileserver.hxx"
#include "../data/data.hxx"
extern std::shared_ptr<restbed::Service> g_service;
extern std::function<void()> g_stop_service;
struct Server final : public mrpc::MRPCServer, public Data {
explicit Server(std::shared_ptr<restbed::Resource> &ptr) : MRPCServer(ptr), Data() {}
explicit Server() : MRPCServer(), Data() {}
std::shared_ptr<Token> get_token(const std::string&);
std::shared_ptr<User> is_token_valid(const std::string&);
std::shared_ptr<User> get_user(std::uint64_t id);
static void delete_node(const std::shared_ptr<User> &user, std::uint64_t id, const std::function<void(std::string)>& log);
void logout_user(std::uint64_t id);
void delete_user(const std::shared_ptr<User> &user);
void send_tfa_mail(const std::shared_ptr<User> &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> &user, const std::vector<std::uint64_t> &ids);
void download(const std::shared_ptr<restbed::Session>&);
void download_multi(const std::shared_ptr<restbed::Session>&);
void upload(const std::shared_ptr<restbed::Session>&);
net::awaitable<void> download(tcp_stream&, const http::request<http::string_body>&);
net::awaitable<void> download_multi(tcp_stream&, const http::request<http::string_body>&);
net::awaitable<void> upload(tcp_stream&, tcp_buffer&, http::request_parser<http::file_body>&);
private:
std::optional<std::string> Auth_signup(std::string &&username, std::string &&password) override;
@ -58,7 +55,7 @@ private:
mrpc::Response<std::uint64_t> FS_get_nodes_size(std::string &&token, std::vector<std::uint64_t> &&nodes) override;
mrpc::Response<mrpc::CreateNodeInfo> FS_create_node(std::string &&token, bool &&file, std::uint64_t &&parent, std::string &&name) override;
std::optional<std::string> FS_move_nodes(std::string &&token, std::vector<std::uint64_t> &&nodes, std::uint64_t &&parent) override;
void FS_delete_nodes(std::string &&token, std::vector<std::uint64_t> &&nodes, mrpc::MRPCStream<std::string> &&stream) override;
void FS_delete_nodes(std::string &&token, std::vector<std::uint64_t> &&nodes, mrpc::MRPCStream<std::string> &stream) override;
mrpc::Response<std::string> FS_download_preview(std::string &&token, std::uint64_t &&node) override;
mrpc::Response<std::string> FS_get_mime(std::string &&token, std::uint64_t &&node) override;
};

View File

@ -1,7 +1,6 @@
#ifndef FILESERVER_SERVER_INTERNAL_HXX
#define FILESERVER_SERVER_INTERNAL_HXX
#include <botan_all.h>
#include "../util/botan.hxx"
#include "server.hxx"
// TODO log user action with __FUNC__

View File

@ -1,7 +1,4 @@
#include <fstream>
#include <corvusoft/restbed/session.hpp>
#include <corvusoft/restbed/request.hpp>
#include <corvusoft/restbed/response.hpp>
#include <spdlog/spdlog.h>
#include <stb_image.h>
#include <stb_image_resize2.h>
@ -20,10 +17,10 @@ struct UploadInfo {
std::shared_ptr<Node> node;
};
void make_preview(const std::shared_ptr<UploadInfo>& info) {
void make_preview(std::filesystem::path &path, const std::shared_ptr<Node> &node) {
int x, y, channels;
auto img = std::unique_ptr<stbi_uc, decltype(&free)>
{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<UploadInfo>& 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<restbed::Session> &s, const restbed::Bytes &bytes) {
std::shared_ptr<UploadInfo> info = s->get("upload");
net::awaitable<void> Server::upload(tcp_stream &s, tcp_buffer &buf, http::request_parser<http::file_body> &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();
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;
}
void Server::upload(const std::shared_ptr<restbed::Session> &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");
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"];
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");
}
check_user() { co_await send_error(s, body, "Invalid user"); co_return; }
std::uint64_t node_id = req->get_header("X-Node", 0);
std::string token = req->get_header("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<std::size_t>("Content-Length", 0);
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::shared_ptr<UploadInfo> 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);
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<http::status::ok, http::empty_body, http::file_body>(body);
res.keep_alive(false);
co_await http::async_write(s, res, net::use_awaitable);
if (ex_ptr)
rethrow_exception(ex_ptr);
}

66
src/util/boost.hxx Normal file
View File

@ -0,0 +1,66 @@
#ifndef FILESERVER_BOOST_HXX
#define FILESERVER_BOOST_HXX
#include <spdlog/spdlog.h>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/experimental/concurrent_channel.hpp>
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp_stream = typename beast::tcp_stream::rebind_executor<net::use_awaitable_t<>::executor_with_default<net::any_io_executor>>::other;
using tcp_buffer = beast::flat_static_buffer<65535>;
template<http::status Status, typename BodyType, typename ReqBody>
http::response<BodyType> create_response(const http::request<ReqBody> &req) {
http::response<BodyType> res{Status, req.version()};
res.keep_alive(req.keep_alive());
return std::move(res);
}
template<typename ReqBody>
net::awaitable<void> send_error(tcp_stream &s, const http::request<ReqBody> &req, std::string &&body) {
auto res = create_response<http::status::bad_request, http::string_body, ReqBody>(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<spdlog::logger> logger;
template<typename MsgType>
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

8
src/util/botan.hxx Normal file
View File

@ -0,0 +1,8 @@
#ifndef FILESERVER_BOTAN_HXX
#define FILESERVER_BOTAN_HXX
#include <liburing.h>
#undef BLOCK_SIZE
#include <botan_all.h>
#endif //FILESERVER_BOTAN_HXX

View File

@ -1,60 +0,0 @@
#ifndef FILESERVER_LOGGING_HXX
#define FILESERVER_LOGGING_HXX
#include <cstdarg>
#include <spdlog/spdlog.h>
#include <corvusoft/restbed/logger.hpp>
namespace {
}
namespace logging {
struct RestbedLogger : public restbed::Logger {
void stop() override {}
void start(const std::shared_ptr<const restbed::Settings>&) 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<spdlog::logger> 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

476
src/util/miniz.cxx Normal file
View File

@ -0,0 +1,476 @@
#include <cstring>
#include <boost/asio/stream_file.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/array.hpp>
#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<void> 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<void> 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<char, 65535> 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<void> 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>(MZ_UINT32_MAX, central_dir_size));
MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, std::min<mz_uint32>(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;
}

39
src/util/miniz.hxx Normal file
View File

@ -0,0 +1,39 @@
#ifndef TEMP_MINIZ_HXX
#define TEMP_MINIZ_HXX
#include <vector>
#include <string>
#include <boost/asio/awaitable.hpp>
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<void> add_dir(const std::string& path);
boost::asio::awaitable<void> add_file(const std::string& file_path, const std::string& archive_path);
boost::asio::awaitable<void> end();
protected:
virtual boost::asio::awaitable<void> write(const void *, size_t n) = 0;
private:
mz_uint64 m_archive_size = 0;
mz_uint32 m_total_files = 0;
std::vector<mz_uint8> m_central_dir;
std::vector<mz_uint32> 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

View File

@ -1,30 +0,0 @@
#ifndef FILESERVER_TIMED_MUTEX_HXX
#define FILESERVER_TIMED_MUTEX_HXX
#include <spdlog/spdlog.h>
#include <shared_mutex>
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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(end - start);
spdlog::info("Lock took {} ms", d.count());
}
void unlock() { m.unlock(); spdlog::info("Unlock"); }
};
#endif //FILESERVER_TIMED_MUTEX_HXX