Compare commits

..

28 Commits

Author SHA1 Message Date
noctuum b1be898ba1 Reject an over-long or empty object_storage key
key_type stores a key of key_size or longer as the empty key.
2026-09-18 16:54:48 +02:00
Jari Sundell bd00b5f6fe Use explicit device name binding rather than a custom syntax. 2026-09-18 23:19:43 +09:00
noctuum b34eb1e895 Own the download factory until commit
A malformed data uri threw before commit, and nothing deleted it.
2026-09-18 11:24:30 +02:00
noctuum b857acd081 Increment d.state_counter on pause
The start path increments it; the pause path assigned it to itself.
2026-09-18 11:05:31 +02:00
noctuum 988aca1281 Release the scgi task buffer on close
clear() keeps the capacity, and the task is pooled until exit.
2026-09-18 10:53:58 +02:00
noctuum 378c7ee854 Reject a throttle rate that does not parse
A discarded parse result left the rate at zero, which is unlimited.
2026-09-18 10:37:14 +02:00
noctuum da9606bfae Take ownership of the magnet metadata object
Nothing freed the bencode map the meta download built.
2026-09-18 10:17:28 +02:00
noctuum 8423803b6e Check the flush on the dht cache and history
good() ran before close(), so a failed write replaced the file.
2026-09-17 16:13:55 +02:00
noctuum c1ce2febf0 Reject an over-long line instead of truncating
getline sets failbit, which both loops read as end of input.
2026-09-17 11:33:44 +02:00
noctuum 5ebb7bd151 Call the target deleter in the tinyxml2 backend
A tracker target allocates, and the deleter went out of scope unused.
2026-09-17 10:33:45 +02:00
noctuum 50f0cad0c6 Release the xmlrpc array item on a throw
xmlrpc_to_target throws for an invalid target, skipping the DECREF.
2026-09-17 10:14:21 +02:00
noctuum 3717649bb3 Guard the value iterator in method.insert.list
The list branch dereferenced end() when no value was given.
2026-09-17 09:54:14 +02:00
noctuum c32b56e9d8 Do not reschedule a deleted scheduler item
schedule.remove from the scheduled command deletes the item.
2026-09-16 11:37:19 +02:00
noctuum de54a4c2ad Clamp the negative width in wstring_width
A narrow terminal makes the file list width go below zero.
2026-09-16 11:18:54 +02:00
noctuum 26fdca8f4d Use empty_visible in the view focus movers
empty() counts filtered entries, so size() - 1 underflows.
2026-09-16 10:57:25 +02:00
noctuum 00bcdaee23 Check the info-hash target in p.call_target
Without it an unknown info-hash segfaults the process.
2026-09-16 10:29:52 +02:00
rakshasa 3787dbe7f3 Tagged release 0.16.23. 2026-09-15 14:49:21 +02:00
noctuum 282c86597a Fix a use after free in the xmlrpc-c index path
The string was freed before it was tested, and tmp was never released.
2026-09-14 11:32:11 +02:00
noctuum c41e63248a Check the flush and sync on session file writes
good() was checked before close(), where the data is actually flushed.
2026-09-14 11:10:12 +02:00
noctuum 641626029a Fix a one byte write past address_copy
The index bound was the buffer size, not the last writable index.
2026-09-14 10:53:12 +02:00
noctuum 0c507c581f Release the zlib deflate state in gzip_compress_to_vector.
deflateEnd was never called, leaking 168 kB per compressed response.
2026-09-13 10:43:16 +02:00
noctuum af0b600b54 Add unit tests for ipv4_range_parse.
Covers single addresses, explicit ranges and the cidr mask boundaries.
2026-09-13 10:05:39 +02:00
noctuum c984a69fd1 Fix cidr /32 ip filter entries covering everything above the address.
Shifting end_mask by the full type width is undefined behavior.
2026-09-13 10:05:39 +02:00
Jakob Breivik Grimstveit 20002ce937 Fix false invalid session error on empty URI
Avoid triggering 'Session data is invalid' when loading downloads with an empty URI.
2026-09-11 12:05:59 +02:00
rakshasa 3caa31113d Bumped scgi max content length to 64mb. 2026-09-06 14:50:19 +02:00
Nicolas PARLANT 0373d227c1 add missing headers for libcxx-23
Signed-off-by: Nicolas PARLANT <nicolas.parlant@parhuet.fr>
2026-09-03 09:59:12 +02:00
rakshasa a8be7e443a Tagged release 0.16.22. 2026-09-02 09:39:57 +02:00
aeiouaeiouaeiouaeiouaeiouaeiou f327523a07 gzip.h: fix build with Clang <17
Signed-off-by: aeiouaeiouaeiouaeiouaeiouaeiou <aeioudev@outlook.com>
2026-08-30 11:18:25 +02:00
37 changed files with 434 additions and 451 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
m4_pattern_allow([PKG_CHECK_EXISTS])
AC_INIT([rtorrent],[0.16.21],[sundell.software@gmail.com])
AC_INIT([rtorrent],[0.16.23],[sundell.software@gmail.com])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIRS([scripts])
@@ -14,7 +14,7 @@ AX_CXX_COMPILE_STDCXX(20, noext, mandatory)
PKG_PROG_PKG_CONFIG
AC_DEFINE([API_VERSION], [26], [api version])
AC_DEFINE([API_VERSION], [27], [api version])
RAK_CHECK_CFLAGS
RAK_CHECK_CXXFLAGS
@@ -49,7 +49,7 @@ fi
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
PKG_CHECK_MODULES([ZLIB], [zlib])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.21])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.23])
AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_C
-1
View File
@@ -187,7 +187,6 @@ libsub_root_a_SOURCES = \
command_helpers.cc \
command_helpers.h \
command_groups.cc \
command_load.cc \
command_local.cc \
command_logging.cc \
command_network.cc \
+3
View File
@@ -431,6 +431,9 @@ p_call_target(const torrent::Object::list_type& args) {
const std::string& peer_id = itr++->as_string();
const std::string& command_key = itr++->as_string();
if (download == nullptr)
throw torrent::input_error("invalid parameters: info-hash not found");
torrent::HashString hash;
if (peer_id.size() != 40)
+1 -1
View File
@@ -157,7 +157,7 @@ system_method_insert_object(const torrent::Object::list_type& args, int flags) {
torrent::Object valueList = torrent::Object::create_list();
torrent::Object::list_type& valueListType = valueList.as_list();
if ((itrArgs)->is_list())
if (itrArgs != args.end() && (itrArgs)->is_list())
valueListType = (itrArgs)->as_list();
control->object_storage()->insert_str(raw_key, valueList, flags);
+36 -1
View File
@@ -1,17 +1,25 @@
#include "config.h"
#include <torrent/exceptions.h>
#include <functional>
#include <cstdio>
#include <string>
#include <vector>
#include <torrent/rate.h>
#include <torrent/hash_string.h>
#include <torrent/utils/log.h>
#include <torrent/utils/directory_events.h>
#include <torrent/utils/file_stat.h>
#include <torrent/utils/string_manip.h>
#include "globals.h"
#include "control.h"
#include "command_helpers.h"
#include "core/download.h"
#include "core/download_list.h"
#include "core/manager.h"
#include "core/view_manager.h"
#include "rpc/command_scheduler.h"
#include "rpc/parse.h"
#include "rpc/parse_commands.h"
#include "utils/watch_ready_queue.h"
@@ -138,6 +146,24 @@ apply_schedule(const torrent::Object::list_type& args, bool if_absent) {
return torrent::Object();
}
torrent::Object
apply_load(const torrent::Object::list_type& args, int flags) {
torrent::Object::list_const_iterator argsItr = args.begin();
if (argsItr == args.end())
throw torrent::input_error("Too few arguments.");
auto& filename = argsItr->as_string();
core::Manager::command_list_type commands;
while (++argsItr != args.end())
commands.push_back(argsItr->as_string());
control->core()->try_create_download_expand(filename, flags, commands);
return torrent::Object();
}
void apply_import(const std::string& path) { if (!rpc::parse_command_file(path)) throw torrent::input_error("Could not open option file: " + path); }
void apply_try_import(const std::string& path) { if (!rpc::parse_command_file(path)) control->core()->push_log_std("Could not read resource file: " + path); }
@@ -342,6 +368,15 @@ initialize_command_events() {
CMD2_ANY_STRING_V("import", [](auto, auto& str) { return apply_import(str); });
CMD2_ANY_STRING_V("try_import", [](auto, auto& str) { return apply_try_import(str); });
CMD2_ANY_LIST ("load.normal", [](auto, auto& args) { return apply_load(args, core::Manager::create_quiet | core::Manager::create_tied); });
CMD2_ANY_LIST ("load.verbose", [](auto, auto& args) { return apply_load(args, core::Manager::create_tied); });
CMD2_ANY_LIST ("load.start", [](auto, auto& args) { return apply_load(args, core::Manager::create_quiet | core::Manager::create_tied | core::Manager::create_start); });
CMD2_ANY_LIST ("load.start_verbose", [](auto, auto& args) { return apply_load(args, core::Manager::create_tied | core::Manager::create_start); });
CMD2_ANY_LIST ("load.raw", [](auto, auto& args) { return apply_load(args, core::Manager::create_quiet | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_verbose", [](auto, auto& args) { return apply_load(args, core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_start", [](auto, auto& args) { return apply_load(args, core::Manager::create_quiet | core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_start_verbose", [](auto, auto& args) { return apply_load(args, core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_VALUE ("close_low_diskspace", [](auto, auto& arg) { return apply_close_low_diskspace(arg, 99); });
CMD2_ANY_VALUE ("close_low_diskspace.normal", [](auto, auto& arg) { return apply_close_low_diskspace(arg, 3); });
+9 -1
View File
@@ -131,7 +131,7 @@ ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address
// copy everything up to '#' to address_copy and work from there
while(address[address_start_index] != '#' && address[address_start_index] != '\r' &&
address[address_start_index] != '\n' && address[address_start_index] != '\0' &&
address_start_index < 4096 ) {
address_start_index < 4095 ) {
address_copy[address_start_index] = address[address_start_index];
address_start_index++;
@@ -206,6 +206,9 @@ ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address
if (mask_bits == 0) {
mask = 0;
end_mask = ~(uint32_t)0;
} else if (mask_bits == 32) {
mask = ~(uint32_t)0;
end_mask = 0;
} else {
mask = (~mask) << (32-mask_bits);
end_mask = (~end_mask) >> mask_bits;
@@ -316,6 +319,11 @@ apply_ipv4_filter_load(const torrent::Object::list_type& args) {
ipv4_filter_parse(buffer, value);
}
if (file.fail() && !file.eof()) {
lineNumber++;
throw torrent::input_error("Exceeded max line length.");
}
} catch (torrent::input_error& e) {
snprintf(buffer, 2048, "Error in ip filter file: %s:%u: %s", filename.c_str(), lineNumber, e.what());
-81
View File
@@ -1,81 +0,0 @@
#include "config.h"
#include "command_helpers.h"
#include "control.h"
#include "core/manager.h"
torrent::Object
apply_load_trusted(const torrent::Object::list_type& args, int flags) {
auto argsItr = args.begin();
if (argsItr == args.end())
throw torrent::input_error("Too few arguments.");
auto& filename = argsItr->as_string();
core::Manager::command_list_type commands;
while (++argsItr != args.end())
commands.push_back(argsItr->as_string());
control->core()->try_create_download_expand(filename, flags, commands);
return torrent::Object();
}
torrent::Object
apply_load_untrusted(const torrent::Object::list_type& args, int flags) {
auto argsItr = args.begin();
if (argsItr == args.end())
throw torrent::input_error("Too few arguments.");
auto& filename = argsItr->as_string();
core::Manager::command_list_type commands;
while (++argsItr != args.end())
commands.push_back(argsItr->as_string());
control->core()->try_create_download_untrusted(filename, flags, commands);
return torrent::Object();
}
void
initialize_command_load() {
CMD2_ANY_LIST ("load.normal", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_quiet | core::Manager::create_tied); });
CMD2_ANY_LIST ("load.verbose", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_tied); });
CMD2_ANY_LIST ("load.start", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_quiet | core::Manager::create_tied | core::Manager::create_start); });
CMD2_ANY_LIST ("load.start_verbose", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_tied | core::Manager::create_start); });
CMD2_ANY_LIST ("load.raw", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_quiet | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_verbose", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_start", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_quiet | core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.raw_start_verbose", [](auto, auto& args) { return apply_load_trusted(args, core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.untrusted.normal", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_quiet); });
CMD2_ANY_LIST ("load.untrusted.verbose", [](auto, auto& args) { return apply_load_untrusted(args, 0); });
CMD2_ANY_LIST ("load.untrusted.start", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_quiet | core::Manager::create_start); });
CMD2_ANY_LIST ("load.untrusted.start_verbose", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_start); });
CMD2_ANY_LIST ("load.untrusted.raw", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_quiet | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.untrusted.raw_verbose", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.untrusted.raw_start", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_quiet | core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_LIST ("load.untrusted.raw_start_verbose", [](auto, auto& args) { return apply_load_untrusted(args, core::Manager::create_start | core::Manager::create_raw_data); });
CMD2_ANY_VALUE_V("load.untrusted.mark_safe", [](auto, auto& arg) {
if (arg == 0)
return;
// TODO: Add a bool to core::Manager.
rpc::rpc.mark_safe("load.untrusted.normal");
rpc::rpc.mark_safe("load.untrusted.verbose");
rpc::rpc.mark_safe("load.untrusted.start");
rpc::rpc.mark_safe("load.untrusted.start_verbose");
rpc::rpc.mark_safe("load.untrusted.raw");
rpc::rpc.mark_safe("load.untrusted.raw_verbose");
rpc::rpc.mark_safe("load.untrusted.raw_start");
rpc::rpc.mark_safe("load.untrusted.raw_start_verbose");
});
}
+15 -9
View File
@@ -374,25 +374,31 @@ initialize_command_network() {
CMD_ANY_STRING ("network.tos.set", [](auto, auto& str) { return apply_tos(str); });
CMD_ANY ("network.bind_address", [nw_config](auto, auto) { return nw_config->bind_address_best_match_str(); });
CMD_ANY_STRING_V("network.bind_address.set", [nw_config](auto, auto& str) { return nw_config->set_bind_address_str(str); });
CMD_ANY_STRING_V("network.bind_address.set", [nw_config](auto, auto& str) { return nw_config->set_bind_address(str); });
CMD_ANY ("network.bind_address.ipv4", [nw_config](auto, auto) { return nw_config->bind_inet_address_str(); });
CMD_ANY_STRING_V("network.bind_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet_address_str(str); });
CMD_ANY_STRING_V("network.bind_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet_address(str); });
CMD_ANY ("network.bind_address.ipv6", [nw_config](auto, auto) { return nw_config->bind_inet6_address_str(); });
CMD_ANY_STRING_V("network.bind_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet6_address_str(str); });
CMD_ANY_STRING_V("network.bind_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet6_address(str); });
CMD_ANY_STRING_V("network.bind_device.set", [nw_config](auto, auto& str) { return nw_config->set_bind_device_name(str); });
CMD_ANY ("network.bind_device.ipv4", [nw_config](auto, auto) { return nw_config->bind_inet_device_name(); });
CMD_ANY_STRING_V("network.bind_device.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet_device_name(str); });
CMD_ANY ("network.bind_device.ipv6", [nw_config](auto, auto) { return nw_config->bind_inet6_device_name(); });
CMD_ANY_STRING_V("network.bind_device.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet6_device_name(str); });
CMD_ANY ("network.local_address", [nw_config](auto, auto) { return nw_config->local_address_best_match_str(); });
CMD_ANY_STRING_V("network.local_address.set", [nw_config](auto, auto& str) { return nw_config->set_local_address_str(str); });
CMD_ANY_STRING_V("network.local_address.set", [nw_config](auto, auto& str) { return nw_config->set_local_address(str); });
CMD_ANY ("network.local_address.ipv4", [nw_config](auto, auto) { return nw_config->local_inet_address_str(); });
CMD_ANY_STRING_V("network.local_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet_address_str(str); });
CMD_ANY_STRING_V("network.local_address.ipv4.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet_address(str); });
CMD_ANY ("network.local_address.ipv6", [nw_config](auto, auto) { return nw_config->local_inet6_address_str(); });
CMD_ANY_STRING_V("network.local_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet6_address_str(str); });
CMD_ANY_STRING_V("network.local_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet6_address(str); });
CMD_ANY ("network.local_port", [nw_config](auto, auto) { return nw_config->local_port_best_match(); });
CMD_ANY_VALUE_V ("network.local_port.set", [nw_config](auto, auto& value) { return nw_config->set_local_port(checked_local_port_value(value, "local")); });
CMD_ANY_VALUE_V ("network.local_port.set", [nw_config](auto, auto& value) { return nw_config->set_local_port(checked_local_port_value(value, "local")); });
CMD_ANY ("network.local_port.ipv4", [nw_config](auto, auto) { return nw_config->local_inet_port(); });
CMD_ANY_VALUE_V ("network.local_port.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet_port(checked_local_port_value(value, "local ipv4")); });
CMD_ANY_VALUE_V ("network.local_port.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet_port(checked_local_port_value(value, "local ipv4")); });
CMD_ANY ("network.local_port.ipv6", [nw_config](auto, auto) { return nw_config->local_inet6_port(); });
CMD_ANY_VALUE_V ("network.local_port.ipv6.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet6_port(checked_local_port_value(value, "local ipv6")); });
CMD_ANY_VALUE_V ("network.local_port.ipv6.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet6_port(checked_local_port_value(value, "local ipv6")); });
CMD_ANY ("network.proxy.global", [](auto, auto) { return torrent::runtime::proxy_manager()->proxy_url(); });
CMD_ANY_STRING_V("network.proxy.global.set", [](auto, auto& str) { return torrent::runtime::proxy_manager()->set_proxy_url(str); });
+3 -1
View File
@@ -83,7 +83,9 @@ apply_throttle(const torrent::Object::list_type& args, bool up) {
throw torrent::input_error("Missing throttle rate for '" + name + "'.");
int64_t rate;
rpc::parse_whole_value_nothrow(arg_itr->as_string().c_str(), &rate);
if (!rpc::parse_whole_value_nothrow(arg_itr->as_string().c_str(), &rate))
throw torrent::input_error("Invalid throttle rate for '" + name + "'.");
if (rate < 0)
throw torrent::input_error("Throttle rate must be non-negative.");
+3 -2
View File
@@ -132,11 +132,12 @@ DhtManager::save_dht_cache() {
torrent::Object cache = torrent::Object::create_map();
cache_file << *torrent::runtime::network_manager()->dht_controller()->store_cache(&cache);
// The data only reaches the kernel here, so this is where a full disk is seen.
cache_file.close();
if (!cache_file.good())
return;
cache_file.close();
::rename(filename_tmp.c_str(), filename.c_str());
}
+87 -206
View File
@@ -7,6 +7,7 @@
#include <functional>
#include <sstream>
#include <stdexcept>
#include <sys/stat.h>
#include <torrent/utils/log.h>
#include <torrent/utils/resume.h>
#include <torrent/object.h>
@@ -27,8 +28,6 @@
namespace core {
static constexpr const char* session_invalid_message = "Session data is invalid, ignoring it";
bool
is_network_uri(const std::string& uri) {
return
@@ -37,16 +36,15 @@ is_network_uri(const std::string& uri) {
std::strncmp(uri.c_str(), "ftp://", 6) == 0;
}
bool
is_magnet_uri(const std::string& uri) {
return
std::strncmp(uri.c_str(), "magnet:?", 8) == 0;
}
static constexpr const char* session_invalid_message = "Session data is invalid, ignoring it";
namespace {
std::unique_ptr<torrent::Object>
static std::unique_ptr<torrent::Object>
download_factory_load_stream(const char* filename, bool* is_invalid) {
struct stat sb;
if (stat(filename, &sb) != 0 || !S_ISREG(sb.st_mode))
return std::unique_ptr<torrent::Object>();
std::fstream stream(filename, std::ios::in | std::ios::binary);
if (!stream.is_open())
@@ -63,34 +61,17 @@ download_factory_load_stream(const char* filename, bool* is_invalid) {
return obj;
}
std::unique_ptr<torrent::Object>
create_untrusted_object(torrent::Object& obj) {
auto trusted_object = std::make_unique<torrent::Object>(torrent::Object::create_map());
if (obj.has_key("info"))
trusted_object->insert_key_move("info", obj.get_key("info"));
if (obj.has_key("announce"))
trusted_object->insert_key_move("announce", obj.get_key("announce"));
if (obj.has_key("announce-list"))
trusted_object->insert_key_move("announce-list", obj.get_key("announce-list"));
if (obj.has_key("creation date"))
trusted_object->insert_key_move("creation date", obj.get_key("creation date"));
if (obj.has_key("created by"))
trusted_object->insert_key_move("created by", obj.get_key("created by"));
return trusted_object;
bool
is_magnet_uri(const std::string& uri) {
return
std::strncmp(uri.c_str(), "magnet:?", 8) == 0;
}
} // namespace anonymous
DownloadFactory::DownloadFactory(Manager* m) :
m_manager(m) {
DownloadFactory::DownloadFactory(Manager* m, bool trusted)
: m_manager(m),
m_trusted(trusted) {
m_task_load.slot() = std::bind(&DownloadFactory::receive_load, this);
m_task_commit.slot() = std::bind(&DownloadFactory::receive_commit, this);
// m_variables["connection_leech"] = rpc::call_command("protocol.connection.leech");
// m_variables["connection_seed"] = rpc::call_command("protocol.connection.seed");
@@ -103,49 +84,21 @@ DownloadFactory::DownloadFactory(Manager* m, bool trusted)
DownloadFactory::~DownloadFactory() {
torrent::this_thread::scheduler()->erase(&m_task_load);
torrent::this_thread::scheduler()->erase(&m_task_commit);
delete m_object;
}
void
DownloadFactory::load_trusted(const std::string& uri) {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::load_trusted() called on an untrusted object");
DownloadFactory::load(const std::string& uri) {
m_uri = uri;
m_task_load.slot() = [this]() { receive_load_trusted(); };
torrent::this_thread::scheduler()->wait_for(&m_task_load, 0ms);
}
// This function must be called before DownloadFactory::commit().
void
DownloadFactory::load_untrusted(const std::string& uri) {
if (m_trusted)
throw torrent::internal_error("DownloadFactory::load_untrusted() called on a trusted object");
m_uri = uri;
m_task_load.slot() = [this]() { receive_load_untrusted(); };
torrent::this_thread::scheduler()->wait_for(&m_task_load, 0ms);
}
void
DownloadFactory::load_raw_data_trusted(const std::string& input) {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::load_raw_data_trusted() called on an untrusted object");
DownloadFactory::load_raw_data(const std::string& input) {
if (m_stream)
throw torrent::internal_error("DownloadFactory::load_raw_data_trusted() called on an object with m_stream != NULL");
m_stream.reset(new std::stringstream(input));
m_loaded = true;
}
void
DownloadFactory::load_raw_data_untrusted(const std::string& input) {
if (m_trusted)
throw torrent::internal_error("DownloadFactory::load_raw_data_untrusted() called on an trusted object");
if (m_stream)
throw torrent::internal_error("DownloadFactory::load_raw_data_untrusted() called on an object with m_stream != NULL");
throw torrent::internal_error("DownloadFactory::load*() called on an object with m_stream != NULL");
m_stream.reset(new std::stringstream(input));
m_loaded = true;
@@ -153,91 +106,53 @@ DownloadFactory::load_raw_data_untrusted(const std::string& input) {
void
DownloadFactory::commit() {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::commit() called on an untrusted object");
m_task_commit.slot() = [this]() { receive_commit(); };
torrent::this_thread::scheduler()->wait_for(&m_task_commit, 0ms);
}
void
DownloadFactory::process_load_network_uri() {
m_stream.reset(new std::stringstream);
DownloadFactory::receive_load() {
if (m_stream)
throw torrent::internal_error("DownloadFactory::load*() called on an object with m_stream != NULL");
// TODO: Add trusted flag.
if (is_network_uri(m_uri)) {
m_stream.reset(new std::stringstream);
auto done_fn = [this]() { receive_loaded(); };
auto failed_fn = [this](const std::string& error) { receive_failed(error); };
auto done_fn = [this]() { receive_loaded(); };
auto failed_fn = [this](const std::string& error) { receive_failed(error); };
m_manager->http_queue()->insert(m_uri, m_stream, done_fn, failed_fn);
m_manager->http_queue()->insert(m_uri, m_stream, done_fn, failed_fn);
m_variables["tied_to_file"] = (int64_t)false;
}
m_variables["tied_to_file"] = (int64_t)false;
return;
}
void
DownloadFactory::process_load_magnet_uri() {
m_stream.reset(new std::stringstream());
*m_stream << "d10:magnet-uri" << m_uri.length() << ":" << m_uri << "e";
if (is_magnet_uri(m_uri)) {
// DEBUG: Use m_object.
m_stream.reset(new std::stringstream());
*m_stream << "d10:magnet-uri" << m_uri.length() << ":" << m_uri << "e";
m_variables["tied_to_file"] = (int64_t)false;
m_variables["tied_to_file"] = (int64_t)false;
receive_loaded();
}
receive_loaded();
return;
}
void
DownloadFactory::process_load_file_uri() {
std::fstream stream(expand_path(m_uri).c_str(), std::ios::in | std::ios::binary);
if (!stream.is_open())
return receive_failed("Could not open file");
m_object = std::make_unique<torrent::Object>();
m_object = new torrent::Object;
stream >> *m_object;
if (!stream.good())
return receive_failed("Reading torrent file failed");
m_is_file = true;
m_isFile = true;
receive_loaded();
}
void
DownloadFactory::receive_load_trusted() {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::receive_load_trusted() called on an untrusted object");
if (m_stream)
throw torrent::internal_error("DownloadFactory::receive_load_trusted() called on an object with null m_stream");
if (is_network_uri(m_uri))
return process_load_network_uri();
if (is_magnet_uri(m_uri))
return process_load_magnet_uri();
process_load_file_uri();
}
void
DownloadFactory::receive_load_untrusted() {
if (m_trusted)
throw torrent::internal_error("DownloadFactory::receive_load_untrusted() called on a trusted object");
if (m_stream)
throw torrent::internal_error("DownloadFactory::load*() called on an object with null m_stream");
if (is_network_uri(m_uri))
return process_load_network_uri();
// TODO: Don't need to handle untrusted commands magnet URIs.
// if (is_magnet_uri(m_uri))
// return process_load_magnet_uri();
throw torrent::internal_error("DownloadFactory::receive_load_untrusted() called on a non-network/magnet URI");
}
void
DownloadFactory::receive_loaded() {
m_loaded = true;
@@ -256,40 +171,38 @@ DownloadFactory::receive_commit() {
void
DownloadFactory::receive_success() {
if (!m_trusted && m_session)
throw torrent::internal_error("DownloadFactory::receive_success() called on an untrusted object with m_session == true");
bool session_invalid = false;
if (m_session && !m_is_file)
throw torrent::internal_error("DownloadFactory::receive_success() called on a non-file object with m_session == true");
bool session_invalid = false;
uint32_t tracker_key = tracker_key = random() % (std::numeric_limits<uint32_t>::max() - 1) + 1;
std::unique_ptr<torrent::Object> rtorrent_object, libtorrent_resume_object;
std::unique_ptr<torrent::Object> rtorrent_object;
std::unique_ptr<torrent::Object> libtorrent_resume_object;
if (m_session) {
rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str(), &session_invalid);
libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid);
if (m_uri.empty())
throw torrent::input_error("Session torrent URI is empty.");
if (session_invalid)
lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str());
rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str(), &session_invalid);
libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid);
if (rtorrent_object && rtorrent_object->has_key_value("key"))
tracker_key = rtorrent_object->get_key_value("key");
if (session_invalid)
lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str());
}
// TODO: This adds the torrent, so untrusted can add brokent torrents.
uint32_t tracker_key;
if (m_stream != nullptr)
object_from_stream();
if (rtorrent_object && rtorrent_object->has_key_value("key"))
tracker_key = rtorrent_object->get_key_value("key");
else
tracker_key = random() % (std::numeric_limits<uint32_t>::max() - 1) + 1;
if (m_trusted)
m_object = create_untrusted_object(*m_object);
Download* download = m_stream != nullptr ?
m_manager->download_list()->create(m_stream.get(), tracker_key, m_printLog) :
m_manager->download_list()->create(m_object, tracker_key, m_printLog);
Download* download = m_manager->download_list()->create(std::move(m_object), tracker_key, m_print_log);
m_object = NULL;
if (download == nullptr) {
// core::Manager should already have added the error message to the log.
if (download == NULL) {
// core::Manager should already have added the error message to
// the log.
m_slot_finished();
return;
}
@@ -298,49 +211,39 @@ DownloadFactory::receive_success() {
download->set_hash_failed(true);
download->set_message(session_invalid_message);
if (m_print_log)
if (m_printLog)
m_manager->push_log_std(std::string(session_invalid_message) + ": \"" + m_uri + "\"");
}
torrent::Object* root = download->bencode();
if (download->download()->info()->is_meta_download()) {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::receive_success() called on an untrusted object with a meta download");
torrent::Object& meta = root->insert_key("rtorrent_meta_download", torrent::Object::create_map());
meta.insert_key("start", m_start);
meta.insert_key("print_log", m_print_log);
meta.insert_key("print_log", m_printLog);
// TODO: ADD UNTRUSTED!!!
auto& commands = meta.insert_key("commands", torrent::Object::create_list()).as_list();
torrent::Object::list_type& commands = meta.insert_key("commands", torrent::Object::create_list()).as_list();
for (auto& m_command : m_commands)
commands.push_back(m_command);
}
if (m_session) {
if (!m_trusted)
throw torrent::internal_error("DownloadFactory::receive_success() called on an untrusted object with m_session == true");
if (rtorrent_object)
root->insert_key_move("rtorrent", *rtorrent_object);
if (libtorrent_resume_object)
root->insert_key_move("libtorrent_resume", *libtorrent_resume_object);
} else if (!m_trusted) {
if (root->has_key("rtorrent") || root->has_key("libtorrent_resume"))
throw torrent::internal_error("DownloadFactory::receive_success() called on an untrusted object with 'rtorrent' or 'libtorrent_resume' keys");
} else {
// We only allow session torrents to keep their 'rtorrent/libtorrent' sections. The
// "fast_resume" section should be safe to keep.
// We only allow session torrents to keep their
// 'rtorrent/libtorrent' sections. The "fast_resume" section
// should be safe to keep.
root->erase_key("rtorrent");
}
auto* rtorrent = &root->insert_preserve_copy("rtorrent", torrent::Object::create_map()).first->second;
auto& resumeObject = root->insert_preserve_copy("libtorrent_resume", torrent::Object::create_map()).first->second;
torrent::Object* rtorrent = &root->insert_preserve_copy("rtorrent", torrent::Object::create_map()).first->second;
torrent::Object& resumeObject = root->insert_preserve_copy("libtorrent_resume", torrent::Object::create_map()).first->second;
rtorrent->insert_key("key", download->tracker_controller().key());
@@ -370,7 +273,7 @@ DownloadFactory::receive_success() {
}
// Skip forcing trackers to scrape when rtorrent starts
if (m_init_load && rpc::call_command_value("trackers.delay_scrape"))
if (m_initLoad && rpc::call_command_value("trackers.delay_scrape"))
download->set_resume_flags(torrent::Download::start_skip_tracker);
// Check first if we already have these values set in the session
@@ -412,7 +315,7 @@ DownloadFactory::receive_success() {
lt_log_print(torrent::LOG_ERROR, "%s: %s", msg.c_str(), m_uri.c_str());
if (m_print_log)
if (m_printLog)
m_manager->push_log_std(msg + ": \"" + m_uri + "\"");
download->set_hash_failed(true);
@@ -436,13 +339,8 @@ DownloadFactory::receive_success() {
if (torrent::log_groups[torrent::LOG_TORRENT_DEBUG].valid())
log_created(download, rtorrent);
if (m_trusted) {
for (const auto& command : m_commands)
rpc::parse_command_multiple_std(command, rpc::make_target(download));
} else {
// TODO: CALL UNTRUSTED COMMANDS
}
for (const auto& command : m_commands)
rpc::parse_command_multiple_std(command, rpc::make_target(download));
if (m_manager->download_list()->find(infohash) == m_manager->download_list()->end())
throw torrent::input_error("The newly created download was removed.");
@@ -456,7 +354,7 @@ DownloadFactory::receive_success() {
} catch (torrent::input_error& e) {
std::string msg = "Command on torrent creation failed: " + std::string(e.what());
if (m_print_log)
if (m_printLog)
m_manager->push_log_std(msg);
if (m_manager->download_list()->find(infohash) != m_manager->download_list()->end()) {
@@ -470,18 +368,6 @@ DownloadFactory::receive_success() {
m_slot_finished();
}
void
DownloadFactory::receive_failed(const std::string& msg) {
if (m_print_log) {
if (m_trusted)
m_manager->push_log_std("Failed to load torrent: " + msg + ": \"" + m_uri + "\"");
else
m_manager->push_log_std("Failed to load untrusted torrent: " + msg);
}
m_slot_finished();
}
void
DownloadFactory::log_created(Download* download, torrent::Object* rtorrent) {
std::stringstream dump;
@@ -508,6 +394,15 @@ DownloadFactory::log_created(Download* download, torrent::Object* rtorrent) {
lt_log_print_dump(torrent::LOG_TORRENT_DEBUG, dump_str.c_str(), dump_str.size(), "Creating new download:");
}
void
DownloadFactory::receive_failed(const std::string& msg) {
// Add message to log.
if (m_printLog)
m_manager->push_log_std(msg + ": \"" + m_uri + "\"");
m_slot_finished();
}
void
DownloadFactory::initialize_rtorrent(Download* download, torrent::Object* rtorrent) {
auto cached_seconds = torrent::this_thread::cached_seconds().count();
@@ -531,7 +426,7 @@ DownloadFactory::initialize_rtorrent(Download* download, torrent::Object* rtorre
rtorrent->insert_preserve_copy("timestamp.finished", (int64_t)0);
rtorrent->insert_preserve_copy("tied_to_file", "");
rtorrent->insert_key("loaded_file", m_is_file ? m_uri : std::string());
rtorrent->insert_key("loaded_file", m_isFile ? m_uri : std::string());
if (rtorrent->has_key_value("priority"))
rpc::call_command("d.priority.set", rtorrent->get_key_value("priority") % 4, rpc::make_target(download));
@@ -563,18 +458,4 @@ DownloadFactory::initialize_rtorrent(Download* download, torrent::Object* rtorre
rtorrent->insert_preserve_copy("choke_heuristics.down.seed", std::string());
}
void
DownloadFactory::object_from_stream() {
m_object = std::make_unique<torrent::Object>();
*m_stream >> *m_object;
if (!m_stream->good() || !m_object->is_map()) {
if (m_print_log)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, stream is not a valid torrent.");
throw torrent::input_error("Invalid torrent data");
}
}
}
+14 -27
View File
@@ -24,18 +24,14 @@ public:
typedef std::vector<std::string> command_list_type;
// Do not destroy this object while it is in a HttpQueue.
DownloadFactory(Manager* m, bool trusted);
DownloadFactory(Manager* m);
~DownloadFactory();
// Calling of receive_load() is delayed so you can change whatever
// you want without fear of the slots being triggered as you call
// load() or commit().
void load_trusted(const std::string& uri);
void load_untrusted(const std::string& uri);
void load_raw_data_trusted(const std::string& input);
void load_raw_data_untrusted(const std::string& input);
void load(const std::string& uri);
void load_raw_data(const std::string& input);
void commit();
command_list_type& commands() { return m_commands; }
@@ -47,21 +43,16 @@ public:
bool get_start() const { return m_start; }
void set_start(bool v) { m_start = v; }
bool get_init_load() const { return m_init_load; }
void set_init_load(bool v) { m_init_load = v; }
bool get_init_load() const { return m_initLoad; }
void set_init_load(bool v) { m_initLoad = v; }
bool print_log() const { return m_print_log; }
void set_print_log(bool v) { m_print_log = v; }
bool print_log() const { return m_printLog; }
void set_print_log(bool v) { m_printLog = v; }
void slot_finished(slot_void s) { m_slot_finished = s; }
private:
void process_load_network_uri();
void process_load_magnet_uri();
void process_load_file_uri();
void receive_load_trusted();
void receive_load_untrusted();
void receive_load();
void receive_loaded();
void receive_commit();
void receive_success();
@@ -71,23 +62,19 @@ private:
void initialize_rtorrent(Download* download, torrent::Object* rtorrent);
void object_from_stream();
Manager* m_manager;
std::shared_ptr<std::iostream> m_stream;
torrent::Object* m_object{};
Manager* m_manager;
std::shared_ptr<std::iostream> m_stream;
std::unique_ptr<torrent::Object> m_object{};
bool m_trusted{};
bool m_commited{};
bool m_loaded{};
std::string m_uri;
bool m_session{};
bool m_start{};
bool m_print_log{true};
bool m_is_file{};
bool m_init_load{};
bool m_printLog{true};
bool m_isFile{};
bool m_initLoad{};
command_list_type m_commands;
torrent::Object::map_type m_variables;
+41 -10
View File
@@ -101,17 +101,49 @@ DownloadList::find_hex_ptr(const char* hash) {
}
Download*
DownloadList::create(std::unique_ptr<torrent::Object> obj, uint32_t tracker_key, bool printLog) {
DownloadList::create(torrent::Object* obj, uint32_t tracker_key, bool printLog) {
torrent::Download download;
auto obj_ptr = obj.get();
try {
download = torrent::download_add(obj.release(), tracker_key);
download = torrent::download_add(obj, tracker_key);
} catch (torrent::local_error& e) {
// TODO: Fix download_add so it takes the unique_ptr.
delete obj_ptr;
if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download: %s", e.what());
delete obj;
return NULL;
}
// There's no non-critical exceptions that should be throwable by
// the ctor, so don't catch.
return new Download(download);
}
Download*
DownloadList::create(std::istream* str, uint32_t tracker_key, bool printLog) {
torrent::Object* object = new torrent::Object;
torrent::Download download;
try {
*str >> *object;
// Don't throw input_error from here as gcc-3.3.5 produces bad
// code.
if (str->fail()) {
delete object;
if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent.");
return NULL;
}
download = torrent::download_add(object, tracker_key);
} catch (torrent::local_error& e) {
delete object;
if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download: %s", e.what());
@@ -269,7 +301,7 @@ DownloadList::set_state_stopped(Download* download) {
void
DownloadList::update_paused_state(Download* download) {
rpc::call_command("d.state_changed.set", torrent::this_thread::cached_seconds().count(), rpc::make_target(download));
rpc::call_command("d.state_counter.set", rpc::call_command_value("d.state_counter", rpc::make_target(download)), rpc::make_target(download));
rpc::call_command("d.state_counter.set", rpc::call_command_value("d.state_counter", rpc::make_target(download)) + 1, rpc::make_target(download));
// If initial seeding is complete, don't try it again when restarting.
if (download->is_done() &&
@@ -694,11 +726,10 @@ DownloadList::process_meta_download(Download* download) {
return;
}
torrent::Object* bencode = new torrent::Object(torrent::Object::create_map());
auto bencode = std::make_unique<torrent::Object>(torrent::Object::create_map());
file >> bencode->insert_key("info", torrent::Object());
if (file.fail()) {
delete bencode;
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent.");
return;
}
@@ -714,7 +745,7 @@ DownloadList::process_meta_download(Download* download) {
erase_ptr(download);
control->core()->try_create_download_from_meta_download(bencode, metafile);
control->core()->try_create_download_from_meta_download(std::move(bencode), metafile);
}
}
+2 -1
View File
@@ -52,7 +52,8 @@ public:
Download* find_hex_ptr(const char* hash);
// Might move this to DownloadFactory.
Download* create(std::unique_ptr<torrent::Object> obj, uint32_t tracker_key, bool printLog);
Download* create(std::istream* str, uint32_t tracker_key, bool printLog);
Download* create(torrent::Object* obj, uint32_t tracker_key, bool printLog);
iterator insert(Download* d);
+37 -77
View File
@@ -37,8 +37,6 @@
namespace core {
void path_expand(std::vector<std::string>* paths, const std::string& pattern);
const int Manager::create_start;
const int Manager::create_tied;
const int Manager::create_quiet;
@@ -197,61 +195,41 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
return;
// Adding download.
DownloadFactory* f = new DownloadFactory(this, true);
auto f = std::make_unique<DownloadFactory>(this);
f->variables()["tied_to_file"] = (int64_t)(bool)(flags & create_tied);
f->commands().insert(f->commands().end(), commands.begin(), commands.end());
f->set_start(flags & create_start);
f->set_print_log(!(flags & create_quiet));
f->slot_finished([f]() { delete f; });
f->slot_finished([factory = f.get()]() { delete factory; });
if (flags & create_raw_data) {
f->load_raw_data_trusted(uri);
} else if (is_data_uri(uri)) {
if (is_data_uri(uri)) {
// Allow the use of data URIs, primarily for JSON-RPC which
// doesn't have a defined mechanism for binary data
f->load_raw_data_trusted(decode_data_uri(uri));
f->load_raw_data(decode_data_uri(uri));
f->variables()["tied_to_file"] = (int64_t)false;
} else if (flags & create_raw_data) {
f->load_raw_data(uri);
} else {
f->load_trusted(uri);
f->load(uri);
}
f->commit();
// From here the finished slot deletes it.
f.release();
}
void
Manager::try_create_download_expand(const std::string& uri, int flags, command_list_type commands) {
if (flags & create_raw_data) {
try_create_download(uri, flags, commands);
return;
}
std::vector<std::string> paths;
paths.reserve(256);
path_expand(&paths, uri);
if (!paths.empty())
for (auto& path : paths)
try_create_download(path, flags, commands);
else
try_create_download(uri, flags, commands);
}
void
Manager::try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile) {
DownloadFactory* f = new DownloadFactory(this, true);
Manager::try_create_download_from_meta_download(std::unique_ptr<torrent::Object> bencode, const std::string& metafile) {
DownloadFactory* f = new DownloadFactory(this);
f->variables()["tied_to_file"] = (int64_t)true;
f->variables()["tied_file"] = metafile;
f->variables()["tied_file"] = metafile;
torrent::Object& meta = bencode->get_key("rtorrent_meta_download");
torrent::Object::list_type& commands = meta.get_key_list("commands");
for (const auto& command : commands)
f->commands().insert(f->commands().end(), command.as_string());
@@ -264,46 +242,13 @@ Manager::try_create_download_from_meta_download(torrent::Object* bencode, const
std::stringstream s;
s.imbue(std::locale::classic());
s << *bencode;
f->load_raw_data_trusted(s.str());
f->load_raw_data(s.str());
f->commit();
}
void
Manager::try_create_download_untrusted(const std::string& uri, int flags, const command_list_type& commands) {
// TODO: Need to check if we are allowed, also rate limit.
if (!(flags & create_raw_data) &&
!is_network_uri(uri) &&
!is_magnet_uri(uri) &&
!is_data_uri(uri)) {
push_log_std("URI type not allowed for untrusted loads.");
return;
}
DownloadFactory* f = new DownloadFactory(this, false);
f->commands().insert(f->commands().end(), commands.begin(), commands.end());
f->set_start(flags & create_start);
f->set_print_log(!(flags & create_quiet));
f->slot_finished([f]() { delete f; });
if (flags & create_raw_data) {
f->load_raw_data_untrusted(uri);
} else if (is_data_uri(uri)) {
// Allow the use of data URIs, primarily for JSON-RPC which
// doesn't have a defined mechanism for binary data
f->load_raw_data_untrusted(decode_data_uri(uri));
f->variables()["tied_to_file"] = (int64_t)false;
} else {
// f->load_untrusted(uri);
throw torrent::internal_error("Manager::try_create_download_untrusted() unimplemented.");
}
f->commit();
utils::Directory
path_expand_transform(std::string path, const utils::directory_entry& entry) {
return path + entry.s_name;
}
namespace {
@@ -362,11 +307,6 @@ split_iterator(const Sequence&) {
}
utils::Directory
path_expand_transform(std::string path, const utils::directory_entry& entry) {
return path + entry.s_name;
}
// Move this somewhere better.
void
path_expand(std::vector<std::string>* paths, const std::string& pattern) {
@@ -423,6 +363,26 @@ manager_equal_tied(const std::string& path, Download* download) {
return path == rpc::call_command_string("d.tied_to_file", rpc::make_target(download));
}
void
Manager::try_create_download_expand(const std::string& uri, int flags, command_list_type commands) {
if (flags & create_raw_data) {
try_create_download(uri, flags, commands);
return;
}
std::vector<std::string> paths;
paths.reserve(256);
path_expand(&paths, uri);
if (!paths.empty())
for (auto& path : paths)
try_create_download(path, flags, commands);
else
try_create_download(uri, flags, commands);
}
// DownloadList's hashing related functions don't actually start the
// hashing, it only reacts to events. This functions checks the
// hashing view and starts hashing if nessesary.
+1 -3
View File
@@ -76,9 +76,7 @@ public:
// Temporary, find a better place for this.
void try_create_download(const std::string& uri, int flags, const command_list_type& commands);
void try_create_download_expand(const std::string& uri, int flags, command_list_type commands = command_list_type());
void try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile);
void try_create_download_untrusted(const std::string& uri, int flags, const command_list_type& commands);
void try_create_download_from_meta_download(std::unique_ptr<torrent::Object> bencode, const std::string& metafile);
private:
void create_http(const std::string& uri);
+2 -2
View File
@@ -216,7 +216,7 @@ View::set_not_visible(Download* download) {
void
View::next_focus(unsigned int i) {
if (empty())
if (empty_visible())
return;
// If at the boundary, roll over
@@ -239,7 +239,7 @@ View::next_focus(unsigned int i) {
void
View::prev_focus(unsigned int i) {
if (empty())
if (empty_visible())
return;
// If at the boundary, roll over
+4 -1
View File
@@ -25,7 +25,10 @@ WindowFileList::WindowFileList(const ui::ElementFileList* element) :
// Convert std::string to std::wstring of given width (in screen positions),
// taking into account that some characters may be occupying two screen positions.
std::wstring
wstring_width(const std::string& i_str, [[maybe_unused]] int width) {
wstring_width(const std::string& i_str, int width) {
if (width < 0)
width = 0;
std::wstring result(width + 1, L' ');
size_t length = std::mbstowcs(result.data(), i_str.c_str(), width);
+7 -1
View File
@@ -61,14 +61,20 @@ CommandScheduler::call_item(value_type item) {
// Remove the item before calling the command if it should be
// removed.
std::string key = item->key();
try {
rpc::call_object(item->command());
} catch (torrent::input_error& e) {
if (m_slotErrorMessage)
m_slotErrorMessage("Scheduled command failed: " + item->key() + ": " + e.what());
m_slotErrorMessage("Scheduled command failed: " + key + ": " + e.what());
}
// The command is allowed to erase or replace this item, which deletes it.
if (std::find(begin(), end(), item) == end())
return;
// Still schedule if we caught a torrrent::input_error?
auto next = item->next_time_scheduled();
+6 -2
View File
@@ -64,8 +64,12 @@ object_storage::insert(const char* key_data, uint32_t key_size, const torrent::O
if (std::find(key_data, key_data + key_size, '\0') != key_data + key_size)
throw torrent::input_error("Found nul-char in string.");
// Check for size > key_size.
// Check for empty string.
// key_type turns a key this long into the empty key.
if (key_size >= object_storage::key_size)
throw torrent::input_error("Key is too long.");
if (key_size == 0)
throw torrent::input_error("Key is empty.");
bool use_raw = false;
torrent::Object object;
+5
View File
@@ -181,6 +181,11 @@ parse_command_file(const std::string& path) {
getCount = 0;
}
if (file.fail() && !file.eof()) {
lineNumber++;
throw torrent::input_error("Exceeded max line length.");
}
} catch (torrent::input_error& e) {
snprintf(buffer, 2048, "Error in option file: %s:%u: %s", path.c_str(), lineNumber, e.what());
+2 -1
View File
@@ -93,7 +93,8 @@ SCgiTask::close() {
// The callbacks are guaranteed to be finished/canceled at this point.
auto lock = std::lock_guard<std::mutex>(m_result_mutex);
m_buffer.clear();
// clear() would keep the capacity, and the task is pooled for the lifetime of the process.
std::vector<char>().swap(m_buffer);
}
void
+1 -1
View File
@@ -16,7 +16,7 @@ class SCgiTask : public torrent::system::Event {
public:
static constexpr int default_buffer_size = 8191;
static constexpr int max_header_size = 2000;
static constexpr int max_content_size = (2 << 23);
static constexpr int max_content_size = (1 << 26);
static constexpr auto timeout_request = std::chrono::seconds(60);
+7 -6
View File
@@ -44,10 +44,9 @@ xmlrpc_list_entry_to_object(xmlrpc_env* env, xmlrpc_value* src, int index) {
if (env->fault_occurred)
throw xmlrpc_error_c(env);
torrent::Object obj = xmlrpc_to_object(env, tmp);
xmlrpc_DECREF(tmp);
utils::scope_guard guard([tmp]() { xmlrpc_DECREF(tmp); });
return obj;
return xmlrpc_to_object(env, tmp);
}
int64_t
@@ -77,16 +76,18 @@ xmlrpc_list_entry_to_value(xmlrpc_env* env, xmlrpc_value* src, int index) {
{
const char* str;
xmlrpc_read_string(env, tmp, &str);
xmlrpc_DECREF(tmp);
if (env->fault_occurred)
throw xmlrpc_error_c(env);
const char* end = str;
int64_t v3 = ::strtoll(str, (char**)&end, 0);
bool invalid = *str == '\0' || *end != '\0';
::free((void*)str);
if (*str == '\0' || *end != '\0')
if (invalid)
throw xmlrpc_error_c(XMLRPC_TYPE_ERROR, "Invalid index.");
return v3;
@@ -211,11 +212,11 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int call_type, rpc::targe
if (env->fault_occurred)
throw xmlrpc_error_c(env);
utils::scope_guard guard([tmp]() { xmlrpc_DECREF(tmp); });
if (target != nullptr)
std::tie(*target, *deleter) = xmlrpc_to_target(env, tmp, call_type);
xmlrpc_DECREF(tmp);
if (env->fault_occurred)
throw xmlrpc_error_c(env);
+3 -4
View File
@@ -19,6 +19,7 @@
#include "rpc/tinyxml2/tinyxml2.h"
#include "rpc/rpc_manager.h"
#include "utils/base64.h"
#include "utils/functional.h"
#include "xmlrpc.h"
namespace rpc {
@@ -258,6 +259,8 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
torrent::Object params_raw = torrent::Object::create_list();
torrent::Object::list_type& params = params_raw.as_list();
rpc::target_type target = rpc::make_target();
std::function<void()> deleter = []() {};
utils::scope_guard guard([&deleter]() { deleter(); });
if (params_element != nullptr) {
if (std::strncmp(params_element->Name(), "params", sizeof("params")) == 0) {
@@ -265,8 +268,6 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
const auto* child = params_element->FirstChildElement("param");
if (child != nullptr) {
std::function<void()> deleter = []() {};
RpcManager::object_to_target(xml_value_to_object(child->FirstChildElement("value")), cmd_itr->second.m_flags, &target, &deleter);
child = child->NextSiblingElement("param");
@@ -282,8 +283,6 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
const auto* child = params_element->FirstChildElement("data")->FirstChildElement("value");
if (child != nullptr) {
std::function<void()> deleter = []() {};
RpcManager::object_to_target(xml_value_to_object(child), cmd_itr->second.m_flags, &target, &deleter);
child = child->NextSiblingElement("value");
+13 -3
View File
@@ -127,8 +127,12 @@ save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream
if (!output.good())
throw torrent::storage_error("failed to write stream to file : " + path);
// The data only reaches the kernel here, so this is where a full disk is seen.
output.close();
if (!output.good())
throw torrent::storage_error("failed to flush stream to file : " + path);
// Ensure that the new file is actually written to the disk
int fd = ::open(path.c_str(), O_WRONLY);
@@ -137,13 +141,19 @@ save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream
if (use_fsyncdisk) {
#ifdef __APPLE__
::fsync(fd);
int sync_result = ::fsync(fd);
#else
::fdatasync(fd);
int sync_result = ::fdatasync(fd);
#endif
if (sync_result == -1) {
::close(fd);
throw torrent::storage_error("failed to sync file to disk : " + path);
}
}
::close(fd);
if (::close(fd) == -1)
throw torrent::storage_error("failed to close file descriptor : " + path);
}
} // namespace anonymous
+4 -4
View File
@@ -157,12 +157,12 @@ load_session_torrents(const std::string& path) {
if (!entry.is_file())
continue;
auto* f = new core::DownloadFactory(control->core(), true);
auto* f = new core::DownloadFactory(control->core());
f->set_session(true);
f->set_init_load(true);
f->slot_finished([f](){ delete f; });
f->load_trusted(entries.path() + entry.s_name);
f->load(entries.path() + entry.s_name);
f->commit();
}
}
@@ -170,12 +170,12 @@ load_session_torrents(const std::string& path) {
void
load_arg_torrents(char** first, char** last) {
for (; first != last; ++first) {
auto* f = new core::DownloadFactory(control->core(), true);
auto* f = new core::DownloadFactory(control->core());
f->set_start(true);
f->set_init_load(true);
f->slot_finished([f](){ delete f; });
f->load_trusted(*first);
f->load(*first);
f->commit();
}
}
+1
View File
@@ -3,6 +3,7 @@
#include <functional>
#include <string>
#include <vector>
int parse_main_options(int argc, char** argv);
void parse_config_file(int argc, char** argv, std::function<void (const std::string&)> parse_fn);
+3 -2
View File
@@ -467,6 +467,9 @@ Root::save_input_history() {
history_file << entry << "|" + category.at((pitr->second + i) % m_input_history_length) + "\n";
}
// The data only reaches the kernel here, so this is where a full disk is seen.
history_file.close();
if (!history_file.good()) {
lt_log_print(torrent::LOG_DEBUG, "input history file corrupted during writing, discarding (path:%s)", history_filename.c_str());
return;
@@ -474,8 +477,6 @@ Root::save_input_history() {
lt_log_print(torrent::LOG_DEBUG, "input history file written (path:%s)", history_filename.c_str());
}
history_file.close();
std::rename(history_filename_tmp.c_str(), history_filename.c_str());
}
+4
View File
@@ -5,6 +5,8 @@
#include <zlib.h>
#include <torrent/exceptions.h>
#include "utils/functional.h"
namespace utils {
void
@@ -21,6 +23,8 @@ gzip_compress_to_vector(const char* buffer, unsigned int length, std::vector<cha
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits | gzip_encoding, gzip_level, Z_DEFAULT_STRATEGY) != Z_OK)
throw torrent::internal_error("gzip_compress_to_vector(...) could not initialize gzip deflate.");
scope_guard guard([&zs]() { deflateEnd(&zs); });
auto max_response_size = deflateBound(&zs, length);
output.resize(offset + max_response_size);
+1
View File
@@ -2,6 +2,7 @@
#define RTORRENT_UTILS_GZIP_H
#include <functional>
#include <vector>
namespace utils {
+2
View File
@@ -51,6 +51,8 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_command_dynamic.cc \
src/test_command_dynamic.h \
src/test_command_ip.cc \
src/test_command_ip.h \
src/test_command_system.cc \
src/test_command_system.h \
src/test_command_path.cc \
+11
View File
@@ -49,6 +49,17 @@ TestObjectStorage::test_validate_keys() {
torrent::raw_string raw_string_4("test_4\0foo", 10);
ASSERT_CATCH_INPUT_ERROR( { m_storage.insert(raw_string_4, torrent::Object("a"), rpc::object_storage::flag_string_type); } );
ASSERT_CATCH_INPUT_ERROR( { m_storage.insert_str("", torrent::Object("a"), rpc::object_storage::flag_string_type); } );
std::string key_max(rpc::object_storage::key_size - 1, 'k');
CPPUNIT_ASSERT(m_storage.insert_str(key_max, torrent::Object("a"), rpc::object_storage::flag_string_type)->first == key_max);
ASSERT_CATCH_INPUT_ERROR( { m_storage.insert_str(key_max + 'k', torrent::Object("a"), rpc::object_storage::flag_string_type); } );
// The over-long key must not have been stored as the empty key.
CPPUNIT_ASSERT(m_storage.find_raw_string(torrent::raw_string::from_c_str("")) == m_storage.end());
m_storage.clear();
}
// And test many other bad/good string combos.
+21
View File
@@ -61,3 +61,24 @@ TestCommandDynamic::test_old_style() {
rpc::commands.call_command("method.insert", rpc::create_object_list("test_old_style.4", "simple", "cat=test.3"));
CPPUNIT_ASSERT(rpc::commands.call_command("test_old_style.4", torrent::Object()).as_string() == "test.3");
}
void
TestCommandDynamic::test_insert_list() {
torrent::Object key_only = torrent::Object::create_list();
key_only.as_list().push_back("test_insert_list.1");
rpc::commands.call_command("method.insert.list", key_only);
torrent::Object result = rpc::commands.call_command("test_insert_list.1", torrent::Object());
CPPUNIT_ASSERT(result.is_list());
CPPUNIT_ASSERT(result.as_list().empty());
rpc::commands.call_command("method.insert.list",
rpc::create_object_list("test_insert_list.2", rpc::create_object_list("a", "b")));
torrent::Object filled = rpc::commands.call_command("test_insert_list.2", torrent::Object());
CPPUNIT_ASSERT(filled.is_list());
CPPUNIT_ASSERT_EQUAL((size_t)2, filled.as_list().size());
}
+2
View File
@@ -7,6 +7,7 @@ class TestCommandDynamic : public test_fixture {
CPPUNIT_TEST(test_basics);
CPPUNIT_TEST(test_get_set);
CPPUNIT_TEST(test_old_style);
CPPUNIT_TEST(test_insert_list);
CPPUNIT_TEST_SUITE_END();
@@ -18,6 +19,7 @@ public:
void test_get_set();
void test_old_style();
void test_insert_list();
private:
std::unique_ptr<TestMainThread> m_test_main_thread;
+55
View File
@@ -0,0 +1,55 @@
#include "config.h"
#include "test/src/test_command_ip.h"
#include <cstdint>
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandIp);
bool ipv4_range_parse(const char* address, uint32_t* address_start, uint32_t* address_end);
static uint32_t
ipv4(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
return (a << 24) | (b << 16) | (c << 8) | d;
}
#define RANGE_ASSERT(address, expected_start, expected_end) \
{ \
uint32_t start = 0; \
uint32_t end = 0; \
\
CPPUNIT_ASSERT(ipv4_range_parse(address, &start, &end)); \
CPPUNIT_ASSERT_EQUAL(expected_start, start); \
CPPUNIT_ASSERT_EQUAL(expected_end, end); \
}
void
TestCommandIp::test_single_address() {
RANGE_ASSERT("10.1.2.3", ipv4(10, 1, 2, 3), ipv4(10, 1, 2, 3));
}
void
TestCommandIp::test_explicit_range() {
RANGE_ASSERT("10.1.2.3-10.1.2.9", ipv4(10, 1, 2, 3), ipv4(10, 1, 2, 9));
}
void
TestCommandIp::test_cidr() {
RANGE_ASSERT("10.0.0.0/8", ipv4(10, 0, 0, 0), ipv4(10, 255, 255, 255));
RANGE_ASSERT("10.1.2.0/24", ipv4(10, 1, 2, 0), ipv4(10, 1, 2, 255));
RANGE_ASSERT("10.1.2.128/25", ipv4(10, 1, 2, 128), ipv4(10, 1, 2, 255));
RANGE_ASSERT("10.1.2.3/31", ipv4(10, 1, 2, 2), ipv4(10, 1, 2, 3));
}
void
TestCommandIp::test_cidr_zero_mask() {
RANGE_ASSERT("0.0.0.0/0", ipv4(0, 0, 0, 0), ipv4(255, 255, 255, 255));
RANGE_ASSERT("10.1.2.3/0", ipv4(0, 0, 0, 0), ipv4(255, 255, 255, 255));
}
void
TestCommandIp::test_cidr_full_mask() {
RANGE_ASSERT("10.1.2.3/32", ipv4(10, 1, 2, 3), ipv4(10, 1, 2, 3));
RANGE_ASSERT("0.0.0.0/32", ipv4(0, 0, 0, 0), ipv4(0, 0, 0, 0));
RANGE_ASSERT("255.255.255.255/32", ipv4(255, 255, 255, 255), ipv4(255, 255, 255, 255));
}
+25
View File
@@ -0,0 +1,25 @@
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
// ipv4_range_parse is a pure function, so this does not use test_fixture and
// the mock and logging setup that comes with it.
class TestCommandIp : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(TestCommandIp);
CPPUNIT_TEST(test_single_address);
CPPUNIT_TEST(test_explicit_range);
CPPUNIT_TEST(test_cidr);
CPPUNIT_TEST(test_cidr_zero_mask);
CPPUNIT_TEST(test_cidr_full_mask);
CPPUNIT_TEST_SUITE_END();
public:
void test_single_address();
void test_explicit_range();
void test_cidr();
void test_cidr_zero_mask();
void test_cidr_full_mask();
};