From 9e964b3ea84fca11bf4d43497e913a50b96b3de7 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Wed, 10 Jun 2020 18:13:14 +0200 Subject: [PATCH] Some bug fixes and final version before release --- file/CMakeLists.txt | 2 + file/include/files/Config.h | 13 + file/include/files/FileServer.h | 9 +- file/local_server/Config.cpp | 13 + file/local_server/HTTPUtils.cpp | 35 ++ file/local_server/HTTPUtils.h | 7 + file/local_server/LocalFileProvider.cpp | 53 +-- file/local_server/LocalFileProvider.h | 57 ++- file/local_server/LocalFileSystem.cpp | 31 +- file/local_server/LocalFileTransfer.cpp | 50 ++- .../LocalFileTransferClientWorker.cpp | 35 +- file/local_server/LocalFileTransferDisk.cpp | 137 ++++++-- .../local_server/LocalFileTransferNetwork.cpp | 332 ++++++++++++------ file/test/main.cpp | 65 +++- 14 files changed, 577 insertions(+), 262 deletions(-) create mode 100644 file/include/files/Config.h create mode 100644 file/local_server/Config.cpp create mode 100644 file/local_server/HTTPUtils.cpp create mode 100644 file/local_server/HTTPUtils.h diff --git a/file/CMakeLists.txt b/file/CMakeLists.txt index 4b506af..870bb8e 100644 --- a/file/CMakeLists.txt +++ b/file/CMakeLists.txt @@ -13,6 +13,8 @@ add_library(TeaSpeak-FileServer STATIC local_server/LocalFileTransferNetwork.cpp local_server/clnpath.cpp local_server/NetTools.cpp + local_server/Config.cpp + local_server/HTTPUtils.cpp ) target_link_libraries(TeaSpeak-FileServer PUBLIC TeaSpeak ${StringVariable_LIBRARIES_STATIC} stdc++fs diff --git a/file/include/files/Config.h b/file/include/files/Config.h new file mode 100644 index 0000000..6abfcd1 --- /dev/null +++ b/file/include/files/Config.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +namespace ts::server::file::config { + enum struct Key { + SSL_OPTION_SUPPLIER + }; + + extern void value_updated(Key /* value */); + extern std::function()> ssl_option_supplier; +} \ No newline at end of file diff --git a/file/include/files/FileServer.h b/file/include/files/FileServer.h index 8f815b7..6c084b3 100644 --- a/file/include/files/FileServer.h +++ b/file/include/files/FileServer.h @@ -13,6 +13,7 @@ #include "./ExecuteResponse.h" #define TRANSFER_KEY_LENGTH (32) +#define TRANSFER_MEDIA_BYTES_LENGTH (32) namespace ts::server::file { class VirtualFileServer; @@ -54,7 +55,8 @@ namespace ts::server::file { std::string name{}; std::chrono::system_clock::time_point modified_at{}; - size_t size{0}; + size_t size{0}; /* file only */ + bool empty{false}; /* directory only */ }; enum struct DirectoryModifyErrorType { @@ -150,7 +152,7 @@ namespace ts::server::file { [[nodiscard]] virtual std::shared_ptr> delete_server(const std::shared_ptr &/* server */) = 0; /* channels */ - [[nodiscard]] virtual std::shared_ptr> query_channel_info(const std::shared_ptr &/* server */, ChannelId /* channel */, const std::vector& /* names */) = 0; + [[nodiscard]] virtual std::shared_ptr> query_channel_info(const std::shared_ptr &/* server */, const std::vector>& /* files */) = 0; [[nodiscard]] virtual std::shared_ptr query_channel_directory(const std::shared_ptr &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0; [[nodiscard]] virtual std::shared_ptr> create_channel_directory(const std::shared_ptr &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0; [[nodiscard]] virtual std::shared_ptr> delete_channel_files(const std::shared_ptr &/* server */, ChannelId /* channel */, const std::vector& /* paths */) = 0; @@ -227,6 +229,9 @@ namespace ts::server::file { size_t file_start_offset{0}; size_t file_current_offset{0}; size_t file_total_size{0}; + + double average_speed{0}; + double current_speed{0}; }; struct TransferInitError { diff --git a/file/local_server/Config.cpp b/file/local_server/Config.cpp new file mode 100644 index 0000000..ed9bcbb --- /dev/null +++ b/file/local_server/Config.cpp @@ -0,0 +1,13 @@ +// +// Created by WolverinDEV on 21/05/2020. +// + +#include "files/Config.h" + +using namespace ts::server::file; + +std::function()> config::ssl_option_supplier{nullptr}; + +void config::value_updated(ts::server::file::config::Key) { + /* we currently do nothing */ +} \ No newline at end of file diff --git a/file/local_server/HTTPUtils.cpp b/file/local_server/HTTPUtils.cpp new file mode 100644 index 0000000..0cab6d3 --- /dev/null +++ b/file/local_server/HTTPUtils.cpp @@ -0,0 +1,35 @@ +// +// Created by WolverinDEV on 22/05/2020. +// + +#include +#include "HTTPUtils.h" + +bool http::parse_url_parameters(const std::string_view &query, std::map& result) { + const auto query_offset = query.find('?'); + if(query_offset == std::string::npos) return false; + + const auto query_end_offset = query.find('#', query_offset); /* fragment (if there is any) */ + + auto offset = query_offset + 1; + size_t next_param; + while(offset > 0) { + next_param = query.find('&', offset); + if(next_param >= query_end_offset) + next_param = query_end_offset; + + if(offset >= next_param) + break; + + /* parameter: [offset;next_param) */ + const auto param_view = query.substr(offset, next_param - offset); + const auto assignment_index = param_view.find('='); + if(assignment_index == std::string::npos) + result[std::string{param_view}] = ""; + else + result[std::string{param_view.substr(0, assignment_index)}] = http::decode_url(std::string{param_view.substr(assignment_index + 1)}); + + offset = next_param + 1; + } + return true; +} \ No newline at end of file diff --git a/file/local_server/HTTPUtils.h b/file/local_server/HTTPUtils.h new file mode 100644 index 0000000..a633653 --- /dev/null +++ b/file/local_server/HTTPUtils.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace http { + bool parse_url_parameters(const std::string_view& /* url */, std::map& /* result */); +} \ No newline at end of file diff --git a/file/local_server/LocalFileProvider.cpp b/file/local_server/LocalFileProvider.cpp index c145840..48ca4a7 100644 --- a/file/local_server/LocalFileProvider.cpp +++ b/file/local_server/LocalFileProvider.cpp @@ -9,58 +9,11 @@ using namespace ts::server; using LocalFileServer = file::LocalFileProvider; using LocalVirtualFileServer = file::LocalVirtualFileServer; -EVP_PKEY* ssl_generate_key() { - auto key = std::unique_ptr(EVP_PKEY_new(), ::EVP_PKEY_free); - - auto rsa = RSA_new(); - auto e = std::unique_ptr(BN_new(), ::BN_free); - BN_set_word(e.get(), RSA_F4); - if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return nullptr; - EVP_PKEY_assign_RSA(key.get(), rsa); - return key.release(); -} - -X509* ssl_generate_certificate(EVP_PKEY* key) { - auto cert = X509_new(); - X509_set_pubkey(cert, key); - - ASN1_INTEGER_set(X509_get_serialNumber(cert), 3); - X509_gmtime_adj(X509_get_notBefore(cert), 0); - X509_gmtime_adj(X509_get_notAfter(cert), 31536000L); - - X509_NAME* name = nullptr; - name = X509_get_subject_name(cert); - //for(const auto& subject : this->subjects) - // X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0); - X509_set_subject_name(cert, name); - - name = X509_get_issuer_name(cert); - //for(const auto& subject : this->issues) - // X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0); - - X509_set_issuer_name(cert, name); - - X509_sign(cert, key, EVP_sha512()); - return cert; -} - std::shared_ptr server_instance{}; bool file::initialize(std::string &error) { server_instance = std::make_shared(); - auto options = std::make_shared(); - options->verbose_io = true; - options->context_method = SSLv23_method(); - options->free_unused_keypairs = false; - - { - std::shared_ptr pkey{ssl_generate_key(), ::EVP_PKEY_free}; - std::shared_ptr cert{ssl_generate_certificate(&*pkey), ::X509_free}; - - options->default_keypair({pkey, cert}); - } - - if(!server_instance->initialize(error, options)) { + if(!server_instance->initialize(error)) { server_instance = nullptr; return false; } @@ -81,7 +34,7 @@ std::shared_ptr file::server() { LocalFileServer::LocalFileProvider() : file_system_{}, file_transfer_{this->file_system_} {} LocalFileServer::~LocalFileProvider() {} -bool LocalFileServer::initialize(std::string &error, const std::shared_ptr& ssl_options) { +bool LocalFileServer::initialize(std::string &error) { if(!this->file_system_.initialize(error, "files/")) return false; @@ -100,7 +53,7 @@ bool LocalFileServer::initialize(std::string &error, const std::shared_ptrfile_transfer_.start(bindings, ssl_options)) { + if(!this->file_transfer_.start(bindings)) { error = "transfer server startup failed"; this->file_system_.finalize(); return false; diff --git a/file/local_server/LocalFileProvider.h b/file/local_server/LocalFileProvider.h index 7e3445b..617c666 100644 --- a/file/local_server/LocalFileProvider.h +++ b/file/local_server/LocalFileProvider.h @@ -49,7 +49,7 @@ namespace ts::server::file { std::shared_ptr> delete_server(const std::shared_ptr & /* server */) override; std::shared_ptr> - query_channel_info(const std::shared_ptr & /* server */, ChannelId /* channel */, const std::vector& /* names */) override; + query_channel_info(const std::shared_ptr & /* server */, const std::vector>& /* names */) override; std::shared_ptr query_channel_directory(const std::shared_ptr & id, ChannelId channelId, const std::string &string) override; @@ -93,7 +93,7 @@ namespace ts::server::file { query_directory(const fs::path& /* base */, const std::string &string, bool); [[nodiscard]] std::shared_ptr> - query_file_info(const fs::path& /* base */, const std::vector &string); + query_file_info(const std::vector> &string); #endif template @@ -140,6 +140,8 @@ namespace ts::server::file { STATE_DISCONNECTED } state{STATE_AWAITING_KEY}; + bool finished_signal_send{false}; + enum NetworkingProtocol { PROTOCOL_UNKNOWN, PROTOCOL_HTTPS, @@ -149,6 +151,7 @@ namespace ts::server::file { enum HTTPUploadState { HTTP_AWAITING_HEADER, HTTP_STATE_AWAITING_BOUNDARY, + HTTP_STATE_AWAITING_BOUNDARY_END, HTTP_STATE_UPLOADING, HTTP_STATE_DOWNLOADING }; @@ -159,6 +162,10 @@ namespace ts::server::file { bool currently_processing{false}; FileClient* next_client{nullptr}; + + bool query_media_bytes{false}; + uint8_t media_bytes[TRANSFER_MEDIA_BYTES_LENGTH]{0}; + uint8_t media_bytes_length{0}; } file{}; struct { @@ -166,7 +173,6 @@ namespace ts::server::file { char key[TRANSFER_KEY_LENGTH]{0}; } transfer_key{}; - /* will be used for both directions */ struct { std::mutex mutex{}; size_t bytes{0}; @@ -175,7 +181,21 @@ namespace ts::server::file { Buffer* buffer_head{nullptr}; Buffer** buffer_tail{&buffer_head}; - } buffer{}; + + bool flushed{false}; + } network_buffer{}; + + struct { + std::mutex mutex{}; + size_t bytes{0}; + + bool buffering_stopped{false}; + + Buffer* buffer_head{nullptr}; + Buffer** buffer_tail{&buffer_head}; + + bool flushed{false}; + } disk_buffer{}; struct { sockaddr_storage address{}; @@ -200,6 +220,8 @@ namespace ts::server::file { std::unique_ptr http_header_buffer{nullptr, free_buffer}; HTTPUploadState http_state{HTTPUploadState::HTTP_AWAITING_HEADER}; + std::string http_boundary{}; + /* Only read the transfer key length at the beginning. We than have the actual limit which will be set via throttle */ size_t max_read_buffer_size{TRANSFER_KEY_LENGTH}; } networking{}; @@ -233,7 +255,8 @@ namespace ts::server::file { void add_network_read_event(bool /* ignore bandwidth limits */); bool send_file_bytes(const void* /* buffer */, size_t /* length */); - bool enqueue_buffer_bytes(const void* /* buffer */, size_t /* length */); + bool enqueue_network_buffer_bytes(const void* /* buffer */, size_t /* length */); + bool enqueue_disk_buffer_bytes(const void* /* buffer */, size_t /* length */); [[nodiscard]] inline std::string log_prefix() const { return "[" + net::to_string(this->networking.address) + "]"; } }; @@ -280,6 +303,9 @@ namespace ts::server::file { FILE_IS_NOT_ACCESSIBLE, + FAILED_TO_READ_MEDIA_BYTES, + COUNT_NOT_CREATE_DIRECTORIES, + MAX }; @@ -302,7 +328,9 @@ namespace ts::server::file { /* FILE_SEEK_FAILED */ "failed to seek to target file offset", /* FILE_SIZE_MISMATCH */ "file size miss match", - /* FILE_IS_NOT_ACCESSIBLE */ "file is not accessible" + /* FILE_IS_NOT_ACCESSIBLE */ "file is not accessible", + /* FAILED_TO_READ_MEDIA_BYTES */ "failed to read file media bytes", + /* COUNT_NOT_CREATE_DIRECTORIES */ "could not create required directories" }; enum struct TransferKeyApplyResult { @@ -316,6 +344,7 @@ namespace ts::server::file { enum struct TransferUploadRawResult { MORE_DATA_TO_RECEIVE, FINISH, + FINISH_OVERFLOW, /* UNKNOWN ERROR */ }; @@ -348,17 +377,9 @@ namespace ts::server::file { explicit LocalFileTransfer(filesystem::LocalFileSystem&); ~LocalFileTransfer(); - [[nodiscard]] bool start(const std::deque>& /* bindings */, const std::shared_ptr& /* ssl options */); + [[nodiscard]] bool start(const std::deque>& /* bindings */); void stop(); - [[nodiscard]] inline const auto& ssl_options() const { - return this->ssl_options_; - } - - inline void set_ssl_options(const std::shared_ptr& options) { - this->ssl_options_ = options; - } - std::shared_ptr>> initialize_channel_transfer(Transfer::Direction direction, const std::shared_ptr& server, ChannelId channelId, const TransferInfo &info) override; @@ -393,8 +414,6 @@ namespace ts::server::file { std::deque> transfers_{}; std::deque> pending_transfers{}; - std::shared_ptr ssl_options_{}; - enum ServerState { STOPPED, RUNNING @@ -458,7 +477,7 @@ namespace ts::server::file { void enqueue_disk_io(const std::shared_ptr& /* client */); void execute_disk_io(const std::shared_ptr& /* client */); - [[nodiscard]] TransferUploadRawResult handle_transfer_upload_raw(const std::shared_ptr& /* client */, const char * /* buffer */, size_t /* length */); + [[nodiscard]] TransferUploadRawResult handle_transfer_upload_raw(const std::shared_ptr& /* client */, const char * /* buffer */, size_t /* length */, size_t* /* bytes written */); [[nodiscard]] TransferUploadHTTPResult handle_transfer_upload_http(const std::shared_ptr& /* client */, const char * /* buffer */, size_t /* length */); void send_http_response(const std::shared_ptr& /* client */, http::HttpResponse& /* response */); @@ -499,7 +518,7 @@ namespace ts::server::file { LocalFileProvider(); virtual ~LocalFileProvider(); - [[nodiscard]] bool initialize(std::string& /* error */, const std::shared_ptr& /* ssl options */); + [[nodiscard]] bool initialize(std::string& /* error */); void finalize(); [[nodiscard]] std::string file_base_path() const override; diff --git a/file/local_server/LocalFileSystem.cpp b/file/local_server/LocalFileSystem.cpp index 49ec00f..3b682f3 100644 --- a/file/local_server/LocalFileSystem.cpp +++ b/file/local_server/LocalFileSystem.cpp @@ -190,6 +190,10 @@ std::shared_ptr LocalFileSystem::query_directory(con dentry.type = DirectoryEntry::DIRECTORY; dentry.name = entry.path().filename(); + dentry.empty = fs::is_empty(entry.path(), error); + if(error) + logWarning(LOG_FT, "Failed to query directory empty state for directory {} ({}/{})", target_path.string(), error.value(), error.message()); + dentry.modified_at = fs::last_write_time(entry.path(), error); if(error) logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", entry.path().string(), error.value(), error.message()); @@ -365,14 +369,13 @@ std::shared_ptr> LocalFileS return this->delete_files(this->server_path(id) / fs::u8path("avatars"), avatar); } -std::shared_ptr> LocalFileSystem::query_file_info(const fs::path &base, - const std::vector &paths) { +std::shared_ptr> LocalFileSystem::query_file_info(const std::vector> &paths) { std::error_code error{}; auto response = this->create_execute_response(); std::vector file_infos{}; file_infos.reserve(paths.size()); - for(const auto& path : paths) { + for(const auto& [base, path] : paths) { auto target_path = base / fs::u8path(path); if(this->exceeds_base_path(base, target_path)) { file_infos.emplace_back(FileInfoResponse::StatusType::PATH_EXCEEDS_ROOT_PATH, "", DirectoryEntry{}); @@ -401,10 +404,13 @@ std::shared_ptr> LocalFileSyste DirectoryEntry dentry{}; dentry.type = DirectoryEntry::DIRECTORY; dentry.name = target_path.filename(); + dentry.empty = fs::is_empty(target_path, error); + if(error) + logWarning(LOG_FT, "Failed to query directory empty state for directory {} ({}/{})", target_path.string(), error.value(), error.message()); dentry.modified_at = fs::last_write_time(target_path, error); if(error) - logWarning(LOG_FT, "Failed to query last write time for file {} ({}/{})", target_path.string(), error.value(), error.message()); + logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", target_path.string(), error.value(), error.message()); dentry.size = 0; file_infos.emplace_back(FileInfoResponse::StatusType::SUCCESS, "", dentry); } else if(status.type() == fs::file_type::regular) { @@ -429,14 +435,23 @@ std::shared_ptr> LocalFileSyste return response; } -std::shared_ptr> LocalFileSystem::query_channel_info(const std::shared_ptr &sid, ChannelId cid, const std::vector &paths) { - return this->query_file_info(this->server_channel_path(sid, cid), paths); +std::shared_ptr> LocalFileSystem::query_channel_info(const std::shared_ptr &sid, const std::vector>& files) { + std::vector> file_paths{}; + for(const auto& [channelId, path] : files) + file_paths.emplace_back(this->server_channel_path(sid, channelId), path); + return this->query_file_info(file_paths); } std::shared_ptr> LocalFileSystem::query_icon_info(const std::shared_ptr &sid, const std::vector &paths) { - return this->query_file_info(this->server_path(sid) / fs::u8path("icons"), paths); + std::vector> file_paths{}; + for(const auto& path : paths) + file_paths.emplace_back(this->server_path(sid) / fs::u8path("icons"), path); + return this->query_file_info(file_paths); } std::shared_ptr > LocalFileSystem::query_avatar_info(const std::shared_ptr &sid, const std::vector &paths) { - return this->query_file_info(this->server_path(sid) / fs::u8path("avatars"), paths); + std::vector> file_paths{}; + for(const auto& path : paths) + file_paths.emplace_back(this->server_path(sid) / fs::u8path("avatars"), path); + return this->query_file_info(file_paths); } \ No newline at end of file diff --git a/file/local_server/LocalFileTransfer.cpp b/file/local_server/LocalFileTransfer.cpp index 8633a94..cd6df12 100644 --- a/file/local_server/LocalFileTransfer.cpp +++ b/file/local_server/LocalFileTransfer.cpp @@ -28,11 +28,21 @@ void transfer::free_buffer(Buffer* buffer) { } FileClient::~FileClient() { - auto head = this->buffer.buffer_head; - while (head) { - auto next = head->next; - free_buffer(head); - head = next; + { + auto head = this->network_buffer.buffer_head; + while (head) { + auto next = head->next; + free_buffer(head); + head = next; + } + } + { + auto head = this->disk_buffer.buffer_head; + while (head) { + auto next = head->next; + free_buffer(head); + head = next; + } } assert(!this->file.file_descriptor); @@ -48,10 +58,7 @@ FileClient::~FileClient() { LocalFileTransfer::LocalFileTransfer(filesystem::LocalFileSystem &fs) : file_system_{fs} {} LocalFileTransfer::~LocalFileTransfer() = default; -bool LocalFileTransfer::start(const std::deque>& bindings, const std::shared_ptr& ssl_options) { - assert(ssl_options); - this->ssl_options_ = ssl_options; - +bool LocalFileTransfer::start(const std::deque>& bindings) { (void) this->start_client_worker(); { @@ -129,7 +136,7 @@ std::shared_ptr>> L std::unique_lock tlock{this->transfers_mutex}; { auto transfers = std::count_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr& client) { - return client->transfer && client->transfer->client_unique_id == info.client_unique_id; + return client->transfer && client->transfer->client_unique_id == info.client_unique_id && client->state < FileClient::STATE_DISCONNECTING; }); transfers += std::count_if(this->pending_transfers.begin(), this->pending_transfers.end(), [&](const std::shared_ptr& transfer) { return transfer->client_unique_id == info.client_unique_id; @@ -191,17 +198,12 @@ std::shared_ptr>> L switch (transfer->target_type) { case Transfer::TARGET_TYPE_AVATAR: absolute_path = this->file_system_.absolute_avatar_path(transfer->server, transfer->target_file_path); - logMessage(LOG_FT, "Initialized avatar transfer for avatar \"{}\" ({} bytes, transferring {} bytes).", transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset); break; case Transfer::TARGET_TYPE_ICON: absolute_path = this->file_system_.absolute_icon_path(transfer->server, transfer->target_file_path); - logMessage(LOG_FT, "Initialized icon transfer for icon \"{}\" ({} bytes, transferring {} bytes).", - transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset); break; case Transfer::TARGET_TYPE_CHANNEL_FILE: absolute_path = this->file_system_.absolute_channel_path(transfer->server, transfer->channel_id, transfer->target_file_path); - logMessage(LOG_FT, "Initialized channel transfer for file \"{}/{}\" ({} bytes, transferring {} bytes).", - transfer->channel_id, transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset); break; case Transfer::TARGET_TYPE_UNKNOWN: default: @@ -264,6 +266,24 @@ std::shared_ptr>> L this->pending_transfers.push_back(transfer); } + switch (transfer->target_type) { + case Transfer::TARGET_TYPE_AVATAR: + logMessage(LOG_FT, "Initialized avatar transfer for avatar \"{}\" ({} bytes, transferring {} bytes).", transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset); + break; + case Transfer::TARGET_TYPE_ICON: + logMessage(LOG_FT, "Initialized icon transfer for icon \"{}\" ({} bytes, transferring {} bytes).", + transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset); + break; + case Transfer::TARGET_TYPE_CHANNEL_FILE: + logMessage(LOG_FT, "Initialized channel transfer for file \"{}/{}\" ({} bytes, transferring {} bytes).", + transfer->channel_id, transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset); + break; + case Transfer::TARGET_TYPE_UNKNOWN: + default: + response->emplace_fail(TransferInitError::INVALID_FILE_TYPE, ""); + return response; + } + if(auto callback{this->callback_transfer_registered}; callback) callback(transfer); diff --git a/file/local_server/LocalFileTransferClientWorker.cpp b/file/local_server/LocalFileTransferClientWorker.cpp index 41de957..09ddcaa 100644 --- a/file/local_server/LocalFileTransferClientWorker.cpp +++ b/file/local_server/LocalFileTransferClientWorker.cpp @@ -46,31 +46,16 @@ void LocalFileTransfer::disconnect_client(const std::shared_ptr &cli client->state = flush ? FileClient::STATE_DISCONNECTING : FileClient::STATE_DISCONNECTED; client->timings.disconnecting = std::chrono::system_clock::now(); if(flush) { - const auto network_flush_time = client->networking.client_throttle.expected_writing_time(client->buffer.bytes) + std::chrono::seconds{10}; + const auto network_flush_time = client->networking.client_throttle.expected_writing_time(client->network_buffer.bytes) + std::chrono::seconds{10}; - if(!client->transfer) { - del_ev_noblock(client->networking.event_read); - del_ev_noblock(client->networking.event_throttle); - client->add_network_write_event_nolock(false); + del_ev_noblock(client->networking.event_read); - /* max flush 10 seconds */ - client->networking.disconnect_timeout = std::chrono::system_clock::now() + network_flush_time; - debugMessage(LOG_FT, "{} Disconnecting client. Flushing pending bytes (max {} seconds)", client->log_prefix(), std::chrono::floor(network_flush_time).count()); - } else if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) { - del_ev_noblock(client->networking.event_read); - del_ev_noblock(client->networking.event_write); - del_ev_noblock(client->networking.event_throttle); + /* max flush 10 seconds */ + client->networking.disconnect_timeout = std::chrono::system_clock::now() + network_flush_time; + debugMessage(LOG_FT, "{} Disconnecting client. Flushing pending bytes (max {} seconds)", client->log_prefix(), std::chrono::floor(network_flush_time).count()); - /* no direct timeout needed here, we're just flushing the disk */ - this->enqueue_disk_io(client); - } else if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) { - del_ev_noblock(client->networking.event_read); - client->add_network_write_event_nolock(false); - - /* max flush 10 seconds */ - client->networking.disconnect_timeout = std::chrono::system_clock::now() + network_flush_time; - debugMessage(LOG_FT, "{} Disconnecting client. Flushing pending bytes (max {} seconds)", client->log_prefix(), std::chrono::floor(network_flush_time).count()); - } + client->add_network_write_event_nolock(false); + this->enqueue_disk_io(client); } else { del_ev_noblock(client->networking.event_read); del_ev_noblock(client->networking.event_write); @@ -221,13 +206,17 @@ TransferStatistics LocalFileTransfer::generate_transfer_statistics_report(const stats.file_start_offset = client->transfer->file_offset; stats.file_current_offset = client->statistics.file_transferred.total_bytes + client->transfer->file_offset; stats.file_total_size = client->transfer->expected_file_size; + + stats.average_speed = client->statistics.file_transferred.average_bandwidth(); + stats.current_speed = client->statistics.file_transferred.current_bandwidth(); + return stats; } void LocalFileTransfer::invoke_aborted_callback(const std::shared_ptr &client, const ts::server::file::transfer::TransferError &error) { auto callback{this->callback_transfer_aborted}; - if(!callback) return; + if(!callback || !client->transfer) return; callback(client->transfer, this->generate_transfer_statistics_report(client), error); } diff --git a/file/local_server/LocalFileTransferDisk.cpp b/file/local_server/LocalFileTransferDisk.cpp index c97fa44..4c52bca 100644 --- a/file/local_server/LocalFileTransferDisk.cpp +++ b/file/local_server/LocalFileTransferDisk.cpp @@ -98,6 +98,24 @@ void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) { provider->disk_io.notify_client_processed.notify_all(); } +bool FileClient::enqueue_disk_buffer_bytes(const void *snd_buffer, size_t size) { + auto tbuffer = allocate_buffer(size); + tbuffer->length = size; + tbuffer->offset = 0; + memcpy(tbuffer->data, snd_buffer, size); + + size_t buffer_size; + { + std::lock_guard block{this->disk_buffer.mutex}; + *this->disk_buffer.buffer_tail = tbuffer; + this->disk_buffer.buffer_tail = &tbuffer->next; + + buffer_size = (this->disk_buffer.bytes += size); + } + + return buffer_size > TRANSFER_MAX_CACHED_BYTES; +} + FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr &transfer) { FileInitializeResult result{FileInitializeResult::SUCCESS}; assert(transfer->transfer); @@ -123,6 +141,18 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr goto error_exit; } } else if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) { + std::error_code fs_error{}; + auto parent_path = fs::u8path(absolute_path).parent_path(); + if(!fs::exists(parent_path)) { + if(!fs::create_directories(parent_path, fs_error) || fs_error) { + logError(LOG_FT, "{} Failed to create required directories for file upload for {}: {}/{}", transfer->log_prefix(), absolute_path, fs_error.value(), fs_error.message()); + result = FileInitializeResult::COUNT_NOT_CREATE_DIRECTORIES; + goto error_exit; + } + } else if(fs_error) { + logWarning(LOG_FT, "{} Failed to check for directory existence of {}: {}/{}. Assuming it exists", transfer->log_prefix(), parent_path.string(), fs_error.value(), fs_error.message()); + } + open_flags = (unsigned) O_WRONLY | (unsigned) O_CREAT; if(transfer->transfer->override_exiting) open_flags |= (unsigned) O_TRUNC; @@ -157,7 +187,8 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr result = FileInitializeResult::DISK_IS_READ_ONLY; break; default: - logWarning(LOG_FT, "{} Failed to start file transfer for file {}: {}/{}", transfer->log_prefix(), absolute_path, errno_, strerror(errno_)); + logWarning(LOG_FT, "{} Failed to start file {} for file {}: {}/{}", transfer->log_prefix(), + transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD ? "download" : "upload", absolute_path, errno_, strerror(errno_)); result = FileInitializeResult::FILE_SYSTEM_ERROR; break; } @@ -195,13 +226,32 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr } } } else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) { + transfer->disk_buffer.flushed = true; /* we're not using this buffer, so it will be flushed all the times */ + auto file_size = lseek(file_data.file_descriptor, 0, SEEK_END); if(file_size != transfer->transfer->expected_file_size) { logWarning(LOG_FT, "{} Expected target file to be of size {}, but file is actually of size {}", transfer->log_prefix(), transfer->transfer->expected_file_size, file_size); result = FileInitializeResult::FILE_SIZE_MISMATCH; goto error_exit; } + if(transfer->file.query_media_bytes && file_size > 0) { + auto new_pos = lseek(file_data.file_descriptor, 0, SEEK_SET); + if(new_pos < 0) { + logWarning(LOG_FT, "{} Failed to seek to file start: {}/{}", transfer->log_prefix(), errno, strerror(errno)); + result = FileInitializeResult::FILE_SEEK_FAILED; + goto error_exit; + } + + auto read = ::read(file_data.file_descriptor, transfer->file.media_bytes, TRANSFER_MEDIA_BYTES_LENGTH); + if(read <= 0) { + logWarning(LOG_FT, "{} Failed to read file media bytes: {}/{}", transfer->log_prefix(), errno, strerror(errno)); + result = FileInitializeResult::FAILED_TO_READ_MEDIA_BYTES; + goto error_exit; + } + transfer->file.media_bytes_length = read; + } } + { auto new_pos = lseek(file_data.file_descriptor, transfer->transfer->file_offset, SEEK_SET); if(new_pos < 0) { @@ -213,7 +263,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr result = FileInitializeResult::FILE_SEEK_FAILED; goto error_exit; } - debugMessage(LOG_FT, "{} Seek to file offset {}. New actual offset is {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos); + logTrace(LOG_FT, "{} Seek to file offset {}. New actual offset is {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos); } return FileInitializeResult::SUCCESS; @@ -231,6 +281,9 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr void LocalFileTransfer::finalize_file_io(const std::shared_ptr &transfer, std::unique_lock &state_lock) { assert(state_lock.owns_lock()); + if(!transfer->transfer) + return; + auto absolute_path = transfer->transfer->absolute_file_path; auto& file_data = transfer->file; @@ -244,26 +297,22 @@ void LocalFileTransfer::finalize_file_io(const std::shared_ptr &tran continue; } - if(file_data.next_client) { - if(this->disk_io.queue_head == &*transfer) { - this->disk_io.queue_head = file_data.next_client; - if(!this->disk_io.queue_head) - this->disk_io.queue_tail = &this->disk_io.queue_head; - } else { - FileClient* head{this->disk_io.queue_head}; - while(head->file.next_client != &*transfer) { - assert(head->file.next_client); - head = head->file.next_client; - } - - head->file.next_client = file_data.next_client; - if(!file_data.next_client) - this->disk_io.queue_tail = &head->file.next_client; - + if(this->disk_io.queue_head == &*transfer) { + this->disk_io.queue_head = file_data.next_client; + if (!this->disk_io.queue_head) + this->disk_io.queue_tail = &this->disk_io.queue_head; + } else if(file_data.next_client || this->disk_io.queue_tail == &file_data.next_client) { + FileClient* head{this->disk_io.queue_head}; + while(head->file.next_client != &*transfer) { + assert(head->file.next_client); + head = head->file.next_client; } - file_data.next_client = nullptr; - } + head->file.next_client = file_data.next_client; + if(!file_data.next_client) + this->disk_io.queue_tail = &head->file.next_client; + } + file_data.next_client = nullptr; break; } } @@ -288,7 +337,7 @@ void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr &clien if(client->state != FileClient::STATE_TRANSFERRING) return; - if(client->buffer.bytes > TRANSFER_MAX_CACHED_BYTES) + if(client->disk_buffer.bytes > TRANSFER_MAX_CACHED_BYTES) return; } else if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) { /* we don't do this check because this might be a flush instruction, where the buffer is actually zero bytes filled */ @@ -317,9 +366,9 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr &clien while(true) { { - std::lock_guard block{client->buffer.mutex}; - buffer = client->buffer.buffer_head; - buffer_left_size = client->buffer.bytes; + std::lock_guard block{client->disk_buffer.mutex}; + buffer = client->disk_buffer.buffer_head; + buffer_left_size = client->disk_buffer.bytes; } if(!buffer) { assert(buffer_left_size == 0); @@ -366,23 +415,23 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr &clien assert(buffer->offset <= buffer->length); if(buffer->length == buffer->offset) { { - std::lock_guard block{client->buffer.mutex}; - client->buffer.buffer_head = buffer->next; + std::lock_guard block{client->disk_buffer.mutex}; + client->disk_buffer.buffer_head = buffer->next; if(!buffer->next) - client->buffer.buffer_tail = &client->buffer.buffer_head; + client->disk_buffer.buffer_tail = &client->disk_buffer.buffer_head; - assert(client->buffer.bytes >= written); - client->buffer.bytes -= written; - buffer_left_size = client->buffer.bytes; + assert(client->disk_buffer.bytes >= written); + client->disk_buffer.bytes -= written; + buffer_left_size = client->disk_buffer.bytes; (void) buffer_left_size; /* trick my IDE here a bit */ } free_buffer(buffer); } else { - std::lock_guard block{client->buffer.mutex}; - assert(client->buffer.bytes >= written); - client->buffer.bytes -= written; - buffer_left_size = client->buffer.bytes; + std::lock_guard block{client->disk_buffer.mutex}; + assert(client->disk_buffer.bytes >= written); + client->disk_buffer.bytes -= written; + buffer_left_size = client->disk_buffer.bytes; (void) buffer_left_size; /* trick my IDE here a bit */ } @@ -390,17 +439,29 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr &clien } } - if(buffer_left_size > 0) + if(buffer_left_size > 0) { this->enqueue_disk_io(client); - else if(client->state == FileClient::STATE_DISCONNECTING) { - debugMessage(LOG_FT, "{} Disk IO has been flushed.", client->log_prefix()); + } else if(client->state == FileClient::STATE_DISCONNECTING) { + { + std::lock_guard nb_lock{client->network_buffer.mutex}; + { + std::lock_guard db_lock{client->disk_buffer.mutex}; + if(std::exchange(client->disk_buffer.flushed, true)) + return; + } + if(!client->network_buffer.flushed) { + logTrace(LOG_FT, "{} Disk IO has been flushed, awaiting network buffer flush.", client->log_prefix()); + return; + } + logTrace(LOG_FT, "{} Disk IO and network buffer have been flushed.", client->log_prefix()); + } std::unique_lock slock{client->state_mutex}; client->handle->disconnect_client(client->shared_from_this(), slock, false); } if(client->state == FileClient::STATE_TRANSFERRING && buffer_left_size < TRANSFER_MAX_CACHED_BYTES / 2) { - if(client->buffer.buffering_stopped) + if(client->disk_buffer.buffering_stopped) logMessage(LOG_FT, "{} Starting network read, buffer is capable for reading again.", client->log_prefix()); client->add_network_read_event(false); } diff --git a/file/local_server/LocalFileTransferNetwork.cpp b/file/local_server/LocalFileTransferNetwork.cpp index 8f8e9bf..5605dc6 100644 --- a/file/local_server/LocalFileTransferNetwork.cpp +++ b/file/local_server/LocalFileTransferNetwork.cpp @@ -7,8 +7,11 @@ #include #include #include +#include +#include #include "./LocalFileProvider.h" #include "./duration_utils.h" +#include "HTTPUtils.h" #if defined(TCP_CORK) && !defined(TCP_NOPUSH) #define TCP_NOPUSH TCP_CORK @@ -88,8 +91,6 @@ void FileClient::add_network_write_event_nolock(bool ignore_bandwidth) { return; case STATE_DISCONNECTING: - if(this->transfer && this->transfer->direction == Transfer::DIRECTION_UPLOAD) - return; /* flush our write buffer */ break; @@ -113,16 +114,16 @@ void FileClient::add_network_write_event_nolock(bool ignore_bandwidth) { bool FileClient::send_file_bytes(const void *snd_buffer, size_t size) { if(this->networking.protocol == FileClient::PROTOCOL_TS_V1) { - return this->enqueue_buffer_bytes(snd_buffer, size); + return this->enqueue_network_buffer_bytes(snd_buffer, size); } else if(this->networking.protocol == FileClient::PROTOCOL_HTTPS) { this->networking.pipe_ssl.send(pipes::buffer_view{snd_buffer, size}); - return this->buffer.bytes > TRANSFER_MAX_CACHED_BYTES; + return this->network_buffer.bytes > TRANSFER_MAX_CACHED_BYTES; } else { return false; } } -bool FileClient::enqueue_buffer_bytes(const void *snd_buffer, size_t size) { +bool FileClient::enqueue_network_buffer_bytes(const void *snd_buffer, size_t size) { auto tbuffer = allocate_buffer(size); tbuffer->length = size; tbuffer->offset = 0; @@ -130,11 +131,11 @@ bool FileClient::enqueue_buffer_bytes(const void *snd_buffer, size_t size) { size_t buffer_size; { - std::lock_guard block{this->buffer.mutex}; - *this->buffer.buffer_tail = tbuffer; - this->buffer.buffer_tail = &tbuffer->next; + std::lock_guard block{this->network_buffer.mutex}; + *this->network_buffer.buffer_tail = tbuffer; + this->network_buffer.buffer_tail = &tbuffer->next; - buffer_size = (this->buffer.bytes += size); + buffer_size = (this->network_buffer.bytes += size); } this->add_network_write_event(false); @@ -341,11 +342,21 @@ bool LocalFileTransfer::initialize_client_ssl(const std::shared_ptr auto& ssl_pipe = client->networking.pipe_ssl; - if(!ssl_pipe.initialize(this->ssl_options_, error)) { + std::shared_ptr options{}; + auto ssl_option_supplier = config::ssl_option_supplier; + if(!ssl_option_supplier || !(options = ssl_option_supplier())) { + logError(0, "{} Failed to initialize client SSL pipe because we've no SSL options.", client->log_prefix()); + + std::unique_lock slock{client->state_mutex}; + client->handle->disconnect_client(client, slock, false); + return false; + } + + if(!ssl_pipe.initialize(options, error)) { logWarning(0, "{} Failed to initialize client SSL pipe ({}). Disconnecting client.", client->log_prefix(), error); std::unique_lock slock{client->state_mutex}; - client->handle->disconnect_client(client->shared_from_this(), slock, false); + client->handle->disconnect_client(client, slock, false); return false; } @@ -369,11 +380,11 @@ bool LocalFileTransfer::initialize_client_ssl(const std::shared_ptr logMessage(LOG_FT, "{} Received SSL error ({}/{}). Closing connection.", client->log_prefix(), error, error_detail); std::unique_lock slock{client->state_mutex}; - client->handle->disconnect_client(client->shared_from_this(), slock, false); + client->handle->disconnect_client(client, slock, false); }); ssl_pipe.callback_write([client](const pipes::buffer_view& buffer) { - client->enqueue_buffer_bytes(buffer.data_ptr(), buffer.length()); + client->enqueue_network_buffer_bytes(buffer.data_ptr(), buffer.length()); client->add_network_write_event(false); }); @@ -549,7 +560,7 @@ void LocalFileTransfer::callback_transfer_network_read(int fd, short events, voi break; if(bytes_buffered > TRANSFER_MAX_CACHED_BYTES) { - transfer->buffer.buffering_stopped = true; + transfer->network_buffer.buffering_stopped = true; debugMessage(LOG_FT, "{} Stopping network read, temp buffer full.", transfer->log_prefix()); return; /* no read event readd, buffer filled */ } @@ -568,12 +579,23 @@ void LocalFileTransfer::callback_transfer_network_write(int fd, short events, vo if((unsigned) events & (unsigned) EV_TIMEOUT) { if(transfer->state == FileClient::STATE_DISCONNECTING) { + { + std::lock_guard nb_lock{transfer->network_buffer.mutex}; + std::lock_guard db_lock{transfer->disk_buffer.mutex}; + if(!std::exchange(transfer->network_buffer.flushed, true)) { + debugMessage(LOG_FT, "{} Failed to flush networking buffer in given timeout. Marking it as flushed.", transfer->log_prefix()); + } + if(!transfer->disk_buffer.flushed) { + logTrace(LOG_FT, "{} Disk IO hasn't been fully flushed yet, awaiting disk IO flush.", transfer->log_prefix()); + return; + } + logTrace(LOG_FT, "{} Disk IO and network buffer have been flushed.", transfer->log_prefix()); + } + { std::unique_lock slock{transfer->state_mutex}; transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false); } - - logMessage(LOG_FT, "{} Flush timeout. Force closing connection.", transfer->log_prefix()); return; } } @@ -583,9 +605,9 @@ void LocalFileTransfer::callback_transfer_network_write(int fd, short events, vo if(!(transfer->state == FileClient::STATE_AWAITING_KEY && transfer->networking.protocol == FileClient::PROTOCOL_HTTPS)) { debugMessage(LOG_FT, "{} Tried to write data to send only stream. Dropping buffers.", transfer->log_prefix()); - std::unique_lock block{transfer->buffer.mutex}; - auto head = std::exchange(transfer->buffer.buffer_head, nullptr); - transfer->buffer.buffer_tail = &transfer->buffer.buffer_head; + std::unique_lock block{transfer->network_buffer.mutex}; + auto head = std::exchange(transfer->network_buffer.buffer_head, nullptr); + transfer->network_buffer.buffer_tail = &transfer->network_buffer.buffer_head; while(head) { auto next = head->next; @@ -601,9 +623,9 @@ void LocalFileTransfer::callback_transfer_network_write(int fd, short events, vo while(true) { { - std::lock_guard block{transfer->buffer.mutex}; - buffer = transfer->buffer.buffer_head; - buffer_left_size = transfer->buffer.bytes; + std::lock_guard block{transfer->network_buffer.mutex}; + buffer = transfer->network_buffer.buffer_head; + buffer_left_size = transfer->network_buffer.bytes; } if(!buffer) { break; @@ -618,7 +640,7 @@ void LocalFileTransfer::callback_transfer_network_write(int fd, short events, vo if(written == 0) { /* EOF, how the hell is this event possible?! (Read should already catch it) */ logError(LOG_FT, "{} Client disconnected unexpectedly on write. Send {} bytes out of {}.", - transfer->log_prefix(), transfer->statistics.file_transferred.total_bytes, transfer->transfer->expected_file_size - transfer->transfer->file_offset); + transfer->log_prefix(), transfer->statistics.file_transferred.total_bytes, transfer->transfer ? transfer->transfer->expected_file_size - transfer->transfer->file_offset : -1); transfer->handle->invoke_aborted_callback(transfer->shared_from_this(), { TransferError::UNEXPECTED_CLIENT_DISCONNECT, "" }); { @@ -632,7 +654,7 @@ void LocalFileTransfer::callback_transfer_network_write(int fd, short events, vo } logError(LOG_FT, "{} Received network write error. Send {} bytes out of {}. Closing transfer.", - transfer->log_prefix(), transfer->statistics.file_transferred.total_bytes, transfer->transfer->expected_file_size - transfer->transfer->file_offset); + transfer->log_prefix(), transfer->statistics.file_transferred.total_bytes, transfer->transfer ? transfer->transfer->expected_file_size - transfer->transfer->file_offset : -1); transfer->handle->invoke_aborted_callback(transfer->shared_from_this(), { TransferError::NETWORK_IO_ERROR, strerror(errno) }); { @@ -646,21 +668,21 @@ void LocalFileTransfer::callback_transfer_network_write(int fd, short events, vo assert(buffer->offset <= buffer->length); if(buffer->length == buffer->offset) { { - std::lock_guard block{transfer->buffer.mutex}; - transfer->buffer.buffer_head = buffer->next; + std::lock_guard block{transfer->network_buffer.mutex}; + transfer->network_buffer.buffer_head = buffer->next; if(!buffer->next) - transfer->buffer.buffer_tail = &transfer->buffer.buffer_head; - assert(transfer->buffer.bytes >= written); - transfer->buffer.bytes -= written; - buffer_left_size = transfer->buffer.bytes; + transfer->network_buffer.buffer_tail = &transfer->network_buffer.buffer_head; + assert(transfer->network_buffer.bytes >= written); + transfer->network_buffer.bytes -= written; + buffer_left_size = transfer->network_buffer.bytes; } free_buffer(buffer); } else { - std::lock_guard block{transfer->buffer.mutex}; - assert(transfer->buffer.bytes >= written); - transfer->buffer.bytes -= written; - buffer_left_size = transfer->buffer.bytes; + std::lock_guard block{transfer->network_buffer.mutex}; + assert(transfer->network_buffer.bytes >= written); + transfer->network_buffer.bytes -= written; + buffer_left_size = transfer->network_buffer.bytes; } transfer->timings.last_write = std::chrono::system_clock::now(); @@ -674,13 +696,27 @@ void LocalFileTransfer::callback_transfer_network_write(int fd, short events, vo if(buffer_left_size > 0) transfer->add_network_write_event(false); else if(transfer->state == FileClient::STATE_DISCONNECTING) { - if(transfer->transfer && transfer->statistics.file_transferred.total_bytes + transfer->transfer->file_offset == transfer->transfer->expected_file_size) { - logMessage(LOG_FT, "{} Finished file transfer within {}. Closing connection.", transfer->log_prefix(), duration_to_string(std::chrono::system_clock::now() - transfer->timings.key_received)); - transfer->handle->report_transfer_statistics(transfer->shared_from_this()); - if(auto callback{transfer->handle->callback_transfer_finished}; callback) - callback(transfer->transfer); - } else { - debugMessage(LOG_FT, "{} Flushed output buffer.", transfer->log_prefix()); + { + std::lock_guard nb_lock{transfer->network_buffer.mutex}; + std::lock_guard db_lock{transfer->disk_buffer.mutex}; + if(std::exchange(transfer->network_buffer.flushed, true)) + return; + + if(!transfer->disk_buffer.flushed) { + debugMessage(LOG_FT, "{} Disk IO hasn't been fully flushed yet, awaiting disk IO flush.", transfer->log_prefix()); + return; + } + + debugMessage(LOG_FT, "{} Disk IO and network buffer have been flushed.", transfer->log_prefix()); + } + + if(!std::exchange(transfer->finished_signal_send, true)) { + if(transfer->transfer && transfer->statistics.file_transferred.total_bytes + transfer->transfer->file_offset == transfer->transfer->expected_file_size) { + logMessage(LOG_FT, "{} Finished file transfer within {}. Closing connection.", transfer->log_prefix(), duration_to_string(std::chrono::system_clock::now() - transfer->timings.key_received)); + transfer->handle->report_transfer_statistics(transfer->shared_from_this()); + if(auto callback{transfer->handle->callback_transfer_finished}; callback) + callback(transfer->transfer); + } } std::unique_lock slock{transfer->state_mutex}; transfer->handle->disconnect_client(transfer->shared_from_this(), slock, false); @@ -705,7 +741,7 @@ size_t LocalFileTransfer::handle_transfer_read_raw(const std::shared_ptrhandle_transfer_read(client, buffer, length); } else if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) { client->networking.pipe_ssl.process_incoming_data(pipes::buffer_view{buffer, length}); - return client->buffer.bytes; + return client->network_buffer.bytes; } else if(client->networking.protocol != FileClient::PROTOCOL_UNKNOWN) { assert(false); logWarning(LOG_FT, "{} Read bytes with unknown protocol. Closing connection.", client->log_prefix()); @@ -751,7 +787,7 @@ size_t LocalFileTransfer::handle_transfer_read_raw(const std::shared_ptrnetworking.pipe_ssl.process_incoming_data(pipes::buffer_view{first_bytes, TRANSFER_KEY_LENGTH}); if(length > 0) client->networking.pipe_ssl.process_incoming_data(pipes::buffer_view{buffer, length}); - return client->buffer.bytes; + return client->network_buffer.bytes; } else { client->networking.protocol = FileClient::PROTOCOL_TS_V1; debugMessage(LOG_FT, "{} Using protocol RAWv1 for file transfer.", client->log_prefix()); @@ -760,8 +796,12 @@ size_t LocalFileTransfer::handle_transfer_read_raw(const std::shared_ptrhandle_transfer_key_provided(client, error_detail); switch(key_result) { case TransferKeyApplyResult::SUCCESS: - if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) + if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) { + logMessage(LOG_FT, "{} Successfully initialized file download for file {}.", client->log_prefix(), client->transfer->absolute_file_path); this->enqueue_disk_io(client); /* we've to take initiative */ + } else { + logMessage(LOG_FT, "{} Successfully upload file download to file {}.", client->log_prefix(), client->transfer->absolute_file_path); + } return length ? this->handle_transfer_read(client, buffer, length) : 0; @@ -830,28 +870,49 @@ size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr return (size_t) -1; } - const auto transfer_key_header = request.findHeader("transfer-key"); - if(!transfer_key_header || transfer_key_header.values.empty()) { - logMessage(0, "{} Missing transfer key header. Disconnecting client.", client->log_prefix()); - response.code = http::code::code(510, "Not Extended"); + if(auto header = request.findHeader("Sec-Fetch-Mode"); request.method == "OPTIONS") { + logMessage(0, "{} Received options request (probably due to cors). Sending allow response and disconnecting client.", client->log_prefix()); + response.code = http::code::_200; + goto send_response_exit; + } + +#if 0 + std::map query{}; + if(!http::parse_url_parameters(request.url, query)) { + logMessage(0, "{} Received request but missing URL parameters ({})", client->log_prefix(), request.url); + response.code = http::code::code(400, "Bad Request"); + goto send_response_exit; + } +#endif + + std::string transfer_key{}; + if(request.parameters.count("transfer-key") == 0 || (transfer_key = request.parameters.at("transfer-key")).empty()) { + logMessage(0, "{} Missing transfer key parameter. Disconnecting client.", client->log_prefix()); + response.code = http::code::code(400, "Bad Request"); response.setHeader("x-error-message", { "missing transfer key" }); goto send_response_exit; } - const auto& transfer_key = transfer_key_header.values[0]; if(transfer_key.length() != TRANSFER_KEY_LENGTH) { logMessage(0, "{} Received too short/long transfer key. Expected {} but received {}. Disconnecting client.", client->log_prefix(), TRANSFER_KEY_LENGTH, transfer_key.length()); - response.code = http::code::code(510, "Not Extended"); + response.code = http::code::code(400, "Bad Request"); response.setHeader("x-error-message", { "key too short/long" }); goto send_response_exit; } client->transfer_key.provided_bytes = TRANSFER_KEY_LENGTH; memcpy(client->transfer_key.key, transfer_key.data(), TRANSFER_KEY_LENGTH); + client->file.query_media_bytes = true; + std::string error_detail{}; auto key_result = this->handle_transfer_key_provided(client, error_detail); switch(key_result) { case TransferKeyApplyResult::SUCCESS: + if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) { + logMessage(LOG_FT, "{} Successfully initialized file download for file {}.", client->log_prefix(), client->transfer->absolute_file_path); + } else { + logMessage(LOG_FT, "{} Successfully upload file download to file {}.", client->log_prefix(), client->transfer->absolute_file_path); + } break; case TransferKeyApplyResult::FILE_ERROR: @@ -885,25 +946,18 @@ size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr response.setHeader("Content-type", {"application/octet-stream; "}); response.setHeader("Content-Transfer-Encoding", {"binary"}); + response.setHeader("Content-Disposition", { - "attachment; filename=\"" + http::encode_url(download_name && !download_name.values.empty() ? download_name.values[0] : "TeaWeb Download") + "\"" + "attachment; filename=\"" + http::encode_url(request.parameters.count("download-name") > 0 ? request.parameters.at("download-name") : client->transfer->file_name) + "\"" }); - /* TODO: X-media-bytes */ -#if 0 - if(this->pendingKey->size > 1) { - char header_buffer[64]; - auto read = fstream->readsome(header_buffer, 64); - if(read > 0) - response.setHeader("X-media-bytes", {base64::encode(header_buffer, read)}); - fstream->seekg(this->pendingKey->offset, std::ios::beg); - } -#endif + response.setHeader("X-media-bytes", { base64::encode((char*) client->file.media_bytes, client->file.media_bytes_length) }); client->networking.http_state = FileClient::HTTP_STATE_DOWNLOADING; goto send_response_exit; } else { - response.setHeader("Content-Length", { std::to_string(client->transfer->expected_file_size) }); + /* we're sending a HTTP response later */ client->networking.http_state = FileClient::HTTP_STATE_AWAITING_BOUNDARY; + goto initialize_exit; } overhead_length = header_buffer->offset - header_end - header_end_token.length(); @@ -912,12 +966,13 @@ size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr send_response_exit: this->send_http_response(client, response); - if(response.code->code != 200) { + if(response.code->code != 200 || !client->transfer) { std::unique_lock slock{client->state_mutex}; client->handle->disconnect_client(client->shared_from_this(), slock, true); return (size_t) -1; } + initialize_exit: if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) this->enqueue_disk_io(client); /* we've to take initiative */ @@ -941,17 +996,22 @@ size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr std::string error_message{}; const auto upload_result = client->handle->handle_transfer_upload_http(client, buffer, length); switch(upload_result) { - case TransferUploadHTTPResult::FINISH: {this->report_transfer_statistics(client); + case TransferUploadHTTPResult::FINISH: { + assert(!client->finished_signal_send); /* we should be faster than the networking flush */ + client->finished_signal_send = true; + logMessage(LOG_FT, "{} File upload has been completed in {}. Disconnecting client.", client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received)); + + this->report_transfer_statistics(client); if(auto callback{this->callback_transfer_finished}; callback) callback(client->transfer); std::unique_lock slock{client->state_mutex}; client->handle->disconnect_client(client->shared_from_this(), slock, true); - return client->buffer.bytes; /* a bit unexact but the best we could get away with it */ + return client->network_buffer.bytes; /* a bit unexact but the best we could get away with it */ } case TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE: - return client->buffer.bytes; /* a bit unexact but the best we could get away with it */ + return client->network_buffer.bytes; /* a bit unexact but the best we could get away with it */ case TransferUploadHTTPResult::MISSING_CONTENT_TYPE: logMessage(LOG_FT, "{} Missing boundary content type. Disconnecting client.", client->log_prefix()); @@ -990,10 +1050,19 @@ size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr return (size_t) -1; } else if(client->networking.protocol == FileClient::PROTOCOL_TS_V1) { - auto result = this->handle_transfer_upload_raw(client, buffer, length); + size_t written_bytes{0}; + auto result = this->handle_transfer_upload_raw(client, buffer, length, &written_bytes); switch (result) { - case TransferUploadRawResult::FINISH: {this->report_transfer_statistics(client); + case TransferUploadRawResult::FINISH_OVERFLOW: + case TransferUploadRawResult::FINISH: { + assert(!client->finished_signal_send); /* we should be faster than the networking flush */ + client->finished_signal_send = true; + if(result == TransferUploadRawResult::FINISH_OVERFLOW) + logMessage(LOG_FT, "{} Client send {} too many bytes (Transfer length was {}). Dropping them, flushing the disk IO and closing the transfer.", client->log_prefix(), length - written_bytes, duration_to_string(std::chrono::system_clock::now() - client->timings.key_received)); + else + logMessage(LOG_FT, "{} File upload has been completed in {}. Flushing disk IO and closing the connection.", client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received)); + this->report_transfer_statistics(client); if(auto callback{this->callback_transfer_finished}; callback) callback(client->transfer); @@ -1003,7 +1072,7 @@ size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr } case TransferUploadRawResult::MORE_DATA_TO_RECEIVE: - return client->buffer.bytes; /* a bit unexact but the best we could get away with it */ + return client->network_buffer.bytes; /* a bit unexact but the best we could get away with it */ } } else { logWarning(LOG_FT, "{} Read message for client with unknown protocol. Dropping {} bytes.", client->log_prefix(), length); @@ -1069,36 +1138,25 @@ TransferKeyApplyResult LocalFileTransfer::handle_transfer_key_provided(const std return TransferKeyApplyResult::SUCCESS; } -TransferUploadRawResult LocalFileTransfer::handle_transfer_upload_raw(const std::shared_ptr &client, const char *buffer, size_t length) { - client->statistics.file_transferred.increase_bytes(length); +TransferUploadRawResult LocalFileTransfer::handle_transfer_upload_raw(const std::shared_ptr &client, const char *buffer, size_t length, size_t* bytesWritten) { + auto write_length = length; + auto write_offset = client->statistics.file_transferred.total_bytes + client->transfer->file_offset; + TransferUploadRawResult result{TransferUploadRawResult::MORE_DATA_TO_RECEIVE}; - bool transfer_finished{false}; - auto writte_offset = client->statistics.file_transferred.total_bytes + client->transfer->file_offset; - if(writte_offset > client->transfer->expected_file_size) { - logMessage(LOG_FT, "{} Client send {} too many bytes (Transfer length was {}). Dropping them, flushing the disk IO and closing the transfer.", client->log_prefix(), writte_offset - client->transfer->expected_file_size, duration_to_string(std::chrono::system_clock::now() - client->timings.key_received)); - length -= writte_offset - client->transfer->expected_file_size; - - transfer_finished = true; - } else if(writte_offset == client->transfer->expected_file_size) { - logMessage(LOG_FT, "{} File upload has been completed in {}. Flushing disk IO and closing the connection.", client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received)); - transfer_finished = true; - } - - auto tbuffer = allocate_buffer(length); - tbuffer->offset = 0; - tbuffer->length = length; - memcpy(tbuffer->data, buffer, length); - - { - std::lock_guard block{client->buffer.mutex}; - *client->buffer.buffer_tail = tbuffer; - client->buffer.buffer_tail = &tbuffer->next; - client->buffer.bytes += length; + if(write_offset + write_length > client->transfer->expected_file_size) { + result = TransferUploadRawResult::FINISH_OVERFLOW; + write_length = client->transfer->expected_file_size - write_offset; + } else if(write_offset == client->transfer->expected_file_size) { + result = TransferUploadRawResult::FINISH; } + client->statistics.file_transferred.increase_bytes(write_length); + client->enqueue_disk_buffer_bytes(buffer, write_length); this->enqueue_disk_io(client); + if(bytesWritten) + *bytesWritten = write_length; - return transfer_finished ? TransferUploadRawResult::FINISH : TransferUploadRawResult::MORE_DATA_TO_RECEIVE; + return result; } //Example boundary: @@ -1120,19 +1178,21 @@ TransferUploadHTTPResult LocalFileTransfer::handle_transfer_upload_http(const st memcpy(boundary_buffer->data + boundary_buffer->offset, buffer, length); boundary_buffer->offset += length; - auto boundary_view = std::string_view{boundary_buffer->data, boundary_buffer->offset}; - auto boundary_end = boundary_view.find(boundary_end_token, old_offset > 3 ? old_offset - 3 : 0); + auto buffer_view = std::string_view{boundary_buffer->data, boundary_buffer->offset}; + auto boundary_end = buffer_view.find(boundary_end_token, old_offset > 3 ? old_offset - 3 : 0); if(boundary_end == std::string::npos) return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE; - auto boundary_token_end = boundary_view.find(boundary_token_end_token); - if(boundary_token_end >= boundary_end) + auto boundary_token_end = buffer_view.find(boundary_token_end_token); + if(boundary_token_end + boundary_token_end_token.size() >= boundary_end) return TransferUploadHTTPResult::BOUNDARY_TOKEN_INVALID; - const auto boundary_token = boundary_view.substr(0, boundary_token_end); - debugMessage(LOG_FT, "{} Received clients HTTP file boundary ({}).", client->log_prefix(), boundary_token); + const auto boundary_token = buffer_view.substr(0, boundary_token_end); + client->networking.http_boundary = boundary_token; + logTrace(LOG_FT, "{} Received clients HTTP file boundary ({}).", client->log_prefix(), boundary_token); - const auto boundary_payload = boundary_view.substr(boundary_token_end + boundary_token_end_token.size()); + const auto boundary_header_offset = boundary_token_end + boundary_token_end_token.size(); + const auto boundary_payload = buffer_view.substr(boundary_header_offset, boundary_end - boundary_header_offset); http::HttpRequest boundary{}; if(!http::parse_request(std::string{boundary_payload}, boundary)) @@ -1141,8 +1201,10 @@ TransferUploadHTTPResult LocalFileTransfer::handle_transfer_upload_http(const st const auto content_type = boundary.findHeader("Content-Type"); if(!content_type || content_type.values.empty()) return TransferUploadHTTPResult::MISSING_CONTENT_TYPE; + /* A bit relaxed here else if(content_type.values[0] != "application/octet-stream") return TransferUploadHTTPResult::INVALID_CONTENT_TYPE; + */ const auto overhead_length = boundary_buffer->offset - boundary_end - boundary_end_token.length(); const auto overhead_data_ptr = boundary_buffer->data + boundary_end + boundary_end_token.length(); @@ -1151,19 +1213,74 @@ TransferUploadHTTPResult LocalFileTransfer::handle_transfer_upload_http(const st boundary_buffer->offset = 0; return overhead_length == 0 ? TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE : this->handle_transfer_upload_http(client, overhead_data_ptr, overhead_length); } else if(client->networking.http_state == FileClient::HTTP_STATE_UPLOADING) { - auto result = this->handle_transfer_upload_raw(client, buffer, length); + size_t bytes_written{0}; + auto result = this->handle_transfer_upload_raw(client, buffer, length, &bytes_written); switch(result) { case TransferUploadRawResult::MORE_DATA_TO_RECEIVE: return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE; + case TransferUploadRawResult::FINISH_OVERFLOW: case TransferUploadRawResult::FINISH: - /* TODO: Try to read the end boundary! */ - return TransferUploadHTTPResult::FINISH; + debugMessage(LOG_FT, "{} File upload has been completed in {}. Awaiting file end boundary.", client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received)); + client->networking.http_state = FileClient::HTTP_STATE_AWAITING_BOUNDARY_END; + + if(length != bytes_written) + return this->handle_transfer_upload_http(client, buffer + bytes_written, length - bytes_written); + return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE; default: assert(false); return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE; } + } else if(client->networking.http_state == FileClient::HTTP_STATE_AWAITING_BOUNDARY_END) { + assert(client->networking.http_header_buffer); + + /* Notice: The buffer ptr might be some data within our header buffer! But since its somewhere in the back its okey */ + auto boundary_buffer = &*client->networking.http_header_buffer; + if(boundary_buffer->offset + length > boundary_buffer->capacity) + return TransferUploadHTTPResult::BOUNDARY_MISSING; + + memcpy(boundary_buffer->data + boundary_buffer->offset, buffer, length); + boundary_buffer->offset += length; + + const auto expected_boundary_size = 2 + client->networking.http_boundary.size() + 4; + if(boundary_buffer->offset < expected_boundary_size) + return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE; + + if(memcmp(boundary_buffer->data, "\r\n", 2) != 0) { + debugMessage(LOG_FT, "{} File boundary seems invalid/miss matching. Expected \\r\\n at {}.", client->log_prefix(), 0); + goto callback_exit; + } + + if(memcmp(boundary_buffer->data + 2 + client->networking.http_boundary.size(), "--\r\n", 4) != 0) { + debugMessage(LOG_FT, "{} File boundary seems invalid/miss matching. Expected --\\r\\n at {}.", client->log_prefix(), 2 + client->networking.http_boundary.size()); + goto callback_exit; + } + + if(memcmp(boundary_buffer->data + 2, client->networking.http_boundary.data(), client->networking.http_boundary.size()) != 0) { + debugMessage(LOG_FT, "{} File upload has been completed but end boundary does not match ({} != {})! Ignoring miss match and finishing transfer", client->log_prefix(), + std::string_view{boundary_buffer->data + 2, client->networking.http_boundary.size()}, + client->networking.http_boundary + ); + + goto callback_exit; + } + + if(boundary_buffer->offset > expected_boundary_size) { + debugMessage(LOG_FT, "{} File boundary has been received, but received {} more unexpected bytes. Ignoring these and finishing the upload.", + client->log_prefix(), boundary_buffer->offset - expected_boundary_size); + } else { + debugMessage(LOG_FT, "{} File boundary has been received.", client->log_prefix()); + } + + callback_exit: + /* send a proper HTTP response */ + { + http::HttpResponse response{}; + response.code = http::code::_200; + this->send_http_response(client, response); + }; + return TransferUploadHTTPResult::FINISH; } else { logWarning(0, "{} Received HTTP(S) data, for an invalid HTTP state ({}).", client->log_prefix(), (int) client->networking.http_state); return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE; @@ -1174,7 +1291,12 @@ inline void apply_cors_and_connection_headers(http::HttpResponse &response) { response.setHeader("Connection", {"close"}); /* close the connection instance, we dont want multiple requests */ response.setHeader("Access-Control-Allow-Methods", {"GET, POST"}); response.setHeader("Access-Control-Allow-Origin", {"*"}); - response.setHeader("Access-Control-Allow-Headers", response.findHeader("Access-Control-Request-Headers").values); //access-control-allow-headers + + auto requestHeaders = response.findHeader("Access-Control-Request-Headers").values; + if(requestHeaders.empty()) { + requestHeaders.emplace_back("*"); + } + response.setHeader("Access-Control-Allow-Headers", requestHeaders); //access-control-allow-headers response.setHeader("Access-Control-Max-Age", {"86400"}); } diff --git a/file/test/main.cpp b/file/test/main.cpp index 77c328e..aa45933 100644 --- a/file/test/main.cpp +++ b/file/test/main.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace fs = std::experimental::filesystem; @@ -55,8 +57,50 @@ inline void print_query(const std::string& message, const file::filesystem::Abst logWarning(LOG_FT, "{}: Unknown response state ({})!", message, (int) response.status); } +EVP_PKEY* ssl_generate_key() { + auto key = std::unique_ptr(EVP_PKEY_new(), ::EVP_PKEY_free); + + auto rsa = RSA_new(); + auto e = std::unique_ptr(BN_new(), ::BN_free); + BN_set_word(e.get(), RSA_F4); + if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return nullptr; + EVP_PKEY_assign_RSA(key.get(), rsa); + return key.release(); +} + +X509* ssl_generate_certificate(EVP_PKEY* key) { + auto cert = X509_new(); + X509_set_pubkey(cert, key); + + ASN1_INTEGER_set(X509_get_serialNumber(cert), 3); + X509_gmtime_adj(X509_get_notBefore(cert), 0); + X509_gmtime_adj(X509_get_notAfter(cert), 31536000L); + + X509_NAME* name = nullptr; + name = X509_get_subject_name(cert); + //for(const auto& subject : this->subjects) + // X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0); + X509_set_subject_name(cert, name); + + name = X509_get_issuer_name(cert); + //for(const auto& subject : this->issues) + // X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0); + + X509_set_issuer_name(cert, name); + + X509_sign(cert, key, EVP_sha512()); + return cert; +} + int main() { evthread_use_pthreads(); + { + std::map query{}; + http::parse_url_parameters("http://www.example.org/suche?stichwort=wiki&no-arg&1arg=&ausgabe=liste&test=test#bla=d&blub=c", query); + for(const auto& [key, value] : query) + std::cout << key << " => " << value << std::endl; + return 0; + } auto log_config = std::make_shared(); log_config->terminalLevel = spdlog::level::trace; @@ -71,6 +115,23 @@ int main() { auto instance = file::server(); + { + auto options = std::make_shared(); + options->verbose_io = true; + options->context_method = SSLv23_method(); + options->free_unused_keypairs = false; + + { + std::shared_ptr pkey{ssl_generate_key(), ::EVP_PKEY_free}; + std::shared_ptr cert{ssl_generate_certificate(&*pkey), ::X509_free}; + + options->default_keypair({pkey, cert}); + } + file::config::ssl_option_supplier = [options]{ + return options; + }; + } + #if 0 auto& fs = instance->file_system(); { @@ -140,7 +201,7 @@ int main() { } #endif -#if 1 +#if 0 auto& ft = instance->file_transfer(); ft.callback_transfer_finished = [](const std::shared_ptr& transfer) { @@ -151,7 +212,7 @@ int main() { logMessage(0, "Transfer started"); }; - ft.callback_transfer_aborted = [](const std::shared_ptr& transfer, const file::transfer::TransferError& error) { + ft.callback_transfer_aborted = [](const std::shared_ptr& transfer, const transfer::TransferStatistics&, const file::transfer::TransferError& error) { logMessage(0, "Transfer aborted ({}/{})", (int) error.error_type, error.error_message); };