Some bug fixes and final version before release

This commit is contained in:
WolverinDEV 2020-06-10 18:13:14 +02:00
parent bfdf940dbf
commit 9e964b3ea8
14 changed files with 577 additions and 262 deletions

View File

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

View File

@ -0,0 +1,13 @@
#pragma once
#include <memory>
#include <pipes/ssl.h>
namespace ts::server::file::config {
enum struct Key {
SSL_OPTION_SUPPLIER
};
extern void value_updated(Key /* value */);
extern std::function<std::shared_ptr<pipes::SSL::Options>()> ssl_option_supplier;
}

View File

@ -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<ExecuteResponse<ServerCommandError>> delete_server(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
/* channels */
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_channel_info(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::vector<std::string>& /* names */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_channel_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* files */) = 0;
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<DirectoryModifyError>> create_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_channel_files(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::vector<std::string>& /* 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 {

View File

@ -0,0 +1,13 @@
//
// Created by WolverinDEV on 21/05/2020.
//
#include "files/Config.h"
using namespace ts::server::file;
std::function<std::shared_ptr<pipes::SSL::Options>()> config::ssl_option_supplier{nullptr};
void config::value_updated(ts::server::file::config::Key) {
/* we currently do nothing */
}

View File

@ -0,0 +1,35 @@
//
// Created by WolverinDEV on 22/05/2020.
//
#include <pipes/misc/http.h>
#include "HTTPUtils.h"
bool http::parse_url_parameters(const std::string_view &query, std::map<std::string, std::string>& 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;
}

View File

@ -0,0 +1,7 @@
#pragma once
#include <map>
namespace http {
bool parse_url_parameters(const std::string_view& /* url */, std::map<std::string, std::string>& /* result */);
}

View File

@ -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, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
auto rsa = RSA_new();
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(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<LocalFileServer> server_instance{};
bool file::initialize(std::string &error) {
server_instance = std::make_shared<LocalFileProvider>();
auto options = std::make_shared<pipes::SSL::Options>();
options->verbose_io = true;
options->context_method = SSLv23_method();
options->free_unused_keypairs = false;
{
std::shared_ptr<EVP_PKEY> pkey{ssl_generate_key(), ::EVP_PKEY_free};
std::shared_ptr<X509> 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::AbstractFileServer> file::server() {
LocalFileServer::LocalFileProvider() : file_system_{}, file_transfer_{this->file_system_} {}
LocalFileServer::~LocalFileProvider() {}
bool LocalFileServer::initialize(std::string &error, const std::shared_ptr<pipes::SSL::Options>& 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_ptr<pipes
bindings.push_back(std::move(binding));
}
if(!this->file_transfer_.start(bindings, ssl_options)) {
if(!this->file_transfer_.start(bindings)) {
error = "transfer server startup failed";
this->file_system_.finalize();
return false;

View File

@ -49,7 +49,7 @@ namespace ts::server::file {
std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(const std::shared_ptr<VirtualFileServer> & /* server */) override;
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_channel_info(const std::shared_ptr<VirtualFileServer> & /* server */, ChannelId /* channel */, const std::vector<std::string>& /* names */) override;
query_channel_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* names */) override;
std::shared_ptr<directory_query_response_t>
query_channel_directory(const std::shared_ptr<VirtualFileServer> & 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<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_file_info(const fs::path& /* base */, const std::vector<std::string> &string);
query_file_info(const std::vector<std::tuple<fs::path, std::string>> &string);
#endif
template <typename error_t, typename result_t = EmptyExecuteResponse>
@ -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<Buffer, decltype(free_buffer)*> 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<std::shared_ptr<NetworkBinding>>& /* bindings */, const std::shared_ptr<pipes::SSL::Options>& /* ssl options */);
[[nodiscard]] bool start(const std::deque<std::shared_ptr<NetworkBinding>>& /* bindings */);
void stop();
[[nodiscard]] inline const auto& ssl_options() const {
return this->ssl_options_;
}
inline void set_ssl_options(const std::shared_ptr<pipes::SSL::Options>& options) {
this->ssl_options_ = options;
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_channel_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, ChannelId channelId,
const TransferInfo &info) override;
@ -393,8 +414,6 @@ namespace ts::server::file {
std::deque<std::shared_ptr<FileClient>> transfers_{};
std::deque<std::shared_ptr<Transfer>> pending_transfers{};
std::shared_ptr<pipes::SSL::Options> ssl_options_{};
enum ServerState {
STOPPED,
RUNNING
@ -458,7 +477,7 @@ namespace ts::server::file {
void enqueue_disk_io(const std::shared_ptr<FileClient>& /* client */);
void execute_disk_io(const std::shared_ptr<FileClient>& /* client */);
[[nodiscard]] TransferUploadRawResult handle_transfer_upload_raw(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */);
[[nodiscard]] TransferUploadRawResult handle_transfer_upload_raw(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */, size_t* /* bytes written */);
[[nodiscard]] TransferUploadHTTPResult handle_transfer_upload_http(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */);
void send_http_response(const std::shared_ptr<FileClient>& /* 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<pipes::SSL::Options>& /* ssl options */);
[[nodiscard]] bool initialize(std::string& /* error */);
void finalize();
[[nodiscard]] std::string file_base_path() const override;

View File

@ -190,6 +190,10 @@ std::shared_ptr<directory_query_response_t> 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<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileS
return this->delete_files(this->server_path(id) / fs::u8path("avatars"), avatar);
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_file_info(const fs::path &base,
const std::vector<std::string> &paths) {
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_file_info(const std::vector<std::tuple<fs::path, std::string>> &paths) {
std::error_code error{};
auto response = this->create_execute_response<FileInfoError, FileInfoResponse>();
std::vector<FileInfoResponse::FileInfo> 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<ExecuteResponse<FileInfoError, FileInfoResponse>> 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<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSyste
return response;
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_channel_info(const std::shared_ptr<VirtualFileServer> &sid, ChannelId cid, const std::vector<std::string> &paths) {
return this->query_file_info(this->server_channel_path(sid, cid), paths);
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_channel_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::tuple<ChannelId, std::string>>& files) {
std::vector<std::tuple<fs::path, std::string>> 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<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_icon_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::string> &paths) {
return this->query_file_info(this->server_path(sid) / fs::u8path("icons"), paths);
std::vector<std::tuple<fs::path, std::string>> 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<ExecuteResponse<FileInfoError, FileInfoResponse> > LocalFileSystem::query_avatar_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::string> &paths) {
return this->query_file_info(this->server_path(sid) / fs::u8path("avatars"), paths);
std::vector<std::tuple<fs::path, std::string>> 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);
}

View File

@ -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<std::shared_ptr<NetworkBinding>>& bindings, const std::shared_ptr<pipes::SSL::Options>& ssl_options) {
assert(ssl_options);
this->ssl_options_ = ssl_options;
bool LocalFileTransfer::start(const std::deque<std::shared_ptr<NetworkBinding>>& bindings) {
(void) this->start_client_worker();
{
@ -129,7 +136,7 @@ std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> L
std::unique_lock tlock{this->transfers_mutex};
{
auto transfers = std::count_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& 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>& transfer) {
return transfer->client_unique_id == info.client_unique_id;
@ -191,17 +198,12 @@ std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> 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<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> 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);

View File

@ -46,31 +46,16 @@ void LocalFileTransfer::disconnect_client(const std::shared_ptr<FileClient> &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<std::chrono::seconds>(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<std::chrono::seconds>(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<std::chrono::seconds>(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<FileClient> &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);
}

View File

@ -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<FileClient> &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<FileClient> &transfer,
std::unique_lock<std::shared_mutex> &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<FileClient> &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<FileClient> &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<FileClient> &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<FileClient> &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<FileClient> &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);
}

View File

@ -7,8 +7,11 @@
#include <netinet/tcp.h>
#include <log/LogUtils.h>
#include <misc/net.h>
#include <misc/base64.h>
#include <include/files/Config.h>
#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<FileClient>
auto& ssl_pipe = client->networking.pipe_ssl;
if(!ssl_pipe.initialize(this->ssl_options_, error)) {
std::shared_ptr<pipes::SSL::Options> 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<FileClient>
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_ptr<FileCli
return this->handle_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_ptr<FileCli
client->networking.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_ptr<FileCli
auto key_result = this->handle_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<FileClient>
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<std::string, std::string> 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<FileClient>
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<FileClient>
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<FileClient>
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<FileClient>
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<FileClient>
}
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<FileClient> &client, const char *buffer, size_t length) {
client->statistics.file_transferred.increase_bytes(length);
TransferUploadRawResult LocalFileTransfer::handle_transfer_upload_raw(const std::shared_ptr<FileClient> &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"});
}

View File

@ -8,6 +8,8 @@
#include <experimental/filesystem>
#include <local_server/clnpath.h>
#include <event2/thread.h>
#include <include/files/Config.h>
#include <local_server/HTTPUtils.h>
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, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
auto rsa = RSA_new();
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(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<std::string, std::string> 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<logger::LoggerConfig>();
log_config->terminalLevel = spdlog::level::trace;
@ -71,6 +115,23 @@ int main() {
auto instance = file::server();
{
auto options = std::make_shared<pipes::SSL::Options>();
options->verbose_io = true;
options->context_method = SSLv23_method();
options->free_unused_keypairs = false;
{
std::shared_ptr<EVP_PKEY> pkey{ssl_generate_key(), ::EVP_PKEY_free};
std::shared_ptr<X509> 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<file::transfer::Transfer>& transfer) {
@ -151,7 +212,7 @@ int main() {
logMessage(0, "Transfer started");
};
ft.callback_transfer_aborted = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const file::transfer::TransferError& error) {
ft.callback_transfer_aborted = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const transfer::TransferStatistics&, const file::transfer::TransferError& error) {
logMessage(0, "Transfer aborted ({}/{})", (int) error.error_type, error.error_message);
};