Some bug fixes and final version before release
This commit is contained in:
parent
bfdf940dbf
commit
9e964b3ea8
@ -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
|
||||
|
13
file/include/files/Config.h
Normal file
13
file/include/files/Config.h
Normal 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;
|
||||
}
|
@ -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 {
|
||||
|
13
file/local_server/Config.cpp
Normal file
13
file/local_server/Config.cpp
Normal 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 */
|
||||
}
|
35
file/local_server/HTTPUtils.cpp
Normal file
35
file/local_server/HTTPUtils.cpp
Normal 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;
|
||||
}
|
7
file/local_server/HTTPUtils.h
Normal file
7
file/local_server/HTTPUtils.h
Normal 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 */);
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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"});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user