Some file transfer updates

This commit is contained in:
WolverinDEV
2020-05-10 16:23:02 +02:00
parent dd4a871bf0
commit 4e3921502d
12 changed files with 742 additions and 233 deletions
+519 -143
View File
@@ -9,7 +9,6 @@
#include <misc/net.h>
#include "./LocalFileProvider.h"
#include "./duration_utils.h"
#include "LocalFileProvider.h"
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
#define TCP_NOPUSH TCP_CORK
@@ -18,6 +17,8 @@
using namespace ts::server::file;
using namespace ts::server::file::transfer;
#define MAX_HTTP_HEADER_SIZE (4096)
inline void add_network_event(FileClient& transfer, event* ev, bool& ev_throttle_readd_flag, bool ignore_bandwidth) {
timeval tv{0, 1}, *ptv{nullptr};
{
@@ -87,16 +88,21 @@ void FileClient::add_network_write_event_nolock(bool ignore_bandwidth) {
return;
case STATE_DISCONNECTING:
if(this->transfer->direction == Transfer::DIRECTION_UPLOAD)
if(this->transfer && this->transfer->direction == Transfer::DIRECTION_UPLOAD)
return;
/* flush our write buffer */
break;
case STATE_AWAITING_KEY:
if(this->networking.protocol != FileClient::PROTOCOL_HTTPS) {
assert(false);
return;
}
break;
case STATE_TRANSFERRING:
break;
//case STATE_PENDING:
//case STATE_AWAITING_KEY:
default:
assert(false);
break;
@@ -107,22 +113,7 @@ 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) {
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->buffer.mutex};
*this->buffer.buffer_tail = tbuffer;
this->buffer.buffer_tail = &tbuffer->next;
buffer_size = (this->buffer.bytes += size);
}
this->add_network_write_event(false);
return buffer_size > TRANSFER_MAX_CACHED_BYTES;
return this->enqueue_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;
@@ -131,6 +122,25 @@ bool FileClient::send_file_bytes(const void *snd_buffer, size_t size) {
}
}
bool FileClient::enqueue_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->buffer.mutex};
*this->buffer.buffer_tail = tbuffer;
this->buffer.buffer_tail = &tbuffer->next;
buffer_size = (this->buffer.bytes += size);
}
this->add_network_write_event(false);
return buffer_size > TRANSFER_MAX_CACHED_BYTES;
}
NetworkingStartResult LocalFileTransfer::start_networking() {
assert(!this->network.active);
@@ -311,6 +321,74 @@ void LocalFileTransfer::finalize_networking(const std::shared_ptr<FileClient> &c
client->networking.file_descriptor = 0;
}
#if 0
void dp_log(void* ptr, pipes::Logger::LogLevel level, const std::string& name, const std::string& message, ...) {
auto max_length = 1024 * 8;
char buffer[max_length];
va_list args;
va_start(args, message);
max_length = vsnprintf(buffer, max_length, message.c_str(), args);
va_end(args);
debugMessage(LOG_GENERAL, "[{}][{}] {}", level, name, std::string{buffer});
}
#endif
bool LocalFileTransfer::initialize_client_ssl(const std::shared_ptr<FileClient> &client) {
std::string error;
auto& ssl_pipe = client->networking.pipe_ssl;
if(!ssl_pipe.initialize(this->ssl_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);
return false;
}
#if 0
auto logger = std::make_shared<pipes::Logger>();
logger->callback_log = dp_log;
ssl_pipe.logger(logger);
#endif
ssl_pipe.direct_process(pipes::PROCESS_DIRECTION_IN, true);
ssl_pipe.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
ssl_pipe.callback_initialized = [client] {
logTrace(LOG_FT, "{} SSL layer has been initialized", client->log_prefix());
};
ssl_pipe.callback_data([&, client](const pipes::buffer_view& message) {
client->handle->handle_transfer_read(client, message.data_ptr<char>(), message.length());
});
ssl_pipe.callback_error([client](int error, const std::string & error_detail) {
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);
});
ssl_pipe.callback_write([client](const pipes::buffer_view& buffer) {
client->enqueue_buffer_bytes(buffer.data_ptr(), buffer.length());
client->add_network_write_event(false);
});
return true;
}
void LocalFileTransfer::finalize_client_ssl(const std::shared_ptr<FileClient> &client) {
auto& ssl_pipe = client->networking.pipe_ssl;
ssl_pipe.callback_initialized = []{};
ssl_pipe.callback_write([](const pipes::buffer_view&){});
ssl_pipe.callback_error([](auto, const auto&){});
ssl_pipe.callback_data([](const auto&){});
}
void LocalFileTransfer::callback_transfer_network_accept(int fd, short, void *ptr_binding) {
auto binding = reinterpret_cast<NetworkBinding*>(ptr_binding);
auto transfer = binding->handle;
@@ -456,10 +534,10 @@ void LocalFileTransfer::callback_transfer_network_read(int fd, short events, voi
size_t bytes_buffered{0};
if(transfer->state == FileClient::STATE_AWAITING_KEY) {
bytes_buffered = transfer->handle->handle_transfer_read(transfer->shared_from_this(), buffer, read);
bytes_buffered = transfer->handle->handle_transfer_read_raw(transfer->shared_from_this(), buffer, read);
} else if(transfer->state == FileClient::STATE_TRANSFERRING) {
if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
bytes_buffered = transfer->handle->handle_transfer_read(transfer->shared_from_this(), buffer, read);
bytes_buffered = transfer->handle->handle_transfer_read_raw(transfer->shared_from_this(), buffer, read);
} else {
debugMessage(LOG_FT, "{} Received {} bytes without any need. Dropping them.", transfer->log_prefix(), read);
}
@@ -500,18 +578,20 @@ void LocalFileTransfer::callback_transfer_network_write(int fd, short events, vo
if((unsigned) events & (unsigned) EV_WRITE) {
if(transfer->state != FileClient::STATE_DISCONNECTING && transfer->state != FileClient::STATE_TRANSFERRING) {
debugMessage(LOG_FT, "{} Tried to write data to send only stream. Dropping buffers.", transfer->log_prefix());
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->buffer.mutex};
auto head = std::exchange(transfer->buffer.buffer_head, nullptr);
transfer->buffer.buffer_tail = &transfer->buffer.buffer_head;
while(head) {
auto next = head->next;
free_buffer(head);
head = next;
while(head) {
auto next = head->next;
free_buffer(head);
head = next;
}
return;
}
return;
}
Buffer* buffer{nullptr};
@@ -598,7 +678,7 @@ 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->statistics.file_bytes_transferred + transfer->transfer->file_offset == transfer->transfer->expected_file_size) {
if(transfer->transfer && transfer->statistics.file_bytes_transferred + 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)
@@ -624,64 +704,241 @@ inline std::string transfer_key_to_string(char key[TRANSFER_KEY_LENGTH]) {
return result;
}
size_t LocalFileTransfer::handle_transfer_read_raw(const std::shared_ptr<FileClient> &client, const char *buffer, size_t length) {
if(client->networking.protocol == FileClient::PROTOCOL_TS_V1) {
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;
} else if(client->networking.protocol != FileClient::PROTOCOL_UNKNOWN) {
assert(false);
logWarning(LOG_FT, "{} Read bytes with unknown protocol. Closing connection.", client->log_prefix());
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, false);
return (size_t) -1;
}
if(client->state != FileClient::STATE_AWAITING_KEY) {
logWarning(LOG_FT, "{} Read bytes with unknown protocol but having not awaiting key state. Closing connection.", client->log_prefix());
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, false);
return (size_t) -1;
}
/* lets read the key bytes (16) and then decide */
if(client->transfer_key.provided_bytes < TRANSFER_KEY_LENGTH) {
const auto bytes_write = std::min(TRANSFER_KEY_LENGTH - client->transfer_key.provided_bytes, length);
memcpy(client->transfer_key.key + client->transfer_key.provided_bytes, buffer, bytes_write);
length -= bytes_write;
buffer += bytes_write;
client->transfer_key.provided_bytes += bytes_write;
}
if(client->transfer_key.provided_bytes < TRANSFER_KEY_LENGTH)
return 0; /* we need more data */
if(pipes::SSL::is_ssl((uint8_t*) client->transfer_key.key, client->transfer_key.provided_bytes)) {
client->networking.protocol = FileClient::PROTOCOL_HTTPS;
client->networking.http_header_buffer.reset(allocate_buffer(MAX_HTTP_HEADER_SIZE)); /* max 8k header */
client->networking.max_read_buffer_size = (size_t) MAX_HTTP_HEADER_SIZE; /* HTTP-Header are sometimes a bit bigger. Dont cap max bandwidth here. */
debugMessage(LOG_FT, "{} Using protocol HTTPS for file transfer.", client->log_prefix());
char first_bytes[TRANSFER_KEY_LENGTH];
memcpy(first_bytes, client->transfer_key.key, TRANSFER_KEY_LENGTH);
client->transfer_key.provided_bytes = 0;
if(!this->initialize_client_ssl(client))
return (size_t) -1;
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;
} else {
client->networking.protocol = FileClient::PROTOCOL_TS_V1;
debugMessage(LOG_FT, "{} Using protocol RAWv1 for file transfer.", client->log_prefix());
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)
this->enqueue_disk_io(client); /* we've to take initiative */
return length ? this->handle_transfer_read(client, buffer, length) : 0;
case TransferKeyApplyResult::UNKNOWN_KEY:
logMessage(LOG_FT, "{} Disconnecting client because we don't recognise his key ({}).", client->log_prefix(), transfer_key_to_string(client->transfer_key.key));
break;
case TransferKeyApplyResult::FILE_ERROR:
assert(client->transfer);
this->report_transfer_statistics(client);
if(auto callback{this->callback_transfer_aborted}; callback)
callback(client->transfer, { TransferError::DISK_INITIALIZE_ERROR, error_detail });
logMessage(LOG_FT, "{} Disconnecting client because we failed to open the target file.", client->log_prefix());
break;
default:
this->report_transfer_statistics(client);
if(auto callback{this->callback_transfer_aborted}; client->transfer && callback)
callback(client->transfer, { TransferError::UNKNOWN, error_detail });
logMessage(LOG_FT, "{} Disconnecting client because of an unknown key initialize error ({}).", client->log_prefix(), (int) key_result);
break;
}
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, false);
return (size_t) -1;
}
return 0;
}
size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr<FileClient> &client, const char *buffer, size_t length) {
if(client->state == FileClient::STATE_AWAITING_KEY) {
if(client->networking.protocol == FileClient::PROTOCOL_UNKNOWN) {
/* lets read the key bytes (16) and then decide */
if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) {
assert(client->networking.http_header_buffer);
auto header_buffer = &*client->networking.http_header_buffer;
if(client->transfer_key.provided_bytes < TRANSFER_KEY_LENGTH) {
const auto bytes_write = std::min(TRANSFER_KEY_LENGTH - client->transfer_key.provided_bytes, length);
memcpy(client->transfer_key.key + client->transfer_key.provided_bytes, buffer, bytes_write);
length -= bytes_write;
buffer += bytes_write;
client->transfer_key.provided_bytes += bytes_write;
http::HttpResponse response{};
size_t overhead_length{0};
char* overhead_data_ptr{nullptr};
if(header_buffer->offset + length > header_buffer->capacity) {
logMessage(LOG_FT, "{} Closing connection due to an too long HTTP(S) header (over {} bytes)", client->log_prefix(), header_buffer->capacity);
response.code = http::code::code(413, "Entity Too Large");
response.setHeader("x-error-message", { "header exceeds max size of " + std::to_string(header_buffer->capacity) });
goto send_response_exit;
}
if(client->transfer_key.provided_bytes < TRANSFER_KEY_LENGTH)
return 0; /* we need more data */
{
http::HttpRequest request{};
if(pipes::SSL::is_ssl((uint8_t*) client->transfer_key.key, client->transfer_key.provided_bytes)) {
client->networking.protocol = FileClient::PROTOCOL_HTTPS;
client->networking.max_read_buffer_size = (size_t) 1024 * 2; /* HTTPS-Header are sometimes a bit bigger. Dont cap max bandwidth here. */
debugMessage(LOG_FT, "{} Using protocol HTTPS for file transfer.", client->log_prefix());
const auto old_offset = header_buffer->offset;
memcpy(header_buffer->data + header_buffer->offset, buffer, length);
header_buffer->offset += length;
char first_bytes[TRANSFER_KEY_LENGTH];
memcpy(first_bytes, client->transfer_key.key, TRANSFER_KEY_LENGTH);
client->transfer_key.provided_bytes = 0;
constexpr static std::string_view header_end_token{"\r\n\r\n"};
auto header_view = std::string_view{header_buffer->data, header_buffer->offset};
auto header_end = header_view.find(header_end_token, old_offset > 3 ? old_offset - 3 : 0);
if(header_end == std::string::npos) return 0;
this->handle_transfer_read(client, first_bytes, TRANSFER_KEY_LENGTH);
return this->handle_transfer_read(client, buffer, length);
} else {
client->networking.protocol = FileClient::PROTOCOL_TS_V1;
debugMessage(LOG_FT, "{} Using protocol RAWv1 for file transfer.", client->log_prefix());
debugMessage(LOG_FT, "{} Received clients HTTP header.", client->log_prefix());
if(!http::parse_request(std::string{header_view.data(), header_end}, request)) {
logError(LOG_FT, "{} Failed to parse HTTP request. Disconnecting client.", client->log_prefix());
return this->handle_transfer_read(client, buffer, length);
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, true);
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");
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.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);
std::string error_detail{};
auto key_result = this->handle_transfer_key_provided(client, error_detail);
switch(key_result) {
case TransferKeyApplyResult::SUCCESS:
break;
case TransferKeyApplyResult::FILE_ERROR:
assert(client->transfer);
this->report_transfer_statistics(client);
if(auto callback{this->callback_transfer_aborted}; callback)
callback(client->transfer, { TransferError::DISK_INITIALIZE_ERROR, error_detail });
logMessage(LOG_FT, "{} Disconnecting client because we failed to open the target file.", client->log_prefix());
response.code = http::code::code(500, "Internal Server Error");
response.setHeader("x-error-message", { error_detail });
goto send_response_exit;
case TransferKeyApplyResult::UNKNOWN_KEY:
logMessage(LOG_FT, "{} Disconnecting client because we don't recognise his key ({}).", client->log_prefix(), transfer_key_to_string(client->transfer_key.key));
response.code = http::code::code(406, "Not Acceptable");
response.setHeader("x-error-message", { "unknown key" });
goto send_response_exit;
default:
this->report_transfer_statistics(client);
if(auto callback{this->callback_transfer_aborted}; client->transfer && callback)
callback(client->transfer, { TransferError::UNKNOWN, error_detail });
logMessage(LOG_FT, "{} Disconnecting client because of an unknown key initialize error ({}).", client->log_prefix(), (int) key_result);
response.code = http::code::code(500, "Internal Server Error");
response.setHeader("x-error-message", { error_detail.empty() ? "failed to initialize transfer" : error_detail });
goto send_response_exit;
}
response.code = http::code::_200;
if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
const auto download_name = request.findHeader("download-name");
response.setHeader("Content-Length", { std::to_string(client->transfer->expected_file_size - client->transfer->file_offset) });
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") + "\""
});
/* 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
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) });
client->networking.http_state = FileClient::HTTP_STATE_AWAITING_BOUNDARY;
}
overhead_length = header_buffer->offset - header_end - header_end_token.length();
overhead_data_ptr = header_buffer->data + header_end + header_end_token.length();
}
} else if(client->networking.protocol == FileClient::PROTOCOL_TS_V1) {
auto key_result = this->handle_transfer_key_provided(client);
switch(key_result) {
case TransferKeyApplyResult::SUCCESS:
return length ? this->handle_transfer_read(client, buffer, length) : 0;
case TransferKeyApplyResult::FILE_ERROR:
logMessage(LOG_FT, "{} Disconnecting client because we failed to open the target file.", client->log_prefix());
return (size_t) -1;
case TransferKeyApplyResult::UNKNOWN_KEY:
logMessage(LOG_FT, "{} Disconnecting client because we don't recognise his key ({}).", client->log_prefix(), transfer_key_to_string(client->transfer_key.key));
return (size_t) -1;
default:
logMessage(LOG_FT, "{} Disconnecting client because of an unknown key initialize error ({}).", client->log_prefix(), (int) key_result);
return (size_t) -1;
send_response_exit:
this->send_http_response(client, response);
if(response.code->code != 200) {
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, true);
return (size_t) -1;
}
} else if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) {
/* TODO! */
/* 1. Await HTTP header
* 2. Test for file transfer key & if its upload or download
*/
if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD)
this->enqueue_disk_io(client); /* we've to take initiative */
header_buffer->offset = 0;
return overhead_length == 0 ? 0 : this->handle_transfer_read(client, overhead_data_ptr, overhead_length);
} else {
logError(LOG_FT, "{} Protocol variable contains unknown protocol. Disconnecting client.", client->log_prefix());
logError(LOG_FT, "{} Protocol variable contains invalid protocol for awaiting key state. Disconnecting client.", client->log_prefix());
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, true);
@@ -695,47 +952,73 @@ size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr<FileClient>
}
if(client->networking.protocol == FileClient::PROTOCOL_HTTPS) {
client->networking.pipe_ssl.process_incoming_data(pipes::buffer_view{buffer, length});
return client->buffer.bytes; /* a bit unexact but the best we could get away with it */
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);
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 */
}
case TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE:
return client->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());
error_message = "invalid boundary content type";
break;
case TransferUploadHTTPResult::INVALID_CONTENT_TYPE:
logMessage(LOG_FT, "{} Invalid boundary content type. Disconnecting client.", client->log_prefix());
error_message = "missing boundary content type";
break;
case TransferUploadHTTPResult::BOUNDARY_MISSING:
logMessage(LOG_FT, "{} Missing boundary token. Disconnecting client.", client->log_prefix());
error_message = "missing boundary token";
break;
case TransferUploadHTTPResult::BOUNDARY_INVALID:
logMessage(LOG_FT, "{} Invalid boundary. Disconnecting client.", client->log_prefix());
error_message = "invalid boundary";
break;
case TransferUploadHTTPResult::BOUNDARY_TOKEN_INVALID:
logMessage(LOG_FT, "{} Invalid boundary token. Disconnecting client.", client->log_prefix());
error_message = "invalid boundary token";
break;
}
http::HttpResponse response{};
response.code = http::code::code(510, "Not Extended");
response.setHeader("x-error-message", { error_message });
client->handle->send_http_response(client, response);
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, true);
return (size_t) -1;
} else if(client->networking.protocol == FileClient::PROTOCOL_TS_V1) {
client->statistics.file_bytes_transferred += length;
auto result = this->handle_transfer_upload_raw(client, buffer, length);
bool transfer_finished{false};
auto writte_offset = client->statistics.file_bytes_transferred + 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;
switch (result) {
case TransferUploadRawResult::FINISH: {this->report_transfer_statistics(client);
if(auto callback{this->callback_transfer_finished}; callback)
callback(client->transfer);
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;
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, true);
return (size_t) -1;
}
case TransferUploadRawResult::MORE_DATA_TO_RECEIVE:
return client->buffer.bytes; /* a bit unexact but the best we could get away with it */
}
auto tbuffer = allocate_buffer(length);
tbuffer->offset = 0;
tbuffer->length = length;
memcpy(tbuffer->data, buffer, length);
size_t buffered_bytes;
{
std::lock_guard block{client->buffer.mutex};
*client->buffer.buffer_tail = tbuffer;
client->buffer.buffer_tail = &tbuffer->next;
buffered_bytes = (client->buffer.bytes += length);
}
this->enqueue_disk_io(client);
if(transfer_finished) {
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 buffered_bytes;
} else {
logWarning(LOG_FT, "{} Read message for client with unknown protocol. Dropping {} bytes.", client->log_prefix(), length);
return 0;
@@ -746,7 +1029,7 @@ size_t LocalFileTransfer::handle_transfer_read(const std::shared_ptr<FileClient>
return 0;
}
TransferKeyApplyResult LocalFileTransfer::handle_transfer_key_provided(const std::shared_ptr<FileClient> &client) {
TransferKeyApplyResult LocalFileTransfer::handle_transfer_key_provided(const std::shared_ptr<FileClient> &client, std::string& error_detail) {
{
std::lock_guard tlock{this->transfers_mutex};
for(auto it = this->pending_transfers.begin(); it != this->pending_transfers.end(); it++) {
@@ -758,14 +1041,8 @@ TransferKeyApplyResult LocalFileTransfer::handle_transfer_key_provided(const std
}
}
if(!client->transfer) {
logMessage(LOG_FT, "{} Received invalid transfer key. Disconnecting client.", client->log_prefix());
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, false);
if(!client->transfer)
return TransferKeyApplyResult::UNKNOWN_KEY;
}
{
std::string absolute_path{};
@@ -788,15 +1065,8 @@ TransferKeyApplyResult LocalFileTransfer::handle_transfer_key_provided(const std
break;
default:
logError(LOG_FT, "{} Tried to initialize client with unknown file target type ({}). Dropping transfer.", client->log_prefix(), (int) transfer->target_type);
this->report_transfer_statistics(client);
if(auto callback{this->callback_transfer_aborted}; callback)
callback(client->transfer, { TransferError::UNKNOWN, "invalid transfer type" });
{
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, false);
}
return TransferKeyApplyResult::FILE_ERROR;
error_detail = "invalid transfer target type";
return TransferKeyApplyResult::INTERNAL_ERROR;
}
debugMessage(LOG_FT, "{} Absolute file path: {}", client->log_prefix(), absolute_path);
client->file.absolute_path = absolute_path;
@@ -812,14 +1082,7 @@ TransferKeyApplyResult LocalFileTransfer::handle_transfer_key_provided(const std
if(io_init_result != FileInitializeResult::SUCCESS) {
logMessage(LOG_FT, "{} Failed to initialize file {}: {}/{}. Disconnecting client.",
client->log_prefix(), client->transfer->direction == Transfer::DIRECTION_UPLOAD ? "writer" : "reader", (int) io_init_result, kFileInitializeResultMessages[(int) io_init_result]);
this->report_transfer_statistics(client);
if(auto callback{this->callback_transfer_aborted}; callback)
callback(client->transfer, { TransferError::DISK_INITIALIZE_ERROR, std::to_string((int) io_init_result) + "/" + std::string{kFileInitializeResultMessages[(int) io_init_result]} });
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, false);
error_detail = std::to_string((int) io_init_result) + "/" + std::string{kFileInitializeResultMessages[(int) io_init_result]};
return TransferKeyApplyResult::FILE_ERROR;
}
@@ -835,9 +1098,122 @@ TransferKeyApplyResult LocalFileTransfer::handle_transfer_key_provided(const std
callback(client->transfer);
client->timings.key_received = std::chrono::system_clock::now();
if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD)
this->enqueue_disk_io(client); /* we've to take initiative */
return TransferKeyApplyResult::SUCCESS;
}
TransferUploadRawResult LocalFileTransfer::handle_transfer_upload_raw(const std::shared_ptr<FileClient> &client, const char *buffer, size_t length) {
client->statistics.file_bytes_transferred += length;
bool transfer_finished{false};
auto writte_offset = client->statistics.file_bytes_transferred + 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;
}
this->enqueue_disk_io(client);
return transfer_finished ? TransferUploadRawResult::FINISH : TransferUploadRawResult::MORE_DATA_TO_RECEIVE;
}
//Example boundary:
//------WebKitFormBoundaryaWP8XAzMBnMOJznv\r\nContent-Disposition: form-data; name="file"; filename="blob"\r\nContent-Type: application/octet-stream\r\n\r\n
TransferUploadHTTPResult LocalFileTransfer::handle_transfer_upload_http(const std::shared_ptr<FileClient> &client,
const char *buffer, size_t length) {
constexpr static std::string_view boundary_end_token{"\r\n\r\n"};
constexpr static std::string_view boundary_token_end_token{"\r\n"};
if(client->networking.http_state == FileClient::HTTP_STATE_AWAITING_BOUNDARY) {
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;
const auto old_offset = boundary_buffer->offset;
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);
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)
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_payload = boundary_view.substr(boundary_token_end + boundary_token_end_token.size());
http::HttpRequest boundary{};
if(!http::parse_request(std::string{boundary_payload}, boundary))
return TransferUploadHTTPResult::BOUNDARY_INVALID;
const auto content_type = boundary.findHeader("Content-Type");
if(!content_type || content_type.values.empty())
return TransferUploadHTTPResult::MISSING_CONTENT_TYPE;
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();
client->networking.http_state = FileClient::HTTP_STATE_UPLOADING;
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);
switch(result) {
case TransferUploadRawResult::MORE_DATA_TO_RECEIVE:
return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE;
case TransferUploadRawResult::FINISH:
/* TODO: Try to read the end boundary! */
return TransferUploadHTTPResult::FINISH;
default:
assert(false);
return TransferUploadHTTPResult::MORE_DATA_TO_RECEIVE;
}
} 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;
}
}
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
response.setHeader("Access-Control-Max-Age", {"86400"});
}
void LocalFileTransfer::send_http_response(const std::shared_ptr<FileClient> &client, http::HttpResponse &response) {
apply_cors_and_connection_headers(response);
response.setHeader("Access-Control-Expose-Headers", {"*, x-error-message, Content-Length, X-media-bytes, Content-Disposition"});
const auto payload = response.build();
client->send_file_bytes(payload.data(), payload.length());
}