This commit is contained in:
parent
4c954b1d98
commit
3b211d1af6
@ -5,31 +5,43 @@ set(CMAKE_CXX_STANDARD 23)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||||
|
|
||||||
include(CPM.cmake)
|
include(CPM.cmake)
|
||||||
CPMAddPackage("gh:richgel999/miniz#3.0.2")
|
|
||||||
CPMAddPackage("gh:gabime/spdlog#v1.13.0")
|
CPMAddPackage("gh:gabime/spdlog#v1.13.0")
|
||||||
CPMAddPackage(
|
|
||||||
NAME restbed
|
set(BOOST_LIBS
|
||||||
VERSION 4.8
|
beast asio assert bind config container core endian
|
||||||
GITHUB_REPOSITORY Corvusoft/restbed
|
intrusive logic mp11 optional smart_ptr static_assert static_string
|
||||||
GIT_TAG 4.8
|
system throw_exception type_traits utility winapi
|
||||||
DOWNLOAD_ONLY YES
|
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(
|
CPMAddPackage(
|
||||||
NAME asio
|
NAME uring
|
||||||
VERSION 1.29.0
|
VERSION 2.5
|
||||||
GITHUB_REPOSITORY chriskohlhoff/asio
|
GITHUB_REPOSITORY axboe/liburing
|
||||||
GIT_TAG asio-1-29-0
|
GIT_TAG liburing-2.5
|
||||||
DOWNLOAD_ONLY YES
|
DOWNLOAD_ONLY YES
|
||||||
)
|
)
|
||||||
|
|
||||||
if(asio_ADDED AND restbed_ADDED)
|
if(uring_ADDED)
|
||||||
file(GLOB_RECURSE restbed_SOURCE "${restbed_SOURCE_DIR}/source/corvusoft/restbed/*.cpp")
|
add_library(uring STATIC
|
||||||
add_library(restbed-static STATIC ${restbed_SOURCE} "${asio_SOURCE_DIR}/asio/src/asio.cpp")
|
${uring_SOURCE_DIR}/src/setup.c
|
||||||
target_compile_definitions(restbed-static PUBLIC ASIO_SEPARATE_COMPILATION)
|
${uring_SOURCE_DIR}/src/queue.c
|
||||||
target_include_directories(restbed-static PUBLIC "${restbed_SOURCE_DIR}/source" "${asio_SOURCE_DIR}/asio/include")
|
${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()
|
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(
|
CPMAddPackage(
|
||||||
NAME botan
|
NAME botan
|
||||||
VERSION 3.4.0
|
VERSION 3.4.0
|
||||||
@ -48,25 +60,21 @@ if(botan_ADDED)
|
|||||||
--amalgamation
|
--amalgamation
|
||||||
--minimized-build
|
--minimized-build
|
||||||
--without-documentation
|
--without-documentation
|
||||||
|
--with-boost
|
||||||
--enable-modules=${BOTAN_MODULES_STR}
|
--enable-modules=${BOTAN_MODULES_STR}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(botan STATIC ${CMAKE_CURRENT_BINARY_DIR}/botan_all.cpp ${CMAKE_CURRENT_BINARY_DIR}/botan_all.h)
|
add_library(botan STATIC ${CMAKE_CURRENT_BINARY_DIR}/botan_all.cpp ${CMAKE_CURRENT_BINARY_DIR}/botan_all.h)
|
||||||
endif()
|
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)
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
add_custom_command(
|
#add_custom_command(
|
||||||
COMMAND ./mrpc ARGS -n src/server/mrpc/fileserver -s cpp fileserver.rs
|
# COMMAND ./mrpc ARGS -n src/server/mrpc/fileserver -s cpp fileserver.rs
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
MAIN_DEPENDENCY fileserver.rs
|
# MAIN_DEPENDENCY fileserver.rs
|
||||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/server/mrpc/fileserver.cxx ${CMAKE_CURRENT_SOURCE_DIR}/src/server/mrpc/fileserver.hxx
|
# OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/server/mrpc/fileserver.cxx ${CMAKE_CURRENT_SOURCE_DIR}/src/server/mrpc/fileserver.hxx
|
||||||
)
|
#)
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
COMMAND sh ARGS -c 'xxd -i -n index_html ${CMAKE_CURRENT_SOURCE_DIR}/frontend/dist/index.html > index_html.h'
|
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.hxx
|
||||||
src/server/mrpc/fileserver.cxx
|
src/server/mrpc/fileserver.cxx
|
||||||
|
|
||||||
src/util/logging.hxx
|
|
||||||
src/util/crash.hxx
|
src/util/crash.hxx
|
||||||
src/util/timed_mutex.hxx
|
|
||||||
src/util/stb.cxx
|
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.hxx
|
||||||
src/data/data_internal.hxx
|
src/data/data_internal.hxx
|
||||||
@ -114,13 +124,13 @@ add_executable(fileserver
|
|||||||
|
|
||||||
target_include_directories(fileserver PRIVATE include ${CMAKE_CURRENT_BINARY_DIR})
|
target_include_directories(fileserver PRIVATE include ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
target_compile_options(fileserver PRIVATE -msse2)
|
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_options(fileserver PRIVATE -static)
|
||||||
target_link_libraries(fileserver PRIVATE
|
target_link_libraries(fileserver PRIVATE
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
restbed-static
|
Boost::beast
|
||||||
#Botan::Botan
|
|
||||||
botan
|
botan
|
||||||
miniz
|
uring
|
||||||
Threads::Threads
|
Threads::Threads
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -8,8 +8,7 @@
|
|||||||
let username = '', key = '', password = '', password2 = '';
|
let username = '', key = '', password = '', password2 = '';
|
||||||
|
|
||||||
async function sendKey() {
|
async function sendKey() {
|
||||||
if (await workingWrapper(() => rpc.Auth_send_recovery_key(username)) == null)
|
await workingWrapper(() => rpc.Auth_send_recovery_key(username));
|
||||||
return;
|
|
||||||
info_banner.set('A message has been sent');
|
info_banner.set('A message has been sent');
|
||||||
enter_key = true;
|
enter_key = true;
|
||||||
}
|
}
|
||||||
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -35,7 +35,8 @@ void Data::load_config() {
|
|||||||
inipp::Ini<char> ini;
|
inipp::Ini<char> ini;
|
||||||
if (!std::filesystem::exists(ini_name)) {
|
if (!std::filesystem::exists(ini_name)) {
|
||||||
ini.sections["web"] = {
|
ini.sections["web"] = {
|
||||||
{"port", "80"}
|
{"port", "80"},
|
||||||
|
{"threads", "6"}
|
||||||
};
|
};
|
||||||
ini.sections["smtp"] = {
|
ini.sections["smtp"] = {
|
||||||
{"host", "127.0.0.1"},
|
{"host", "127.0.0.1"},
|
||||||
@ -68,6 +69,7 @@ void Data::load_config() {
|
|||||||
std::string entry;
|
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]
|
#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, 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, host); config.smtp_host = entry;
|
||||||
get_entry(smtp, port); config.smtp_port = std::stoul(entry);
|
get_entry(smtp, port); config.smtp_port = std::stoul(entry);
|
||||||
get_entry(smtp, user); config.smtp_user = entry;
|
get_entry(smtp, user); config.smtp_user = entry;
|
||||||
|
@ -49,7 +49,7 @@ struct Token {
|
|||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
std::string smtp_host, smtp_user, smtp_pass, smtp_from, admin_mail;
|
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 {
|
struct Data {
|
||||||
|
211
src/main.cxx
211
src/main.cxx
@ -1,36 +1,122 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <csignal>
|
#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 <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 "server/server.hxx"
|
||||||
|
#include "util/botan.hxx"
|
||||||
#include "index_html.h"
|
#include "index_html.h"
|
||||||
#include "favicon_svg.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};
|
std::function<void()> g_stop_service;
|
||||||
const static restbed::Bytes favicon_bytes{favicon_svg, favicon_svg + favicon_svg_len};
|
std::shared_ptr<spdlog::logger> err_handler::logger{};
|
||||||
|
|
||||||
void signal_shutdown(const int) {
|
Server server{};
|
||||||
spdlog::info("Received stop signal");
|
std::string index_etag{};
|
||||||
g_service->stop();
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keep_alive)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (beast::system_error &se) {
|
||||||
|
if (se.code() != http::error::end_of_stream)
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
ec = stream.socket().shutdown(tcp::socket::shutdown_send, ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void error_handler(const int code, const std::exception& ex, const std::shared_ptr<restbed::Session> session) {
|
net::awaitable<void> do_listen(tcp::endpoint endpoint) {
|
||||||
std::stringstream ss;
|
auto acceptor = net::use_awaitable.as_default_on(tcp::acceptor(co_await net::this_coro::executor));
|
||||||
ss << "Encountered error with code '" << std::to_string(code) << "'";
|
|
||||||
if (session != nullptr)
|
acceptor.open(endpoint.protocol());
|
||||||
ss << " in session from '" << session->get_origin() << "'";
|
acceptor.set_option(net::socket_base::reuse_address(true));
|
||||||
ss << ": " << ex.what();
|
acceptor.bind(endpoint);
|
||||||
spdlog::error(ss.str());
|
acceptor.listen(net::socket_base::max_listen_connections);
|
||||||
if (session != nullptr)
|
|
||||||
session->close(code, ex.what());
|
while (true) {
|
||||||
|
net::co_spawn(
|
||||||
|
acceptor.get_executor(),
|
||||||
|
do_session(tcp_stream(co_await acceptor.async_accept())),
|
||||||
|
err_handler{}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
@ -39,83 +125,28 @@ int main() {
|
|||||||
spdlog::default_logger()->sinks().push_back(file_sink);
|
spdlog::default_logger()->sinks().push_back(file_sink);
|
||||||
spdlog::set_level(spdlog::level::trace);
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
|
||||||
std::string index_etag;
|
|
||||||
{
|
{
|
||||||
auto md5_hash = Botan::HashFunction::create_or_throw("MD5");
|
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()) + "\"";
|
index_etag = "\"" + Botan::hex_encode(md5_hash->final()) + "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mrpc_resource = std::make_shared<restbed::Resource>();
|
net::io_context ioc{server.config.threads};
|
||||||
mrpc_resource->set_path("/mrpc");
|
net::co_spawn(ioc, do_listen(tcp::endpoint{net::ip::address_v4::any(), server.config.server_port}), err_handler{});
|
||||||
Server server{mrpc_resource};
|
|
||||||
|
|
||||||
#define mk_res(url) auto url##_resource = std::make_shared<restbed::Resource>(); \
|
net::signal_set signals{ioc, SIGINT, SIGTERM};
|
||||||
url##_resource->set_path("/" #url); \
|
signals.async_wait([&ioc](const beast::error_code&, int){ ioc.stop(); });
|
||||||
url##_resource->set_method_handler("POST", [&server](auto s){ server.url(s); })
|
g_stop_service = [&ioc] { ioc.stop(); };
|
||||||
|
|
||||||
mk_res(download);
|
spdlog::info("Listening on :{}", server.config.server_port);
|
||||||
mk_res(download_multi);
|
|
||||||
mk_res(upload);
|
|
||||||
#undef mk_res
|
|
||||||
|
|
||||||
auto index_resource = std::make_shared<restbed::Resource>();
|
std::vector<std::thread> v;
|
||||||
index_resource->set_path("/");
|
for (std::uint16_t i = server.config.threads-1; i > 0; --i)
|
||||||
index_resource->set_method_handler("GET", [&index_etag](const std::shared_ptr<restbed::Session>& s){
|
v.emplace_back([&ioc] { ioc.run(); });
|
||||||
auto req = s->get_request();
|
ioc.run();
|
||||||
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}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
auto favicon_resource = std::make_shared<restbed::Resource>();
|
for (auto &t : v)
|
||||||
favicon_resource->set_path("/favicon.svg");
|
t.join();
|
||||||
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();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -97,6 +97,6 @@ std::optional<std::string> Server::Admin_unsudo(std::string &&token) {
|
|||||||
std::optional<std::string> Server::Admin_shutdown(std::string &&token) {
|
std::optional<std::string> Server::Admin_shutdown(std::string &&token) {
|
||||||
check_admin_optional();
|
check_admin_optional();
|
||||||
spdlog::info("Received rpc shutdown request from admin user {}", user->name);
|
spdlog::info("Received rpc shutdown request from admin user {}", user->name);
|
||||||
g_service->stop();
|
g_stop_service();
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -4,181 +4,146 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <miniz.h>
|
#include "../util/miniz.hxx"
|
||||||
#include <corvusoft/restbed/session.hpp>
|
|
||||||
#include <corvusoft/restbed/request.hpp>
|
|
||||||
#include <corvusoft/restbed/response.hpp>
|
|
||||||
#include "server_internal.hxx"
|
#include "server_internal.hxx"
|
||||||
|
|
||||||
void Server::download(const std::shared_ptr<restbed::Session> &s) {
|
net::awaitable<void> Server::download(tcp_stream &s, const http::request<http::string_body> &req) {
|
||||||
const auto req = s->get_request();
|
auto body = req.body();
|
||||||
std::size_t body_len = req->get_header("Content-Length", 0);
|
if (body.empty()) { co_await send_error(s, req, "Empty body"); co_return; }
|
||||||
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 node_str, token;
|
|
||||||
for (const auto part : std::views::split(body, '&')) {
|
|
||||||
std::string_view part_view{part};
|
|
||||||
auto equal_pos = part_view.find_first_of('=');
|
|
||||||
auto key = part_view.substr(0, equal_pos);
|
|
||||||
if (key == "node")
|
|
||||||
node_str = part_view.substr(equal_pos+1);
|
|
||||||
else if (key == "token")
|
|
||||||
token = part_view.substr(equal_pos+1);
|
|
||||||
}
|
|
||||||
if (node_str.empty())
|
|
||||||
return s->close(400, "Missing node");
|
|
||||||
if (token.empty())
|
|
||||||
return s->close(400, "Missing token");
|
|
||||||
|
|
||||||
std::uint64_t node_id;
|
std::string node_str, token;
|
||||||
auto res = std::from_chars(node_str.data(), node_str.data() + node_str.size(), node_id);
|
for (const auto part : std::views::split(body, '&')) {
|
||||||
if (res.ec != std::errc{})
|
std::string_view part_view{part};
|
||||||
return s->close(400, "Invalid node");
|
auto equal_pos = part_view.find_first_of('=');
|
||||||
|
auto key = part_view.substr(0, equal_pos);
|
||||||
check_user() return s->close(400, "Invalid user");
|
if (key == "node")
|
||||||
{
|
node_str = part_view.substr(equal_pos+1);
|
||||||
std::shared_lock lock{user->node_lock};
|
else if (key == "token")
|
||||||
auto node = get_node(user, node_id);
|
token = part_view.substr(equal_pos+1);
|
||||||
if (!node) return s->close(400, "Invalid node");
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
if (node_str.empty()) { co_await send_error(s, req, "Missing node"); co_return; }
|
||||||
|
if (token.empty()) { co_await send_error(s, req, "Missing token"); co_return; }
|
||||||
|
|
||||||
|
std::uint64_t node_id;
|
||||||
|
auto fc_res = std::from_chars(node_str.data(), node_str.data() + node_str.size(), node_id);
|
||||||
|
if (fc_res.ec != std::errc{}) { co_await send_error(s, req, "Invalid node"); co_return; }
|
||||||
|
|
||||||
|
check_user() { co_await send_error(s, req, "Invalid user"); co_return; }
|
||||||
|
|
||||||
|
std::shared_lock lock{user->node_lock};
|
||||||
|
auto node = get_node(user, node_id);
|
||||||
|
if (!node) { co_await send_error(s, req, "Invalid node"); co_return; }
|
||||||
|
|
||||||
|
beast::error_code ec;
|
||||||
|
auto mime = get_mime_type(node->name);
|
||||||
|
auto res = create_response<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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::download_multi(const std::shared_ptr<restbed::Session> &s) {
|
struct Zip : public ZipArchive {
|
||||||
const auto req = s->get_request();
|
tcp_stream *s;
|
||||||
const auto body_len = req->get_header("Content-Length", 0);
|
http::response<http::buffer_body> *body;
|
||||||
s->fetch(body_len, [this](const std::shared_ptr<restbed::Session> &s, const restbed::Bytes &b){
|
http::response_serializer<http::buffer_body> *sr;
|
||||||
std::string body{b.cbegin(), b.cend()};
|
|
||||||
if (body.empty())
|
|
||||||
return s->close(400, "empty body");
|
|
||||||
std::string nodes_str, token;
|
|
||||||
for (const auto part : std::views::split(body, '&')) {
|
|
||||||
std::string_view part_view{part};
|
|
||||||
auto equal_pos = part_view.find_first_of('=');
|
|
||||||
auto key = part_view.substr(0, equal_pos);
|
|
||||||
if (key == "nodes")
|
|
||||||
nodes_str = part_view.substr(equal_pos+1);
|
|
||||||
else if (key == "token")
|
|
||||||
token = part_view.substr(equal_pos+1);
|
|
||||||
}
|
|
||||||
if (nodes_str.empty())
|
|
||||||
return s->close(400, "Missing nodes");
|
|
||||||
if (token.empty())
|
|
||||||
return s->close(400, "Missing token");
|
|
||||||
|
|
||||||
std::vector<std::uint64_t> node_ids;
|
Zip(tcp_stream *s, http::response<http::buffer_body> *body, http::response_serializer<http::buffer_body> *sr)
|
||||||
for (const auto part : std::views::split(nodes_str, '.')) {
|
: s(s), body(body), sr(sr) {}
|
||||||
std::uint64_t node_id;
|
|
||||||
auto res = std::from_chars(part.data(), part.data() + part.size(), node_id);
|
|
||||||
if (res.ec != std::errc{})
|
|
||||||
return s->close(400, "Invalid node " + std::string{std::string_view{part}});
|
|
||||||
node_ids.push_back(node_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
check_user() return s->close(400, "Invalid user");
|
protected:
|
||||||
{
|
net::awaitable<void> write(const void *pVoid, size_t n) override {
|
||||||
std::shared_lock lock{user->node_lock};
|
if (n == 0)
|
||||||
std::vector<std::shared_ptr<Node>> nodes;
|
co_return;
|
||||||
for (auto node_id : node_ids) {
|
|
||||||
auto node = get_node(user, node_id);
|
body->body().data = (void*)pVoid;
|
||||||
if (!node) return s->close(400, "Invalid node " + std::to_string(node_id));
|
body->body().size = n;
|
||||||
nodes.push_back(node);
|
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; }
|
||||||
|
|
||||||
|
std::string nodes_str, token;
|
||||||
|
for (const auto part : std::views::split(body, '&')) {
|
||||||
|
std::string_view part_view{part};
|
||||||
|
auto equal_pos = part_view.find_first_of('=');
|
||||||
|
auto key = part_view.substr(0, equal_pos);
|
||||||
|
if (key == "nodes")
|
||||||
|
nodes_str = part_view.substr(equal_pos+1);
|
||||||
|
else if (key == "token")
|
||||||
|
token = part_view.substr(equal_pos+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes_str.empty()) { co_await send_error(s, req, "Missing nodes"); co_return; }
|
||||||
|
if (token.empty()) { co_await send_error(s, req, "Missing token"); co_return; }
|
||||||
|
|
||||||
|
std::vector<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{}) { co_await send_error(s, req, "Invalid node " + std::string{std::string_view{part}}); co_return; }
|
||||||
|
node_ids.push_back(node_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
check_user() { co_await send_error(s, req, "Invalid user"); co_return; }
|
||||||
|
|
||||||
|
std::shared_lock lock{user->node_lock};
|
||||||
|
std::vector<std::shared_ptr<Node>> nodes;
|
||||||
|
for (auto node_id : node_ids) {
|
||||||
|
auto node = get_node(user, node_id);
|
||||||
|
if (!node) { co_await send_error(s, req, "Invalid node " + std::to_string(node_id)); co_return; }
|
||||||
|
nodes.push_back(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res = create_response<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;
|
||||||
|
|
||||||
|
http::response_serializer<http::buffer_body> sr{res};
|
||||||
|
|
||||||
|
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{});
|
||||||
|
|
||||||
|
while (!todo.empty()) {
|
||||||
|
const auto &node = todo.front();
|
||||||
|
if (node.first->file) {
|
||||||
|
auto path = node.second / node.first->name;
|
||||||
|
auto real_path = user->user_dir / std::to_string(node.first->id);
|
||||||
|
co_await zip.add_file(real_path, path);
|
||||||
|
} else {
|
||||||
|
auto path = node.second / node.first->name;
|
||||||
|
auto dir_path = path.string() + "/";
|
||||||
|
co_await zip.add_dir(dir_path);
|
||||||
|
for (const auto &child : node.first->children) {
|
||||||
|
auto p = std::make_pair(child, path);
|
||||||
|
todo.push_back(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
s->yield(
|
|
||||||
200,
|
|
||||||
"",
|
|
||||||
std::multimap<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};
|
|
||||||
|
|
||||||
mz_zip_archive archive;
|
|
||||||
mz_zip_zero_struct(&archive);
|
|
||||||
archive.m_pWrite = zip_write_func;
|
|
||||||
archive.m_pIO_opaque = s.get();
|
|
||||||
|
|
||||||
mz_zip_writer_init_v2(&archive, 0, MZ_ZIP_FLAG_WRITE_ZIP64);
|
|
||||||
|
|
||||||
std::deque<std::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);
|
|
||||||
} else {
|
|
||||||
auto path = node.second / node.first->name;
|
|
||||||
auto dir_path = path.string() + "/";
|
|
||||||
mz_zip_writer_add_mem(&archive, dir_path.c_str(), nullptr, 0, 0);
|
|
||||||
for (const auto &child : node.first->children) {
|
|
||||||
auto p = std::make_pair(child, path);
|
|
||||||
if (child->file)
|
|
||||||
handle_file(p);
|
|
||||||
else
|
|
||||||
todo.push_back(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
todo.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
mz_zip_writer_finalize_archive(&archive);
|
|
||||||
mz_zip_writer_end(&archive);
|
|
||||||
|
|
||||||
s->close("0\r\n\r\n");
|
|
||||||
}};
|
|
||||||
zip_thread.detach();
|
|
||||||
}
|
}
|
||||||
});
|
todo.pop_front();
|
||||||
|
}
|
||||||
|
co_await zip.end();
|
||||||
|
|
||||||
|
res.body().data = nullptr;
|
||||||
|
res.body().more = false;
|
||||||
|
co_await http::async_write(s, sr, net::use_awaitable);
|
||||||
}
|
}
|
||||||
|
@ -34,43 +34,6 @@ std::string get_path(std::shared_ptr<Node> node) {
|
|||||||
return ret.empty() ? "/" : ret;
|
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 Server::nodes_size(const std::shared_ptr<User> &user, const std::vector<std::uint64_t> &ids) {
|
||||||
std::uint64_t total = 0;
|
std::uint64_t total = 0;
|
||||||
std::deque<Node*> todo;
|
std::deque<Node*> todo;
|
||||||
@ -231,22 +194,53 @@ std::optional<std::string> Server::FS_move_nodes(std::string &&token, std::vecto
|
|||||||
return std::nullopt;
|
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() {
|
check_user() {
|
||||||
stream.close();
|
stream.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread deleter{[this, nodes = std::move(nodes), user = std::move(user), stream = std::move(stream)](){
|
std::unique_lock lock{user->node_lock};
|
||||||
for (const auto& node : nodes) {
|
std::stack<std::shared_ptr<Node>> todo;
|
||||||
if (node == 0)
|
for (const auto &node_id: nodes) {
|
||||||
continue;
|
if (node_id == 0)
|
||||||
delete_node(user, node, [&stream](const std::string &log){ stream.send(log); });
|
continue;
|
||||||
|
auto node = user->nodes.find(node_id);
|
||||||
|
if (node == user->nodes.end())
|
||||||
|
continue;
|
||||||
|
todo.push(node->second);
|
||||||
|
}
|
||||||
|
while (!todo.empty()) {
|
||||||
|
auto node = todo.top();
|
||||||
|
auto log_path = get_path(node);
|
||||||
|
|
||||||
|
if (!node->children.empty()) {
|
||||||
|
stream.send("Entering " + log_path + "\n");
|
||||||
|
for (const auto &child: node->children)
|
||||||
|
todo.push(child);
|
||||||
|
node->children.clear();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
stream.close();
|
|
||||||
save();
|
stream.send("Deleting " + log_path + "...");
|
||||||
}};
|
if (node->file) {
|
||||||
deleter.detach();
|
auto path = user->user_dir / std::to_string(node->id);
|
||||||
|
std::filesystem::remove(path);
|
||||||
|
if (node->preview)
|
||||||
|
std::filesystem::remove(path.replace_extension("png"));
|
||||||
|
}
|
||||||
|
if (node->parent)
|
||||||
|
node->parent->children.remove(node);
|
||||||
|
node->parent.reset();
|
||||||
|
user->nodes.erase(node->id);
|
||||||
|
stream.send(" Done\n");
|
||||||
|
|
||||||
|
todo.pop();
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds{1});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
mrpc::Response<std::string> Server::FS_download_preview(std::string &&token, std::uint64_t &&node_id) {
|
mrpc::Response<std::string> Server::FS_download_preview(std::string &&token, std::uint64_t &&node_id) {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
#include <asio.hpp>
|
#include <boost/asio/streambuf.hpp>
|
||||||
#include <botan_asio/asio_stream.h>
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include "server_internal.hxx"
|
#include "server_internal.hxx"
|
||||||
|
#include "../util/boost.hxx"
|
||||||
|
|
||||||
struct SocketData {
|
struct SocketData {
|
||||||
asio::streambuf socket_buf;
|
net::streambuf socket_buf;
|
||||||
std::istream socket_istream{&socket_buf};
|
std::istream socket_istream{&socket_buf};
|
||||||
std::string last_send;
|
std::string last_send;
|
||||||
};
|
};
|
||||||
@ -20,7 +20,7 @@ void expect_code(SocketData &data, Socket &s, const std::string& code) {
|
|||||||
std::string line;
|
std::string line;
|
||||||
do {
|
do {
|
||||||
std::string buf;
|
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');
|
std::getline(data.socket_istream, line, '\n');
|
||||||
} while (line[3] == '-');
|
} while (line[3] == '-');
|
||||||
if (std::string_view{line}.substr(0, 3) != code)
|
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) {
|
void send(SocketData &data, Socket &s, std::string l) {
|
||||||
data.last_send = l.substr(0, l.find_first_of('\r'));
|
data.last_send = l.substr(0, l.find_first_of('\r'));
|
||||||
l += "\r\n";
|
l += "\r\n";
|
||||||
asio::write(s, asio::buffer(l, l.size()));
|
net::write(s, net::buffer(l, l.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_date() {
|
std::string get_date() {
|
||||||
@ -44,7 +44,7 @@ std::string get_date() {
|
|||||||
|
|
||||||
std::string get_hostname() {
|
std::string get_hostname() {
|
||||||
try {
|
try {
|
||||||
return asio::ip::host_name();
|
return net::ip::host_name();
|
||||||
} catch (std::exception &_) {
|
} catch (std::exception &_) {
|
||||||
return "";
|
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; }
|
[[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
|
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>(
|
auto ssl_ctx = std::make_shared<Botan::TLS::Context>(
|
||||||
std::make_shared<CredMan>(),
|
std::make_shared<CredMan>(),
|
||||||
#if defined(BOTAN_HAS_SYSTEM_RNG)
|
#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>()
|
std::make_shared<Policy>()
|
||||||
);
|
);
|
||||||
|
|
||||||
asio::ip::tcp::socket s{ctx};
|
net::ip::tcp::socket s{ctx};
|
||||||
asio::ip::tcp::resolver res{ctx};
|
net::ip::tcp::resolver res{ctx};
|
||||||
|
|
||||||
SocketData data;
|
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");
|
expect_code(data, s, "220");
|
||||||
|
|
||||||
send(data, s, "EHLO " + host_name);
|
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");
|
expect_code(data, s, "220");
|
||||||
|
|
||||||
// switch_to_ssl
|
// 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);
|
ss.handshake(Botan::TLS::Connection_Side::Client);
|
||||||
|
|
||||||
send(data, ss, "EHLO " + host_name);
|
send(data, ss, "EHLO " + host_name);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#include "fileserver.hxx"
|
#include "fileserver.hxx"
|
||||||
#include <corvusoft/restbed/session.hpp>
|
#include <boost/asio/co_spawn.hpp>
|
||||||
#include <corvusoft/restbed/resource.hpp>
|
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||||
#include <corvusoft/restbed/request.hpp>
|
|
||||||
|
|
||||||
|
using namespace boost::asio::experimental::awaitable_operators;
|
||||||
using namespace mrpc;
|
using namespace mrpc;
|
||||||
|
|
||||||
|
|
||||||
@ -356,67 +356,68 @@ PathSegment& PathSegment::operator<<(const rapidjson::Value &__j) {
|
|||||||
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void send_msg(const std::shared_ptr<restbed::Session> &c, const T &v) {
|
net::awaitable<void> send_msg(tcp_stream &c, const http::request<http::string_body> &req, const T &v) {
|
||||||
if (c->is_closed())
|
|
||||||
return;
|
|
||||||
rapidjson::StringBuffer s;
|
rapidjson::StringBuffer s;
|
||||||
mrpc::MRPCJWriter writer{s};
|
mrpc::MRPCJWriter writer{s};
|
||||||
v >> writer;
|
v >> writer;
|
||||||
const auto body_ptr = s.GetString();
|
auto res = create_response<http::status::ok, http::string_body, http::string_body>(req);
|
||||||
const auto body = restbed::Bytes{body_ptr, body_ptr+s.GetLength()};
|
res.set(http::field::content_type, "application/json");
|
||||||
c->yield(
|
res.body() = s.GetString();
|
||||||
200,
|
res.prepare_payload();
|
||||||
body,
|
co_await http::async_write(c, res, net::use_awaitable);
|
||||||
std::multimap<std::string, std::string>{
|
|
||||||
{"Content-Type", "application/json"},
|
|
||||||
{"Content-Length", std::to_string(body.size())}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void send_sse_msg(const std::shared_ptr<restbed::Session> &c, const T &v) {
|
net::awaitable<void> send_sse_msg(tcp_stream *c, const T &v) {
|
||||||
if (c->is_closed())
|
|
||||||
return;
|
|
||||||
rapidjson::StringBuffer s;
|
rapidjson::StringBuffer s;
|
||||||
std::memcpy(s.Push(5), "data:", 5);
|
std::memcpy(s.Push(5), "data:", 5);
|
||||||
mrpc::MRPCJWriter writer{s};
|
mrpc::MRPCJWriter writer{s};
|
||||||
v >> writer;
|
v >> writer;
|
||||||
std::memcpy(s.Push(2), "\n\n", 2);
|
std::memcpy(s.Push(2), "\n\n", 2);
|
||||||
const auto body_ptr = s.GetString();
|
co_await net::async_write(*c, net::buffer(s.GetString(), s.GetLength()), net::use_awaitable);
|
||||||
const auto body = restbed::Bytes{body_ptr, body_ptr+s.GetLength()};
|
|
||||||
c->yield(body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mrpc::MRPCStreamImpl::MRPCStreamImpl(const std::shared_ptr<restbed::Session> &conn) : conn(conn) {
|
template<typename T>
|
||||||
conn->yield(
|
net::awaitable<void> process_channel(tcp_stream *conn, std::shared_ptr<net::experimental::concurrent_channel<void(boost::system::error_code, T)>> chan) {
|
||||||
200,
|
http::response<http::empty_body> res{http::status::ok, 11};
|
||||||
std::multimap<std::string, std::string>{
|
res.keep_alive(false);
|
||||||
{"Cache-Control", "no-cache"},
|
res.set(http::field::cache_control, "no-cache");
|
||||||
{"Content-Type", "text/event-stream"}
|
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"); }
|
template<> void MRPCStream<std::string>::send(const std::string &v) noexcept { chan->try_send(beast::error_code{}, v); }
|
||||||
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); }
|
|
||||||
|
|
||||||
|
net::awaitable<void> mrpc::MRPCServer::msg_handler(tcp_stream &__c, const http::request<http::string_body> &__req) {
|
||||||
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) {
|
|
||||||
rapidjson::Document __j;
|
rapidjson::Document __j;
|
||||||
__j.Parse((const char*)__msg.data(), __msg.size());
|
__j.Parse(__req.body().data(), __req.body().size());
|
||||||
if (__j.HasParseError())
|
if (__j.HasParseError())
|
||||||
throw std::exception{};
|
throw std::exception{};
|
||||||
std::string __service, __method;
|
std::string __service, __method;
|
||||||
@ -431,74 +432,76 @@ void mrpc::MRPCServer::msg_handler(const std::shared_ptr<restbed::Session> __c,
|
|||||||
|
|
||||||
std::string username; username << json_get(__data, "username");
|
std::string username; username << json_get(__data, "username");
|
||||||
std::string password; password << json_get(__data, "password");
|
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") {
|
} else if (__method == "login") {
|
||||||
|
|
||||||
std::string username; username << json_get(__data, "username");
|
std::string username; username << json_get(__data, "username");
|
||||||
std::string password; password << json_get(__data, "password");
|
std::string password; password << json_get(__data, "password");
|
||||||
std::optional<std::string> otp; otp << json_get(__data, "otp");
|
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") {
|
} else if (__method == "send_recovery_key") {
|
||||||
|
|
||||||
std::string username; username << json_get(__data, "username");
|
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") {
|
} else if (__method == "reset_password") {
|
||||||
|
|
||||||
std::string key; key << json_get(__data, "key");
|
std::string key; key << json_get(__data, "key");
|
||||||
std::string password; password << json_get(__data, "password");
|
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") {
|
} else if (__method == "change_password") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::string old_pw; old_pw << json_get(__data, "old_pw");
|
std::string old_pw; old_pw << json_get(__data, "old_pw");
|
||||||
std::string new_pw; new_pw << json_get(__data, "new_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") {
|
} else if (__method == "logout") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
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") {
|
} else if (__method == "logout_all") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
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") {
|
} else if (__method == "tfa_setup_mail") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
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") {
|
} else if (__method == "tfa_setup_totp") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
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") {
|
} else if (__method == "tfa_complete") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::string otp; otp << json_get(__data, "otp");
|
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") {
|
} else if (__method == "tfa_disable") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
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") {
|
} else if (__method == "delete_user") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
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") {
|
} else if (__method == "session_info") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
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 { throw std::exception{}; }
|
||||||
} else if (__service == "Admin") {
|
} 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");
|
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") {
|
} else if (__method == "delete_user") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::uint64_t user; user << json_get(__data, "user");
|
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") {
|
} else if (__method == "logout") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::uint64_t user; user << json_get(__data, "user");
|
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") {
|
} else if (__method == "disable_tfa") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::uint64_t user; user << json_get(__data, "user");
|
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") {
|
} else if (__method == "set_admin") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::uint64_t user; user << json_get(__data, "user");
|
std::uint64_t user; user << json_get(__data, "user");
|
||||||
bool admin; admin << json_get(__data, "admin");
|
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") {
|
} else if (__method == "set_enabled") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::uint64_t user; user << json_get(__data, "user");
|
std::uint64_t user; user << json_get(__data, "user");
|
||||||
bool enabled; enabled << json_get(__data, "enabled");
|
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") {
|
} else if (__method == "sudo") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::uint64_t user; user << json_get(__data, "user");
|
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") {
|
} else if (__method == "unsudo") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
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") {
|
} else if (__method == "shutdown") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
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 { throw std::exception{}; }
|
||||||
} else if (__service == "FS") {
|
} 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::string token; token << json_get(__data, "token");
|
||||||
std::uint64_t node; node << json_get(__data, "node");
|
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") {
|
} else if (__method == "get_path") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::uint64_t node; node << json_get(__data, "node");
|
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") {
|
} else if (__method == "get_nodes_size") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::vector<std::uint64_t> nodes; nodes << json_get(__data, "nodes");
|
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") {
|
} else if (__method == "create_node") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
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::uint64_t parent; parent << json_get(__data, "parent");
|
||||||
std::string name; name << json_get(__data, "name");
|
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") {
|
} else if (__method == "move_nodes") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::vector<std::uint64_t> nodes; nodes << json_get(__data, "nodes");
|
std::vector<std::uint64_t> nodes; nodes << json_get(__data, "nodes");
|
||||||
std::uint64_t parent; parent << json_get(__data, "parent");
|
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") {
|
} 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::string token; token << json_get(__data, "token");
|
||||||
std::vector<std::uint64_t> nodes; nodes << json_get(__data, "nodes");
|
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") {
|
} else if (__method == "download_preview") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::uint64_t node; node << json_get(__data, "node");
|
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") {
|
} else if (__method == "get_mime") {
|
||||||
|
|
||||||
std::string token; token << json_get(__data, "token");
|
std::string token; token << json_get(__data, "token");
|
||||||
std::uint64_t node; node << json_get(__data, "node");
|
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{}; }
|
else { throw std::exception{}; }
|
||||||
}
|
}
|
||||||
|
@ -8,16 +8,11 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <corvusoft/restbed/byte.hpp>
|
|
||||||
#define RAPIDJSON_HAS_STDSTRING 1
|
#define RAPIDJSON_HAS_STDSTRING 1
|
||||||
#include <rapidjson/stringbuffer.h>
|
#include <rapidjson/stringbuffer.h>
|
||||||
#include <rapidjson/writer.h>
|
#include <rapidjson/writer.h>
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
|
#include "../../util/boost.hxx"
|
||||||
namespace restbed {
|
|
||||||
class Resource;
|
|
||||||
class Session;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace mrpc {
|
namespace mrpc {
|
||||||
using MRPCJWriter = rapidjson::Writer<rapidjson::StringBuffer>;
|
using MRPCJWriter = rapidjson::Writer<rapidjson::StringBuffer>;
|
||||||
@ -103,25 +98,20 @@ struct PathSegment {
|
|||||||
PathSegment& operator <<(const rapidjson::Value&);
|
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>
|
template<typename T>
|
||||||
struct MRPCStream final : MRPCStreamImpl {
|
struct MRPCStream final {
|
||||||
explicit MRPCStream(const std::shared_ptr<restbed::Session> &conn) : MRPCStreamImpl(conn) {}
|
explicit MRPCStream(tcp_stream *conn);
|
||||||
void send(const T &v) const noexcept;
|
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>;
|
template struct MRPCStream<std::string>;
|
||||||
|
|
||||||
struct MRPCServer {
|
struct MRPCServer {
|
||||||
MRPCServer() = delete;
|
net::awaitable<void> msg_handler(tcp_stream&, const http::request<http::string_body>&);
|
||||||
explicit MRPCServer(std::shared_ptr<restbed::Resource>&);
|
|
||||||
private:
|
private:
|
||||||
virtual std::optional<std::string> Auth_signup(std::string &&username, std::string &&password) = 0;
|
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;
|
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<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 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 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_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 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;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,16 @@ void Server::logout_user(std::uint64_t id) {
|
|||||||
void Server::delete_user(const std::shared_ptr<User> &user) {
|
void Server::delete_user(const std::shared_ptr<User> &user) {
|
||||||
std::unique_lock lock{user_lock};
|
std::unique_lock lock{user_lock};
|
||||||
logout_user(user->id);
|
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);
|
users.erase(user->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
#ifndef FILESERVER_SERVER_HXX
|
#ifndef FILESERVER_SERVER_HXX
|
||||||
#define FILESERVER_SERVER_HXX
|
#define FILESERVER_SERVER_HXX
|
||||||
|
|
||||||
#include <corvusoft/restbed/service.hpp>
|
|
||||||
#include "mrpc/fileserver.hxx"
|
#include "mrpc/fileserver.hxx"
|
||||||
#include "../data/data.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 {
|
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<Token> get_token(const std::string&);
|
||||||
std::shared_ptr<User> is_token_valid(const std::string&);
|
std::shared_ptr<User> is_token_valid(const std::string&);
|
||||||
std::shared_ptr<User> get_user(std::uint64_t id);
|
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 logout_user(std::uint64_t id);
|
||||||
void delete_user(const std::shared_ptr<User> &user);
|
void delete_user(const std::shared_ptr<User> &user);
|
||||||
void send_tfa_mail(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);
|
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);
|
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>&);
|
net::awaitable<void> download(tcp_stream&, const http::request<http::string_body>&);
|
||||||
void download_multi(const std::shared_ptr<restbed::Session>&);
|
net::awaitable<void> download_multi(tcp_stream&, const http::request<http::string_body>&);
|
||||||
void upload(const std::shared_ptr<restbed::Session>&);
|
net::awaitable<void> upload(tcp_stream&, tcp_buffer&, http::request_parser<http::file_body>&);
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<std::string> Auth_signup(std::string &&username, std::string &&password) override;
|
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<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;
|
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;
|
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_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;
|
mrpc::Response<std::string> FS_get_mime(std::string &&token, std::uint64_t &&node) override;
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#ifndef FILESERVER_SERVER_INTERNAL_HXX
|
#ifndef FILESERVER_SERVER_INTERNAL_HXX
|
||||||
#define FILESERVER_SERVER_INTERNAL_HXX
|
#define FILESERVER_SERVER_INTERNAL_HXX
|
||||||
|
#include "../util/botan.hxx"
|
||||||
#include <botan_all.h>
|
|
||||||
#include "server.hxx"
|
#include "server.hxx"
|
||||||
|
|
||||||
// TODO log user action with __FUNC__
|
// TODO log user action with __FUNC__
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <corvusoft/restbed/session.hpp>
|
|
||||||
#include <corvusoft/restbed/request.hpp>
|
|
||||||
#include <corvusoft/restbed/response.hpp>
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <stb_image.h>
|
#include <stb_image.h>
|
||||||
#include <stb_image_resize2.h>
|
#include <stb_image_resize2.h>
|
||||||
@ -20,10 +17,10 @@ struct UploadInfo {
|
|||||||
std::shared_ptr<Node> node;
|
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;
|
int x, y, channels;
|
||||||
auto img = std::unique_ptr<stbi_uc, decltype(&free)>
|
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)
|
if (!img)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -44,70 +41,66 @@ void make_preview(const std::shared_ptr<UploadInfo>& info) {
|
|||||||
if (!rimg)
|
if (!rimg)
|
||||||
return;
|
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))
|
if (!stbi_write_png(png_path.c_str(), new_x, new_y, channels, rimg.get(), 0))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
info->node->preview = true;
|
node->preview = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetch_handler(const std::shared_ptr<restbed::Session> &s, const restbed::Bytes &bytes) {
|
net::awaitable<void> Server::upload(tcp_stream &s, tcp_buffer &buf, http::request_parser<http::file_body> &req) {
|
||||||
std::shared_ptr<UploadInfo> info = s->get("upload");
|
auto &body = req.get();
|
||||||
|
|
||||||
std::size_t read = bytes.size();
|
if (body["X-Node"].empty()) { co_await send_error(s, body, "Missing node"); co_return; }
|
||||||
info->to_read -= std::min(read, info->to_read);
|
if (body["X-Token"].empty()) { co_await send_error(s, body, "Missing token"); co_return; }
|
||||||
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);
|
|
||||||
|
|
||||||
std::size_t real_size = std::filesystem::file_size(info->path);
|
if (body[http::field::transfer_encoding] == "chunked") {
|
||||||
info->node->size = real_size;
|
spdlog::critical("Encountered a chunked upload!");
|
||||||
auto ext = std::filesystem::path{info->node->name}.extension().string();
|
co_await send_error(s, body, "Sorry but your browser is not supported yet"); co_return;
|
||||||
if (real_size < max_image_size && image_extension.contains(ext))
|
|
||||||
make_preview(info);
|
|
||||||
|
|
||||||
info->node_lock.unlock();
|
|
||||||
info->server->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::upload(const std::shared_ptr<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");
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint64_t node_id = req->get_header("X-Node", 0);
|
auto node_str = body["X-Node"];
|
||||||
std::string token = req->get_header("X-Token");
|
std::uint64_t node_id = 0;
|
||||||
|
std::from_chars(node_str.data(), node_str.data() + node_str.size(), node_id);
|
||||||
|
std::string token = body["X-Token"];
|
||||||
|
|
||||||
check_user() return s->close(400, "Invalid user");
|
check_user() { co_await send_error(s, body, "Invalid user"); co_return; }
|
||||||
{
|
|
||||||
std::shared_lock lock{user->node_lock};
|
auto node = get_node(user, node_id);
|
||||||
auto node = get_node(user, node_id);
|
if (!node) { co_await send_error(s, body, "Invalid node"); co_return; }
|
||||||
if (!node) return s->close(400, "Invalid node");
|
if (!node->file) { co_await send_error(s, body, "Can't upload to a directory"); co_return; }
|
||||||
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);
|
auto path = user->user_dir / std::to_string(node->id);
|
||||||
auto path = user->user_dir / std::to_string(node->id);
|
if (node->preview) {
|
||||||
if (node->preview) {
|
node->preview = false;
|
||||||
node->preview = false;
|
std::filesystem::remove(path.replace_extension("png"));
|
||||||
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
66
src/util/boost.hxx
Normal 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
8
src/util/botan.hxx
Normal 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
|
@ -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
476
src/util/miniz.cxx
Normal 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
39
src/util/miniz.hxx
Normal 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
|
@ -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
|
|
Loading…
Reference in New Issue
Block a user