mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-10-04 21:29:21 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9b9629235 | |||
| cc1fcbe3e5 | |||
| 877e5f6ed3 | |||
| ee64f8ea50 | |||
| bf1265d954 | |||
| 6c3c77a2b7 | |||
| 120fcb0273 | |||
| c36956813d | |||
| 05563b4c9b | |||
| a544f8ad68 | |||
| b35413ea71 | |||
| f9f19fb041 |
+3
-3
@@ -1,6 +1,6 @@
|
||||
m4_pattern_allow([PKG_CHECK_EXISTS])
|
||||
|
||||
AC_INIT([rtorrent],[0.16.24],[sundell.software@gmail.com])
|
||||
AC_INIT([rtorrent],[0.16.22],[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], [28], [api version])
|
||||
AC_DEFINE([API_VERSION], [26], [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.24])
|
||||
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.22])
|
||||
|
||||
AC_LANG_PUSH(C++)
|
||||
TORRENT_WITH_XMLRPC_C
|
||||
|
||||
+1
-12
@@ -402,21 +402,13 @@ p_multicall(core::Download* download, const torrent::Object::list_type& args) {
|
||||
auto resultRaw = torrent::Object::create_list();
|
||||
auto& result = resultRaw.as_list();
|
||||
|
||||
auto* connection_list = download->connection_list();
|
||||
const auto change_counter = connection_list->change_counter();
|
||||
|
||||
for (const auto& connection : *connection_list) {
|
||||
for (const auto& connection : *download->connection_list()) {
|
||||
torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list();
|
||||
|
||||
for (auto cItr = ++args.begin(); cItr != args.end(); cItr++) {
|
||||
const std::string& cmd = cItr->as_string();
|
||||
|
||||
row.push_back(rpc::parse_command(rpc::make_target(connection), cmd.c_str(), cmd.c_str() + cmd.size()).first);
|
||||
|
||||
// Erasing a peer frees it and swaps the last element into its place, so
|
||||
// neither this peer nor the iteration survives a change to the list.
|
||||
if (connection_list->change_counter() != change_counter)
|
||||
throw torrent::input_error("Command changed the connection list during p.multicall.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,9 +431,6 @@ 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)
|
||||
|
||||
@@ -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 != args.end() && (itrArgs)->is_list())
|
||||
if ((itrArgs)->is_list())
|
||||
valueListType = (itrArgs)->as_list();
|
||||
|
||||
control->object_storage()->insert_str(raw_key, valueList, flags);
|
||||
|
||||
+2
-7
@@ -319,11 +319,6 @@ 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());
|
||||
|
||||
@@ -344,8 +339,8 @@ apply_ipv4_filter_dump() {
|
||||
torrent::Object raw_result = torrent::Object::create_list();
|
||||
torrent::Object::list_type& result = raw_result.as_list();
|
||||
|
||||
const torrent::ipv4_table::range_map_type& range_map = torrent::PeerList::ipv4_filter()->range_map;
|
||||
torrent::ipv4_table::range_map_type::const_iterator iter = range_map.begin();
|
||||
torrent::ipv4_table::range_map_type range_map = torrent::PeerList::ipv4_filter()->range_map;
|
||||
torrent::ipv4_table::range_map_type::iterator iter = range_map.begin();
|
||||
|
||||
while(iter != range_map.end()) {
|
||||
char buffer[64];
|
||||
|
||||
@@ -122,10 +122,7 @@ log_vmmap_dump(const std::string& str) {
|
||||
fprintf(log_file, "%8p-%8p [%5llxk]\n", all_mapping.ptr, (char*)all_mapping.ptr + all_mapping.length, (long long unsigned int)(all_mapping.length / 1024));
|
||||
}
|
||||
|
||||
// The buffered output only reaches the kernel here.
|
||||
if (fclose(log_file) != 0)
|
||||
throw torrent::input_error("Could not write log file: " + str);
|
||||
|
||||
fclose(log_file);
|
||||
return torrent::Object();
|
||||
}
|
||||
|
||||
|
||||
+9
-15
@@ -374,31 +374,25 @@ 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); });
|
||||
CMD_ANY_STRING_V("network.bind_address.set", [nw_config](auto, auto& str) { return nw_config->set_bind_address_str(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); });
|
||||
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 ("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); });
|
||||
|
||||
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_STRING_V("network.bind_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_bind_inet6_address_str(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); });
|
||||
CMD_ANY_STRING_V("network.local_address.set", [nw_config](auto, auto& str) { return nw_config->set_local_address_str(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); });
|
||||
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 ("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); });
|
||||
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 ("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); });
|
||||
|
||||
+60
-29
@@ -1,9 +1,10 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <limits>
|
||||
#include <cstdio>
|
||||
#include <torrent/throttle.h>
|
||||
#include <torrent/rate.h>
|
||||
#include <torrent/download/resource_manager.h>
|
||||
#include <torrent/net/socket_address.h>
|
||||
|
||||
#include "core/manager.h"
|
||||
#include "ui/root.h"
|
||||
@@ -14,6 +15,59 @@
|
||||
#include "control.h"
|
||||
#include "command_helpers.h"
|
||||
|
||||
std::pair<uint32_t, uint32_t>
|
||||
parse_address_range(const torrent::Object::list_type& args, torrent::Object::list_type::const_iterator itr) {
|
||||
unsigned int prefixWidth, ret;
|
||||
char dummy;
|
||||
char host[1024];
|
||||
torrent::sa_unique_ptr sa;
|
||||
|
||||
ret = std::sscanf(itr->as_string().c_str(), "%1023[^/]/%d%c", host, &prefixWidth, &dummy);
|
||||
|
||||
if (ret < 1)
|
||||
throw torrent::input_error("Invalid address/prefix.");
|
||||
|
||||
try {
|
||||
sa = torrent::sa_copy(torrent::sa_lookup_address(host, AF_INET).get());
|
||||
|
||||
} catch (torrent::input_error& e) {
|
||||
throw torrent::input_error("Could not resolve host: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
uint32_t begin, end;
|
||||
|
||||
auto sa_addr = htonl(reinterpret_cast<sockaddr_in*>(sa.get())->sin_addr.s_addr);
|
||||
|
||||
begin = end = sa_addr;
|
||||
|
||||
if (ret == 2) {
|
||||
if (++itr != args.end())
|
||||
throw torrent::input_error("Cannot specify both network and range end.");
|
||||
|
||||
uint32_t netmask = std::numeric_limits<uint32_t>::max() << (32 - prefixWidth);
|
||||
|
||||
if (prefixWidth >= 32 || sa_addr & ~netmask)
|
||||
throw torrent::input_error("Invalid address/prefix.");
|
||||
|
||||
end = sa_addr | ~netmask;
|
||||
|
||||
} else if (++itr != args.end()) {
|
||||
try {
|
||||
sa = torrent::sa_copy(torrent::sa_lookup_address(itr->as_string(), AF_INET).get());
|
||||
|
||||
} catch (torrent::input_error& e) {
|
||||
throw torrent::input_error("Could not resolve host: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
sa_addr = htonl(reinterpret_cast<sockaddr_in*>(sa.get())->sin_addr.s_addr);
|
||||
end = sa_addr;
|
||||
}
|
||||
|
||||
// convert to [begin, end) making sure the end doesn't overflow
|
||||
// (this precludes 255.255.255.255 from ever matching, but that's not a real IP anyway)
|
||||
return std::make_pair((uint32_t)begin, (uint32_t)std::max(end, end + 1));
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
apply_throttle(const torrent::Object::list_type& args, bool up) {
|
||||
auto arg_itr = args.begin();
|
||||
@@ -29,16 +83,11 @@ apply_throttle(const torrent::Object::list_type& args, bool up) {
|
||||
throw torrent::input_error("Missing throttle rate for '" + name + "'.");
|
||||
|
||||
int64_t rate;
|
||||
|
||||
if (!rpc::parse_whole_value_nothrow(arg_itr->as_string().c_str(), &rate))
|
||||
throw torrent::input_error("Invalid throttle rate for '" + name + "'.");
|
||||
rpc::parse_whole_value_nothrow(arg_itr->as_string().c_str(), &rate);
|
||||
|
||||
if (rate < 0)
|
||||
throw torrent::input_error("Throttle rate must be non-negative.");
|
||||
|
||||
if (rate > (std::numeric_limits<int64_t>::max() >> 10))
|
||||
throw torrent::input_error("Throttle rate is too large.");
|
||||
|
||||
auto itr = control->core()->throttles().find(name);
|
||||
|
||||
if (itr == control->core()->throttles().end())
|
||||
@@ -89,24 +138,6 @@ throttle_update(const char* variable, int64_t value) {
|
||||
return torrent::Object();
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
throttle_rate_to_kb(int64_t rate) {
|
||||
if (rate < 0 || rate > std::numeric_limits<unsigned int>::max() - 1)
|
||||
throw torrent::input_error("Throttle rate must be between 0 and 4294967294.");
|
||||
|
||||
return static_cast<unsigned int>(rate >> 10);
|
||||
}
|
||||
|
||||
static void
|
||||
set_up_throttle_i64(ui::Root* root, int64_t rate) {
|
||||
root->set_up_throttle(throttle_rate_to_kb(rate));
|
||||
}
|
||||
|
||||
static void
|
||||
set_down_throttle_i64(ui::Root* root, int64_t rate) {
|
||||
root->set_down_throttle(throttle_rate_to_kb(rate));
|
||||
}
|
||||
|
||||
void
|
||||
initialize_command_throttle() {
|
||||
CMD2_ANY ("throttle.unchoked_uploads", std::bind(&torrent::ResourceManager::currently_upload_unchoked, torrent::resource_manager()));
|
||||
@@ -143,13 +174,13 @@ initialize_command_throttle() {
|
||||
CMD2_ANY ("throttle.global_up.rate", std::bind(&torrent::Rate::rate, torrent::up_rate()));
|
||||
CMD2_ANY ("throttle.global_up.total", std::bind(&torrent::Rate::total, torrent::up_rate()));
|
||||
CMD2_ANY ("throttle.global_up.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::up_throttle_global()));
|
||||
CMD2_ANY_VALUE_V ("throttle.global_up.max_rate.set", std::bind(&set_up_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_KB("throttle.global_up.max_rate.set_kb", std::bind(&set_up_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_V ("throttle.global_up.max_rate.set", std::bind(&ui::Root::set_up_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_KB("throttle.global_up.max_rate.set_kb", std::bind(&ui::Root::set_up_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY ("throttle.global_down.rate", std::bind(&torrent::Rate::rate, torrent::down_rate()));
|
||||
CMD2_ANY ("throttle.global_down.total", std::bind(&torrent::Rate::total, torrent::down_rate()));
|
||||
CMD2_ANY ("throttle.global_down.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::down_throttle_global()));
|
||||
CMD2_ANY_VALUE_V ("throttle.global_down.max_rate.set", std::bind(&set_down_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_KB("throttle.global_down.max_rate.set_kb", std::bind(&set_down_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_V ("throttle.global_down.max_rate.set", std::bind(&ui::Root::set_down_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_KB("throttle.global_down.max_rate.set_kb", std::bind(&ui::Root::set_down_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
|
||||
// Temporary names, need to change this to accept real rates rather
|
||||
// than kB.
|
||||
|
||||
+2
-12
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <ctime>
|
||||
#include <limits>
|
||||
#include <regex>
|
||||
@@ -127,20 +126,11 @@ apply_value([[maybe_unused]] rpc::target_type target, const torrent::Object::lis
|
||||
if (args.front().is_value()) {
|
||||
val = args.front().as_value();
|
||||
} else {
|
||||
int64_t base = args.size() > 1 ? args.back().is_value() ?
|
||||
args.back().as_value() : strtoll(args.back().as_string().c_str(), NULL, 10) : 10;
|
||||
|
||||
if (base != 0 && (base < 2 || base > 36))
|
||||
throw torrent::input_error("'value' base must be 0 or between 2 and 36!");
|
||||
|
||||
int base = args.size() > 1 ? args.back().is_value() ?
|
||||
args.back().as_value() : strtol(args.back().as_string().c_str(), NULL, 10) : 10;
|
||||
char* endptr = 0;
|
||||
errno = 0;
|
||||
|
||||
val = strtoll(args.front().as_string().c_str(), &endptr, base);
|
||||
|
||||
if (errno == ERANGE)
|
||||
throw torrent::input_error("Number out of range: " + args.front().as_string());
|
||||
|
||||
while (*endptr == ' ' || *endptr == '\n') ++endptr;
|
||||
if (*endptr)
|
||||
throw torrent::input_error("Junk at end of number: " + args.front().as_string());
|
||||
|
||||
+3
-12
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/object.h>
|
||||
#include <torrent/object_stream.h>
|
||||
#include <torrent/rate.h>
|
||||
@@ -63,14 +62,7 @@ DhtManager::load_dht_cache() {
|
||||
LT_LOG("could not open cache file (path:%s)", cache_filename.c_str());
|
||||
}
|
||||
|
||||
try {
|
||||
torrent::runtime::network_manager()->dht_controller()->initialize(cache);
|
||||
|
||||
} catch (const torrent::base_error& e) {
|
||||
LT_LOG_ERROR("cache file rejected, discarding (path:%s errmsg:%s)", cache_filename.c_str(), e.what());
|
||||
|
||||
torrent::runtime::network_manager()->dht_controller()->initialize(torrent::Object::create_map());
|
||||
}
|
||||
torrent::runtime::network_manager()->dht_controller()->initialize(cache);
|
||||
|
||||
if (m_start == dht_on)
|
||||
start_dht();
|
||||
@@ -140,12 +132,11 @@ 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());
|
||||
}
|
||||
|
||||
|
||||
@@ -51,14 +51,6 @@ public:
|
||||
bool is_hash_checking() const { return m_download.is_hash_checking(); }
|
||||
|
||||
bool is_hash_failed() const { return m_hashFailed; }
|
||||
|
||||
// Expires once the download is erased, even if other owners keep the
|
||||
// object alive. Take it before triggering events that may erase.
|
||||
std::weak_ptr<void> lifetime() const { return m_lifetime; }
|
||||
void release_lifetime() { m_lifetime.reset(); }
|
||||
|
||||
bool is_erasing() const { return m_erasing; }
|
||||
void set_erasing() { m_erasing = true; }
|
||||
void set_hash_failed(bool v) { m_hashFailed = v; }
|
||||
|
||||
download_type* download() { return &m_download; }
|
||||
@@ -111,8 +103,6 @@ private:
|
||||
// Store the FileList instance so we can use slots etc on it.
|
||||
download_type m_download;
|
||||
bool m_hashFailed{};
|
||||
bool m_erasing{};
|
||||
std::shared_ptr<void> m_lifetime{std::make_shared<char>()};
|
||||
std::string m_message;
|
||||
uint32_t m_resumeFlags{default_resume_flags};
|
||||
unsigned int m_group{};
|
||||
|
||||
@@ -194,12 +194,6 @@ DownloadList::erase(iterator itr) {
|
||||
if (itr == end())
|
||||
throw torrent::internal_error("DownloadList::erase(...) could not find download.");
|
||||
|
||||
// An event handler below may erase the same download again.
|
||||
if ((*itr)->is_erasing())
|
||||
return std::next(itr);
|
||||
|
||||
(*itr)->set_erasing();
|
||||
|
||||
lt_log_print_info(torrent::LOG_TORRENT_INFO, (*itr)->info(), "download_list", "Erasing download.");
|
||||
|
||||
// Makes sure close doesn't restart hashing of this download.
|
||||
@@ -213,7 +207,6 @@ DownloadList::erase(iterator itr) {
|
||||
for (auto v : *control->view_manager())
|
||||
v->erase(itr->get());
|
||||
|
||||
(*itr)->release_lifetime();
|
||||
torrent::download_remove(*(*itr)->download());
|
||||
|
||||
return base_type::erase(itr);
|
||||
@@ -282,7 +275,6 @@ void
|
||||
DownloadList::close_directly(Download* download) {
|
||||
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly.");
|
||||
|
||||
auto lifetime = download->lifetime();
|
||||
bool was_active = download->download()->info()->is_active();
|
||||
bool was_open = download->download()->info()->is_open();
|
||||
|
||||
@@ -291,19 +283,11 @@ DownloadList::close_directly(Download* download) {
|
||||
|
||||
if (was_active) {
|
||||
DL_TRIGGER_EVENT(download, "event.download.paused");
|
||||
|
||||
if (lifetime.expired())
|
||||
return;
|
||||
|
||||
update_paused_state(download);
|
||||
}
|
||||
|
||||
if (was_open) {
|
||||
DL_TRIGGER_EVENT(download, "event.download.hash_removed");
|
||||
|
||||
if (lifetime.expired())
|
||||
return;
|
||||
|
||||
DL_TRIGGER_EVENT(download, "event.download.closed");
|
||||
}
|
||||
}
|
||||
@@ -317,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)) + 1, 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));
|
||||
|
||||
// If initial seeding is complete, don't try it again when restarting.
|
||||
if (download->is_done() &&
|
||||
@@ -346,13 +330,8 @@ DownloadList::close_throw(Download* download) {
|
||||
// When pause gets called it will clear the initial hash check state
|
||||
// and set hash failed. This should ensure hashing doesn't restart
|
||||
// until resume gets called.
|
||||
auto lifetime = download->lifetime();
|
||||
|
||||
pause(download);
|
||||
|
||||
if (lifetime.expired())
|
||||
return;
|
||||
|
||||
// Check for is_open after pause due to hashing.
|
||||
if (!download->is_open())
|
||||
return;
|
||||
@@ -372,10 +351,6 @@ DownloadList::close_throw(Download* download) {
|
||||
throw torrent::internal_error("DownloadList::close_throw(...) called but we're going into a hashing loop.");
|
||||
|
||||
DL_TRIGGER_EVENT(download, "event.download.hash_removed");
|
||||
|
||||
if (lifetime.expired())
|
||||
return;
|
||||
|
||||
DL_TRIGGER_EVENT(download, "event.download.closed");
|
||||
}
|
||||
|
||||
@@ -482,8 +457,6 @@ DownloadList::pause(Download* download, int flags) {
|
||||
|
||||
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Pausing download: flags:%0x.", flags);
|
||||
|
||||
auto lifetime = download->lifetime();
|
||||
|
||||
try {
|
||||
|
||||
download->set_resume_flags(Download::default_resume_flags);
|
||||
@@ -497,9 +470,6 @@ DownloadList::pause(Download* download, int flags) {
|
||||
rpc::call_command_set_value("d.hashing.set", Download::variable_hashing_stopped, rpc::make_target(download));
|
||||
|
||||
DL_TRIGGER_EVENT(download, "event.download.hash_removed");
|
||||
|
||||
if (lifetime.expired())
|
||||
return;
|
||||
}
|
||||
|
||||
if (!download->download()->info()->is_active())
|
||||
@@ -513,9 +483,6 @@ DownloadList::pause(Download* download, int flags) {
|
||||
// view.
|
||||
DL_TRIGGER_EVENT(download, "event.download.paused");
|
||||
|
||||
if (lifetime.expired())
|
||||
return;
|
||||
|
||||
update_paused_state(download);
|
||||
|
||||
// Save the state after all the slots, etc have been called so we
|
||||
@@ -596,15 +563,9 @@ DownloadList::hash_done(Download* download) {
|
||||
rpc::call_command("d.complete.set", (int64_t)download->is_done(), rpc::make_target(download));
|
||||
torrent::resume_save_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume"));
|
||||
|
||||
if (rpc::call_command_value("d.state", rpc::make_target(download)) == 1) {
|
||||
auto lifetime = download->lifetime();
|
||||
|
||||
if (rpc::call_command_value("d.state", rpc::make_target(download)) == 1)
|
||||
resume(download, download->resume_flags());
|
||||
|
||||
if (lifetime.expired())
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Download::variable_hashing_last:
|
||||
@@ -641,24 +602,11 @@ DownloadList::hash_queue(Download* download, int type) {
|
||||
|
||||
// HACK
|
||||
if (download->is_open()) {
|
||||
auto lifetime = download->lifetime();
|
||||
|
||||
pause(download, torrent::Download::stop_skip_tracker);
|
||||
|
||||
if (lifetime.expired())
|
||||
return;
|
||||
|
||||
download->download()->close();
|
||||
|
||||
DL_TRIGGER_EVENT(download, "event.download.hash_removed");
|
||||
|
||||
if (lifetime.expired())
|
||||
return;
|
||||
|
||||
DL_TRIGGER_EVENT(download, "event.download.closed");
|
||||
|
||||
if (lifetime.expired())
|
||||
return;
|
||||
}
|
||||
|
||||
torrent::resume_clear_progress(*download->download(), download->download()->bencode()->get_key("libtorrent_resume"));
|
||||
@@ -734,12 +682,12 @@ DownloadList::confirm_finished(Download* download) {
|
||||
// up/downloaded baseline.
|
||||
download->download()->send_completed();
|
||||
|
||||
// The finished event may erase the download.
|
||||
auto lifetime = download->lifetime();
|
||||
// Save the hash in case the finished event erases it.
|
||||
torrent::HashString infohash = download->info()->hash();
|
||||
|
||||
DL_TRIGGER_EVENT(download, "event.download.finished");
|
||||
|
||||
if (lifetime.expired())
|
||||
if (find(infohash) == end())
|
||||
return;
|
||||
|
||||
// if (download->resume_flags() != Download::default_resume_flags)
|
||||
@@ -778,10 +726,11 @@ DownloadList::process_meta_download(Download* download) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto bencode = std::make_unique<torrent::Object>(torrent::Object::create_map());
|
||||
torrent::Object* bencode = new 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;
|
||||
}
|
||||
@@ -797,7 +746,7 @@ DownloadList::process_meta_download(Download* download) {
|
||||
|
||||
erase_ptr(download);
|
||||
|
||||
control->core()->try_create_download_from_meta_download(std::move(bencode), metafile);
|
||||
control->core()->try_create_download_from_meta_download(bencode, metafile);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-6
@@ -195,14 +195,14 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
|
||||
return;
|
||||
|
||||
// Adding download.
|
||||
auto f = std::make_unique<DownloadFactory>(this);
|
||||
DownloadFactory* f = new 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([factory = f.get()]() { delete factory; });
|
||||
f->slot_finished([f]() { delete f; });
|
||||
|
||||
if (is_data_uri(uri)) {
|
||||
// Allow the use of data URIs, primarily for JSON-RPC which
|
||||
@@ -216,13 +216,10 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
|
||||
}
|
||||
|
||||
f->commit();
|
||||
|
||||
// From here the finished slot deletes it.
|
||||
f.release();
|
||||
}
|
||||
|
||||
void
|
||||
Manager::try_create_download_from_meta_download(std::unique_ptr<torrent::Object> bencode, const std::string& metafile) {
|
||||
Manager::try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile) {
|
||||
DownloadFactory* f = new DownloadFactory(this);
|
||||
|
||||
f->variables()["tied_to_file"] = (int64_t)true;
|
||||
|
||||
+1
-1
@@ -76,7 +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(std::unique_ptr<torrent::Object> bencode, const std::string& metafile);
|
||||
void try_create_download_from_meta_download(torrent::Object* bencode, const std::string& metafile);
|
||||
|
||||
private:
|
||||
void create_http(const std::string& uri);
|
||||
|
||||
+3
-3
@@ -216,7 +216,7 @@ View::set_not_visible(Download* download) {
|
||||
|
||||
void
|
||||
View::next_focus(unsigned int i) {
|
||||
if (empty_visible())
|
||||
if (empty())
|
||||
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_visible())
|
||||
if (empty())
|
||||
return;
|
||||
|
||||
// If at the boundary, roll over
|
||||
@@ -301,7 +301,7 @@ View::filter() {
|
||||
std::for_each(changed.begin(), splitChanged, [this](const auto& d) { rpc::call_object_d_nothrow(m_event_removed, d.get()); });
|
||||
|
||||
if (!m_event_added.is_empty())
|
||||
std::for_each(splitChanged, changed.end(), [this](const auto& d) { rpc::call_object_d_nothrow(m_event_added, d.get()); });
|
||||
std::for_each(changed.begin(), splitChanged, [this](const auto& d) { rpc::call_object_d_nothrow(m_event_added, d.get()); });
|
||||
|
||||
emit_changed();
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ 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, int width) {
|
||||
if (width < 0)
|
||||
width = 0;
|
||||
|
||||
wstring_width(const std::string& i_str, [[maybe_unused]] int width) {
|
||||
std::wstring result(width + 1, L' ');
|
||||
size_t length = std::mbstowcs(result.data(), i_str.c_str(), width);
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <torrent/torrent.h>
|
||||
#include <torrent/system/callbacks.h>
|
||||
#include <torrent/system/thread.h>
|
||||
@@ -43,20 +41,13 @@ WindowLog::redraw() {
|
||||
|
||||
int pos = m_canvas->height();
|
||||
|
||||
std::vector<torrent::log_entry> entries;
|
||||
for (iterator itr = m_log->end(), last = find_older(); itr != last && pos > 0; --pos) {
|
||||
itr--;
|
||||
|
||||
{
|
||||
std::lock_guard<torrent::log_buffer> guard(*m_log);
|
||||
|
||||
for (iterator itr = m_log->end(), last = find_older(); itr != last && entries.size() < static_cast<size_t>(pos); )
|
||||
entries.push_back(*--itr);
|
||||
}
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
char buffer[16];
|
||||
print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(entry.timestamp));
|
||||
print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(itr->timestamp));
|
||||
|
||||
m_canvas->print(0, --pos, "(%s) %s", buffer, entry.message.c_str());
|
||||
m_canvas->print(0, pos - 1, "(%s) %s", buffer, itr->message.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,13 +60,8 @@ WindowLog::receive_update() {
|
||||
if (!is_active())
|
||||
return;
|
||||
|
||||
std::iterator_traits<iterator>::difference_type height;
|
||||
|
||||
{
|
||||
std::lock_guard<torrent::log_buffer> guard(*m_log);
|
||||
|
||||
height = std::min(std::distance(find_older(), (iterator)m_log->end()), (std::iterator_traits<iterator>::difference_type)10);
|
||||
}
|
||||
auto itr = find_older();
|
||||
auto height = std::min(std::distance(itr, (iterator)m_log->end()), (std::iterator_traits<iterator>::difference_type)10);
|
||||
|
||||
if (height != m_max_height) {
|
||||
m_min_height = height != 0 ? 1 : 0;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "canvas.h"
|
||||
#include "utils.h"
|
||||
@@ -31,34 +29,24 @@ WindowLogComplete::redraw() {
|
||||
|
||||
int pos = m_canvas->height();
|
||||
|
||||
std::vector<torrent::log_entry> entries;
|
||||
|
||||
{
|
||||
std::lock_guard<torrent::log_buffer> guard(*m_log);
|
||||
|
||||
for (iterator itr = m_log->end(), last = m_log->begin(); itr != last && entries.size() < static_cast<size_t>(pos); )
|
||||
entries.push_back(*--itr);
|
||||
}
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
if (pos <= 0)
|
||||
break;
|
||||
for (iterator itr = m_log->end(), last = m_log->begin(); itr != last && pos > 0; ) {
|
||||
itr--;
|
||||
|
||||
char buffer[16];
|
||||
|
||||
// Use an arbitrary min width of 60 for allowing multiple
|
||||
// lines. This should ensure we don't mess up the display when the
|
||||
// screen is shrunk too much.
|
||||
unsigned int timeWidth = 3 + print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(entry.timestamp)) - buffer;
|
||||
unsigned int timeWidth = 3 + print_hhmmss_local(buffer, buffer + 16, static_cast<time_t>(itr->timestamp)) - buffer;
|
||||
|
||||
unsigned int logWidth = m_canvas->width() > 60 ? (m_canvas->width() - timeWidth) : (60 - timeWidth);
|
||||
unsigned int logHeight = (entry.message.size() + logWidth - 1) / logWidth;
|
||||
unsigned int logHeight = (itr->message.size() + logWidth - 1) / logWidth;
|
||||
|
||||
for (unsigned int j = logHeight; j > 0 && pos > 0; --j, --pos)
|
||||
if (j == 1)
|
||||
m_canvas->print(0, pos - 1, "(%s) %s", buffer, entry.message.substr(0, m_canvas->width() - timeWidth).c_str());
|
||||
m_canvas->print(0, pos - 1, "(%s) %s", buffer, itr->message.substr(0, m_canvas->width() - timeWidth).c_str());
|
||||
else
|
||||
m_canvas->print(timeWidth, pos - 1, "%s", entry.message.substr(logWidth * (j - 1), m_canvas->width() - timeWidth).c_str());
|
||||
m_canvas->print(timeWidth, pos - 1, "%s", itr->message.substr(logWidth * (j - 1), m_canvas->width() - timeWidth).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ namespace input {
|
||||
|
||||
void
|
||||
InputEvent::insert() {
|
||||
torrent::this_thread::poll()->open(this);
|
||||
torrent::this_thread::poll()->insert_read(this);
|
||||
torrent::this_thread::poll()->open_and_insert_read(this);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
+18
-15
@@ -112,8 +112,9 @@ main(int argc, char** argv) {
|
||||
|
||||
torrent::log_initialize();
|
||||
|
||||
// TODO: Create a fake thread object for initializing other processes and enabling logging.
|
||||
torrent::initialize_main_thread();
|
||||
torrent::runtime::initialize_worker_process_and_main_thread([argc, argv]() {
|
||||
parse_config_file(argc, argv, [](auto& path) { parse_config_file_comments("worker", path); });
|
||||
});
|
||||
|
||||
// Block SIGCHLD until all threads are created, then unblock on main-thread, to avoid SIGCHLD
|
||||
// interrupting other threads.
|
||||
@@ -125,9 +126,9 @@ main(int argc, char** argv) {
|
||||
SignalHandler::set_block(SIGCHLD);
|
||||
|
||||
// All signal handlers must restore errno if they return.
|
||||
SignalHandler::set_handler(SIGSEGV, std::bind(&do_panic, SIGSEGV));
|
||||
SignalHandler::set_handler(SIGILL, std::bind(&do_panic, SIGILL));
|
||||
SignalHandler::set_handler(SIGFPE, std::bind(&do_panic, SIGFPE));
|
||||
SignalHandler::set_handler(SIGSEGV, []() { do_panic(SIGSEGV); });
|
||||
SignalHandler::set_handler(SIGILL, []() { do_panic(SIGILL); });
|
||||
SignalHandler::set_handler(SIGFPE, []() { do_panic(SIGFPE); });
|
||||
|
||||
// Limited list of commands with the following format:
|
||||
//
|
||||
@@ -136,19 +137,17 @@ main(int argc, char** argv) {
|
||||
// # do:log.open_file=system,/usr/rakshasa/system.log
|
||||
// # do:log.add_output=system,system
|
||||
//
|
||||
parse_config_file(argc, argv, [](auto& path) {
|
||||
if (path.empty())
|
||||
return;
|
||||
|
||||
parse_config_file_comments(path);
|
||||
});
|
||||
// # do-worker:log.open_file=system,/usr/rakshasa/system-worker.log
|
||||
// # do-worker:log.add_output=system,system
|
||||
//
|
||||
parse_config_file(argc, argv, [](auto& path) { parse_config_file_comments("", path); });
|
||||
|
||||
control = new Control;
|
||||
|
||||
SignalHandler::set_handler(SIGINT, std::bind(&Control::receive_normal_shutdown, control));
|
||||
SignalHandler::set_handler(SIGHUP, std::bind(&Control::receive_normal_shutdown, control));
|
||||
SignalHandler::set_handler(SIGTERM, std::bind(&Control::receive_quick_shutdown, control));
|
||||
SignalHandler::set_handler(SIGWINCH, std::bind(&display::Manager::force_redraw, control->display()));
|
||||
SignalHandler::set_handler(SIGINT, []() { control->receive_normal_shutdown(); });
|
||||
SignalHandler::set_handler(SIGHUP, []() { control->receive_normal_shutdown(); });
|
||||
SignalHandler::set_handler(SIGTERM, []() { control->receive_quick_shutdown(); });
|
||||
SignalHandler::set_handler(SIGWINCH, []() { control->display()->force_redraw(); });
|
||||
|
||||
SignalHandler::set_sigaction_handler(SIGBUS, &handle_sigbus);
|
||||
|
||||
@@ -469,6 +468,8 @@ main(int argc, char** argv) {
|
||||
control->ui()->load_input_history();
|
||||
|
||||
torrent::net_thread::http_stack()->set_user_agent(USER_AGENT);
|
||||
|
||||
torrent::runtime::initialize_worker_process();
|
||||
torrent::runtime::initialize_network();
|
||||
|
||||
// Load session torrents and perform scheduled tasks to ensure session torrents are loaded
|
||||
@@ -550,6 +551,8 @@ handle_sigbus(int signum, siginfo_t* sa, [[maybe_unused]] void* ptr) {
|
||||
#else
|
||||
output << "Stack dump not enabled." << std::endl;
|
||||
#endif
|
||||
output << std::endl << "Error: " << std::strerror(sa->si_errno) << std::endl;
|
||||
|
||||
const char* signal_reason;
|
||||
|
||||
switch (sa->si_code) {
|
||||
|
||||
@@ -108,6 +108,6 @@ OptionParser::call_int_pair(slot_int_pair slot, const std::string& arg) {
|
||||
|
||||
if (a < 0 || b < 0)
|
||||
throw std::runtime_error("Invalid argument, \"" + arg + "\" should be positive numbers");
|
||||
|
||||
|
||||
slot(a, b);
|
||||
}
|
||||
|
||||
@@ -61,20 +61,14 @@ 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: " + key + ": " + e.what());
|
||||
m_slotErrorMessage("Scheduled command failed: " + item->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();
|
||||
|
||||
|
||||
+1
-47
@@ -2,10 +2,8 @@
|
||||
|
||||
#include "rpc/jsonrpc.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <torrent/common.h>
|
||||
#include <torrent/torrent.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
@@ -15,7 +13,6 @@
|
||||
#include "rpc/command_map.h"
|
||||
#include "rpc/nlohmann/json.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "rpc/scgi_task.h"
|
||||
#include "torrent/exceptions.h"
|
||||
#include "torrent/object.h"
|
||||
#include "utils/functional.h"
|
||||
@@ -225,49 +222,13 @@ handle_notification(const json& request) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using json_input_adapter = decltype(nlohmann::detail::input_adapter(std::declval<const char*>(), std::declval<const char*>()));
|
||||
using json_dom_parser = nlohmann::detail::json_sax_dom_parser<json, json_input_adapter>;
|
||||
|
||||
class json_depth_limited_parser : public json_dom_parser {
|
||||
public:
|
||||
explicit json_depth_limited_parser(json& root) : json_dom_parser(root) {}
|
||||
|
||||
bool start_object(std::size_t length) { return enter() && json_dom_parser::start_object(length); }
|
||||
bool start_array(std::size_t length) { return enter() && json_dom_parser::start_array(length); }
|
||||
|
||||
bool end_object() { m_depth--; return json_dom_parser::end_object(); }
|
||||
bool end_array() { m_depth--; return json_dom_parser::end_array(); }
|
||||
|
||||
private:
|
||||
bool enter() { return ++m_depth <= max_json_depth; }
|
||||
|
||||
uint32_t m_depth{0};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool
|
||||
JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
|
||||
json response;
|
||||
json body;
|
||||
|
||||
if (length > m_size_limit) {
|
||||
auto err_str = json_error(JSONRPC_INVALID_REQUEST_ERROR, "content size exceeds maximum RPC limit", nullptr).dump();
|
||||
|
||||
return callback(err_str.c_str(), err_str.size());
|
||||
}
|
||||
|
||||
try {
|
||||
json_depth_limited_parser handler(body);
|
||||
|
||||
if (!json::sax_parse(in_buffer, in_buffer + length, &handler)) {
|
||||
auto err_str = json_error(JSONRPC_INVALID_REQUEST_ERROR, "maximum nesting depth exceeded", nullptr).dump();
|
||||
|
||||
return callback(err_str.c_str(), err_str.size());
|
||||
}
|
||||
|
||||
body = json::parse(in_buffer, in_buffer + length);
|
||||
switch (body.type()) {
|
||||
case json::value_t::object: {
|
||||
if (!body.contains("id")) {
|
||||
@@ -303,13 +264,6 @@ JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
|
||||
|
||||
std::string response_str = response.dump();
|
||||
|
||||
if (response_str.size() > SCgiTask::max_response_size) {
|
||||
const auto& id = response.is_object() && response.contains("id") ? response["id"] : json(nullptr);
|
||||
auto err_str = json_error(JSONRPC_INTERNAL_ERROR, "response size exceeds maximum RPC limit", id).dump();
|
||||
|
||||
return callback(err_str.c_str(), err_str.size());
|
||||
}
|
||||
|
||||
return callback(response_str.c_str(), response_str.size());
|
||||
|
||||
} catch (json::exception& e) {
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "rpc/scgi_task.h"
|
||||
|
||||
namespace rpc {
|
||||
|
||||
class JsonRpc {
|
||||
@@ -19,11 +17,6 @@ public:
|
||||
bool process(const char* in_buffer, uint32_t length, slot_write callback);
|
||||
|
||||
void insert_command(const char* name, const char* parm, const char* doc) {};
|
||||
|
||||
void set_size_limit(uint64_t size) { m_size_limit = size; }
|
||||
|
||||
private:
|
||||
uint64_t m_size_limit{SCgiTask::max_content_size};
|
||||
};
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
@@ -26,7 +26,10 @@ const size_t object_storage::key_size;
|
||||
|
||||
object_storage::iterator
|
||||
object_storage::find_raw_string(const torrent::raw_string& key) {
|
||||
return find(key_type::from_raw_string(key));
|
||||
fixed_key_type<64> k;
|
||||
k.set_c_str(key.data());
|
||||
|
||||
return find(k);
|
||||
}
|
||||
|
||||
object_storage::iterator
|
||||
@@ -61,12 +64,8 @@ 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.");
|
||||
|
||||
// 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.");
|
||||
// Check for size > key_size.
|
||||
// Check for empty string.
|
||||
|
||||
bool use_raw = false;
|
||||
torrent::Object object;
|
||||
|
||||
@@ -158,7 +158,14 @@ fixed_key_type<MaxSize>::set_data(const value_type* src_data, size_type src_size
|
||||
|
||||
template <size_t MaxSize> inline void
|
||||
fixed_key_type<MaxSize>::set_c_str(const value_type* src_data) {
|
||||
set_c_str(src_data, std::strlen(src_data));
|
||||
value_type* itr = m_data;
|
||||
const value_type* last = m_data + max_size;
|
||||
|
||||
while (itr != last && *src_data != '\0')
|
||||
*itr++ = *src_data++;
|
||||
|
||||
*itr = '\0';
|
||||
m_size = std::distance(m_data, itr);
|
||||
}
|
||||
|
||||
template <size_t MaxSize> inline void
|
||||
|
||||
@@ -181,11 +181,6 @@ 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());
|
||||
|
||||
|
||||
@@ -172,11 +172,7 @@ RpcManager::set_size_limit(uint64_t size) {
|
||||
if (size > SCgiTask::max_content_size)
|
||||
throw torrent::input_error("XMLRPC size limit cannot exceed the SCGI content size limit.");
|
||||
|
||||
if (size < min_size_limit)
|
||||
throw torrent::input_error("XMLRPC size limit is too small to hold a request.");
|
||||
|
||||
m_xmlrpc.set_size_limit(size);
|
||||
m_jsonrpc.set_size_limit(size);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -53,10 +53,6 @@ public:
|
||||
enum RPCType { XML,
|
||||
JSON };
|
||||
|
||||
// A limit below the shortest useful request rejects every request, the one
|
||||
// that would raise it again included.
|
||||
static constexpr uint64_t min_size_limit = 1024;
|
||||
|
||||
RpcManager() = default;
|
||||
~RpcManager() = default;
|
||||
|
||||
|
||||
+1
-2
@@ -109,8 +109,7 @@ void
|
||||
SCgi::activate() {
|
||||
assert(torrent::this_thread::thread() == scgi_thread::thread());
|
||||
|
||||
torrent::this_thread::poll()->open(this);
|
||||
torrent::this_thread::poll()->insert_read(this);
|
||||
torrent::this_thread::poll()->open_and_insert_read(this);
|
||||
}
|
||||
|
||||
// TODO: This should close the fd to avoid reuse.
|
||||
|
||||
@@ -50,8 +50,7 @@ SCgiTask::open(SCgi* parent, int fd) {
|
||||
// m_trusted=false into the next reuse, given that the
|
||||
// UNTRUSTED_CONNECTION=0 parse branch is a no-op.
|
||||
|
||||
torrent::this_thread::poll()->open(this);
|
||||
torrent::this_thread::poll()->insert_read(this);
|
||||
torrent::this_thread::poll()->open_and_insert_read(this);
|
||||
|
||||
torrent::this_thread::scheduler()->update_wait_for_ceil_seconds(&m_task_timeout, timeout_request);
|
||||
|
||||
@@ -93,8 +92,7 @@ SCgiTask::close() {
|
||||
// The callbacks are guaranteed to be finished/canceled at this point.
|
||||
auto lock = std::lock_guard<std::mutex>(m_result_mutex);
|
||||
|
||||
// clear() would keep the capacity, and the task is pooled for the lifetime of the process.
|
||||
std::vector<char>().swap(m_buffer);
|
||||
m_buffer.clear();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -405,7 +403,7 @@ void
|
||||
SCgiTask::receive_write(const char* buffer, uint32_t length) {
|
||||
assert(torrent::this_thread::thread() == torrent::main_thread::thread());
|
||||
|
||||
if (buffer == nullptr || length > max_response_size)
|
||||
if (buffer == nullptr || length > (100 << 20))
|
||||
throw torrent::internal_error("SCgiTask::receive_write(...) received bad input.");
|
||||
|
||||
// Main thread callback already locked this mutex.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define RTORRENT_RPC_SCGI_TASK_H
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
@@ -19,8 +18,6 @@ public:
|
||||
static constexpr int max_header_size = 2000;
|
||||
static constexpr int max_content_size = (1 << 26);
|
||||
|
||||
static constexpr uint32_t max_response_size = (100 << 20);
|
||||
|
||||
static constexpr auto timeout_request = std::chrono::seconds(60);
|
||||
|
||||
enum ContentType { XML, JSON };
|
||||
|
||||
+7
-6
@@ -27,11 +27,11 @@ public:
|
||||
virtual ~xmlrpc_error_c() throw() {}
|
||||
|
||||
virtual int type() const throw() { return m_type; }
|
||||
virtual const char* what() const throw() { return m_msg.c_str(); }
|
||||
virtual const char* what() const throw() { return m_msg; }
|
||||
|
||||
private:
|
||||
int m_type;
|
||||
std::string m_msg;
|
||||
const char* m_msg;
|
||||
};
|
||||
|
||||
torrent::Object xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int call_type = 0, rpc::target_type* target = NULL, std::function<void()>* deleter = NULL);
|
||||
@@ -44,9 +44,10 @@ xmlrpc_list_entry_to_object(xmlrpc_env* env, xmlrpc_value* src, int index) {
|
||||
if (env->fault_occurred)
|
||||
throw xmlrpc_error_c(env);
|
||||
|
||||
utils::scope_guard guard([tmp]() { xmlrpc_DECREF(tmp); });
|
||||
torrent::Object obj = xmlrpc_to_object(env, tmp);
|
||||
xmlrpc_DECREF(tmp);
|
||||
|
||||
return xmlrpc_to_object(env, tmp);
|
||||
return obj;
|
||||
}
|
||||
|
||||
int64_t
|
||||
@@ -212,11 +213,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);
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "rpc/tinyxml2/tinyxml2.h"
|
||||
#include "rpc/rpc_manager.h"
|
||||
#include "utils/base64.h"
|
||||
#include "utils/functional.h"
|
||||
#include "xmlrpc.h"
|
||||
|
||||
namespace rpc {
|
||||
@@ -259,8 +258,6 @@ 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) {
|
||||
@@ -268,6 +265,8 @@ 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");
|
||||
|
||||
@@ -283,6 +282,8 @@ 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");
|
||||
|
||||
@@ -326,20 +327,15 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
|
||||
auto& result_list = result.as_list();
|
||||
auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"});
|
||||
for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) {
|
||||
auto method_name_member = element_access(child, {"struct", "member"});
|
||||
auto member_name = method_name_member->FirstChildElement("name");
|
||||
|
||||
if (member_name == nullptr || member_name->GetText() == nullptr ||
|
||||
std::strncmp(member_name->GetText(), "methodName", sizeof("methodName")) != 0)
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "multicall struct's first member must be methodName");
|
||||
|
||||
auto sub_method_name = element_access(method_name_member, {"value", "string"})->GetText();
|
||||
auto sub_method_name = element_access(child, {"struct", "member", "value", "string"})->GetText();
|
||||
|
||||
if (sub_method_name == nullptr)
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "multicall methodName element is empty");
|
||||
// If sub_params ends up a nullptr at the end of this if-chian,
|
||||
// execute_command will turn it into an empty list
|
||||
auto sub_params = method_name_member->NextSiblingElement("member");
|
||||
auto sub_params = element_access(child, {"struct", "member"});
|
||||
if (sub_params != nullptr)
|
||||
sub_params = sub_params->NextSiblingElement("member");
|
||||
if (sub_params != nullptr)
|
||||
sub_params = sub_params->FirstChildElement("value");
|
||||
if (sub_params != nullptr)
|
||||
@@ -430,14 +426,6 @@ XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
|
||||
// remains.
|
||||
tinyxml2::XMLPrinter printer(nullptr, true, 0);
|
||||
process_document(&doc, &printer);
|
||||
|
||||
if (printer.CStrSize() - 1 > static_cast<int>(SCgiTask::max_response_size)) {
|
||||
tinyxml2::XMLPrinter fault_printer(nullptr, true, 0);
|
||||
print_xmlrpc_fault(XMLRPC_LIMIT_EXCEEDED_ERROR, "Response size exceeds maximum XML-RPC limit", &fault_printer);
|
||||
|
||||
return slotWrite(fault_printer.CStr(), fault_printer.CStrSize() - 1);
|
||||
}
|
||||
|
||||
return slotWrite(printer.CStr(), printer.CStrSize() - 1);
|
||||
} catch (rpc_error& e) {
|
||||
tinyxml2::XMLPrinter printer(nullptr, true, 0);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "download_storer.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
@@ -117,36 +116,28 @@ is_correct_format(const std::string& f) {
|
||||
|
||||
void
|
||||
save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream& stream) {
|
||||
// Remove any leftover temporary file first so that O_EXCL only ever fails on
|
||||
// an entry that appeared after the unlink, and O_NOFOLLOW keeps a symlink
|
||||
// planted in the session directory from redirecting the write.
|
||||
if (::unlink(path.c_str()) == -1 && errno != ENOENT)
|
||||
throw torrent::storage_error("failed to remove stale file : " + path);
|
||||
std::fstream output(path.c_str(), std::ios::out | std::ios::trunc);
|
||||
|
||||
// TODO: If we cannot open more files, wait for some to finish and try again.
|
||||
int fd = ::open(path.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
|
||||
|
||||
if (fd < 0)
|
||||
if (!output.is_open())
|
||||
throw torrent::storage_error("failed to open file for writing : " + path);
|
||||
|
||||
const auto data = stream.view();
|
||||
std::size_t remaining = data.size();
|
||||
const char* cursor = data.data();
|
||||
output << stream.rdbuf();
|
||||
|
||||
while (remaining != 0) {
|
||||
ssize_t result = ::write(fd, cursor, remaining);
|
||||
if (!output.good())
|
||||
throw torrent::storage_error("failed to write stream to file : " + path);
|
||||
|
||||
if (result == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
// The data only reaches the kernel here, so this is where a full disk is seen.
|
||||
output.close();
|
||||
|
||||
::close(fd);
|
||||
throw torrent::storage_error("failed to write stream to file : " + path);
|
||||
}
|
||||
if (!output.good())
|
||||
throw torrent::storage_error("failed to flush stream to file : " + path);
|
||||
|
||||
cursor += result;
|
||||
remaining -= result;
|
||||
}
|
||||
// Ensure that the new file is actually written to the disk
|
||||
int fd = ::open(path.c_str(), O_WRONLY);
|
||||
|
||||
if (fd < 0)
|
||||
throw torrent::storage_error("failed to open file descriptor for fsync : " + path);
|
||||
|
||||
if (use_fsyncdisk) {
|
||||
#ifdef __APPLE__
|
||||
@@ -161,7 +152,6 @@ save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream
|
||||
}
|
||||
}
|
||||
|
||||
// A full disk may only be seen when the descriptor is closed.
|
||||
if (::close(fd) == -1)
|
||||
throw torrent::storage_error("failed to close file descriptor : " + path);
|
||||
}
|
||||
|
||||
+11
-11
@@ -86,13 +86,9 @@ config_comment_log(const std::string& command, const std::string& raw_args) {
|
||||
pos = next_pos + 1;
|
||||
}
|
||||
|
||||
if (command == "log.add_output") {
|
||||
if (args.size() != 2)
|
||||
throw torrent::input_error("Invalid number of arguments.");
|
||||
|
||||
if (command == "log.add_output")
|
||||
log_add_group_output_str(args[0], args[1]);
|
||||
|
||||
} else if (command == "log.open_file")
|
||||
else if (command == "log.open_file")
|
||||
apply_log_open_str(0, args);
|
||||
else if (command == "log.open_file.flush")
|
||||
apply_log_open_str(log_flag_flush, args);
|
||||
@@ -112,18 +108,22 @@ config_comment_log(const std::string& command, const std::string& raw_args) {
|
||||
throw torrent::input_error("Unknown log command: " + command);
|
||||
}
|
||||
|
||||
// Call special commands in the format "# do:command=args" in the config file.
|
||||
// Call special commands in the format "# do:command=args" or "# do-<category>:command=args" in the config file.
|
||||
void
|
||||
parse_config_file_comments(const std::string& path) {
|
||||
parse_config_file_comments(const std::string& category, const std::string& path) {
|
||||
if (path.empty())
|
||||
return;
|
||||
|
||||
std::fstream file(path, std::ios::in);
|
||||
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
std::string line;
|
||||
std::string prefix = category.empty() ? "# do:" : "# do-" + category + ":";
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
if (line.size() <= 5 || line.compare(0, 5, "# do:") != 0)
|
||||
if (line.size() <= prefix.size() || line.compare(0, prefix.size(), prefix) != 0)
|
||||
continue;
|
||||
|
||||
auto equal_pos = line.find('=');
|
||||
@@ -131,8 +131,8 @@ parse_config_file_comments(const std::string& path) {
|
||||
if (equal_pos == std::string::npos)
|
||||
throw torrent::input_error("Invalid command in config file comment: " + line);
|
||||
|
||||
std::string command = line.substr(5, equal_pos - 5);
|
||||
std::string args = line.substr(equal_pos + 1);
|
||||
auto command = line.substr(prefix.size(), equal_pos - prefix.size());
|
||||
auto args = line.substr(equal_pos + 1);
|
||||
|
||||
if (command.empty())
|
||||
throw torrent::input_error("Invalid command in config file comment: " + line);
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
|
||||
int parse_main_options(int argc, char** argv);
|
||||
void parse_config_file(int argc, char** argv, std::function<void (const std::string&)> parse_fn);
|
||||
void parse_config_file_comments(const std::string& path);
|
||||
void parse_config_file_comments(const std::string& category, const std::string& path);
|
||||
|
||||
void load_session_torrents(const std::string& path);
|
||||
void load_arg_torrents(char** first, char** last);
|
||||
|
||||
+8
-36
@@ -30,38 +30,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
std::string
|
||||
create_filter_pattern(const std::string& input) {
|
||||
std::string pattern = input;
|
||||
|
||||
if (pattern.empty())
|
||||
return pattern;
|
||||
|
||||
if (pattern.back() != '$')
|
||||
pattern = pattern + ".*";
|
||||
if (pattern.front() != '^')
|
||||
pattern = ".*" + pattern;
|
||||
|
||||
std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower);
|
||||
return pattern;
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
create_filter_command(const std::string& pattern) {
|
||||
torrent::Object name = torrent::Object::create_dict_key();
|
||||
name.as_dict_key() = "d.name";
|
||||
|
||||
torrent::Object args = torrent::Object::create_list();
|
||||
args.as_list().push_back(name);
|
||||
args.as_list().push_back(torrent::Object(pattern));
|
||||
|
||||
torrent::Object command = torrent::Object::create_dict_key();
|
||||
command.as_dict_key() = "match";
|
||||
command.as_dict_obj() = args;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
DownloadList::DownloadList() {
|
||||
m_uiArray[DISPLAY_DOWNLOAD] = NULL;
|
||||
m_uiArray[DISPLAY_DOWNLOAD_LIST] = new ElementDownloadList();
|
||||
@@ -351,12 +319,16 @@ DownloadList::receive_exit_input(Input type) {
|
||||
current_view()->filter();
|
||||
current_view()->sort();
|
||||
} else {
|
||||
std::string pattern = create_filter_pattern(input->str());
|
||||
|
||||
std::string pattern = input->str();
|
||||
if (pattern.back() != '$')
|
||||
pattern = pattern + ".*";
|
||||
if (pattern.front() != '^')
|
||||
pattern = ".*" + pattern;
|
||||
std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower);
|
||||
std::string temp_filter = "match={d.name=," + pattern + "}";
|
||||
if (rpc::call_command_value("view.filter.temp.log"))
|
||||
control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern);
|
||||
|
||||
current_view()->set_filter_temp(create_filter_command(pattern));
|
||||
current_view()->set_filter_temp(temp_filter);
|
||||
current_view()->filter();
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#ifndef RTORRENT_UI_DOWNLOAD_LIST_H
|
||||
#define RTORRENT_UI_DOWNLOAD_LIST_H
|
||||
|
||||
#include <string>
|
||||
#include <torrent/object.h>
|
||||
|
||||
#include "element_base.h"
|
||||
#include "globals.h"
|
||||
#include "display/manager.h"
|
||||
@@ -32,12 +29,6 @@ namespace ui {
|
||||
|
||||
class Download;
|
||||
|
||||
// Wrap a user-typed download name pattern in the anchors 'match' expects.
|
||||
std::string create_filter_pattern(const std::string& input);
|
||||
|
||||
// Build the 'match' command the temporary view filter runs against d.name.
|
||||
torrent::Object create_filter_command(const std::string& pattern);
|
||||
|
||||
class DownloadList : public ElementBase {
|
||||
public:
|
||||
typedef display::WindowDownloadList WList;
|
||||
|
||||
+2
-3
@@ -467,9 +467,6 @@ 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;
|
||||
@@ -477,6 +474,8 @@ 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());
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,10 @@ public:
|
||||
void set_down_throttle(unsigned int throttle);
|
||||
void set_up_throttle(unsigned int throttle);
|
||||
|
||||
// Rename to raw or something, make base function.
|
||||
void set_down_throttle_i64(int64_t throttle) { set_down_throttle(throttle >> 10); }
|
||||
void set_up_throttle_i64(int64_t throttle) { set_up_throttle(throttle >> 10); }
|
||||
|
||||
void adjust_down_throttle(int throttle);
|
||||
void adjust_up_throttle(int throttle);
|
||||
|
||||
|
||||
+5
-27
@@ -5,7 +5,6 @@
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <functional>
|
||||
#include <sys/stat.h>
|
||||
#include <torrent/exceptions.h>
|
||||
@@ -14,24 +13,6 @@
|
||||
|
||||
namespace utils {
|
||||
|
||||
namespace {
|
||||
|
||||
uint8_t
|
||||
entry_type_from_mode(mode_t mode) {
|
||||
if (S_ISREG(mode))
|
||||
return DT_REG;
|
||||
|
||||
if (S_ISDIR(mode))
|
||||
return DT_DIR;
|
||||
|
||||
if (S_ISLNK(mode))
|
||||
return DT_LNK;
|
||||
|
||||
return DT_UNKNOWN;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Keep this?
|
||||
bool
|
||||
Directory::is_valid() const {
|
||||
@@ -57,6 +38,9 @@ Directory::update(int flags) {
|
||||
return false;
|
||||
|
||||
struct dirent* entry;
|
||||
#ifdef __sun__
|
||||
struct stat s;
|
||||
#endif
|
||||
|
||||
while ((entry = readdir(d)) != NULL) {
|
||||
if ((flags & update_hide_dot) && entry->d_name[0] == '.')
|
||||
@@ -65,22 +49,16 @@ Directory::update(int flags) {
|
||||
iterator itr = base_type::insert(end(), value_type());
|
||||
|
||||
#ifdef __sun__
|
||||
stat(entry->d_name, &s);
|
||||
itr->s_fileno = entry->d_ino;
|
||||
itr->s_reclen = 0;
|
||||
itr->s_type = DT_UNKNOWN;
|
||||
itr->s_type = s.st_mode;
|
||||
#else
|
||||
itr->s_fileno = entry->d_fileno;
|
||||
itr->s_reclen = entry->d_reclen;
|
||||
itr->s_type = entry->d_type;
|
||||
#endif
|
||||
|
||||
if (itr->s_type == DT_UNKNOWN) {
|
||||
struct stat st;
|
||||
|
||||
if (fstatat(dirfd(d), entry->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0)
|
||||
itr->s_type = entry_type_from_mode(st.st_mode);
|
||||
}
|
||||
|
||||
#ifdef DIRENT_NAMLEN_EXISTS_FOOBAR
|
||||
itr->s_name = std::string(entry->d_name, entry->d_name + entry->d_namlen);
|
||||
#else
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
#define RTORRENT_UTILS_DIRECTORY_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <dirent.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace utils {
|
||||
|
||||
struct directory_entry {
|
||||
bool is_file() const { return s_type == DT_REG; }
|
||||
// Fix.
|
||||
bool is_file() const { return true; }
|
||||
|
||||
// The name and types should match POSIX.
|
||||
uint32_t s_fileno;
|
||||
|
||||
+1
-19
@@ -1,5 +1,4 @@
|
||||
TESTS = \
|
||||
rtorrent_Test_Core \
|
||||
rtorrent_Test_Rpc \
|
||||
rtorrent_Test_Src
|
||||
|
||||
@@ -8,7 +7,6 @@ check_PROGRAMS = $(TESTS)
|
||||
rtorrent_Test_LDADD = \
|
||||
../src/libsub_root.a
|
||||
|
||||
rtorrent_Test_Core_LDADD = $(rtorrent_Test_LDADD)
|
||||
rtorrent_Test_Rpc_LDADD = $(rtorrent_Test_LDADD)
|
||||
rtorrent_Test_Src_LDADD = $(rtorrent_Test_LDADD)
|
||||
|
||||
@@ -32,10 +30,6 @@ rtorrent_Test_Common = \
|
||||
helpers/test_utils.h \
|
||||
helpers/utils.h
|
||||
|
||||
rtorrent_Test_Core_SOURCES = $(rtorrent_Test_Common) \
|
||||
core/test_view.cc \
|
||||
core/test_view.h
|
||||
|
||||
rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
|
||||
rpc/test_command.cc \
|
||||
rpc/test_command.h \
|
||||
@@ -52,9 +46,7 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
|
||||
rpc/test_object_storage.cc \
|
||||
rpc/test_object_storage.h \
|
||||
rpc/test_parse_options.cc \
|
||||
rpc/test_parse_options.h \
|
||||
rpc/test_rpc_manager.cc \
|
||||
rpc/test_rpc_manager.h
|
||||
rpc/test_parse_options.h
|
||||
|
||||
rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
|
||||
src/test_command_dynamic.cc \
|
||||
@@ -67,19 +59,9 @@ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
|
||||
src/test_command_path.h \
|
||||
src/test_command_string.cc \
|
||||
src/test_command_string.h \
|
||||
src/test_command_throttle.cc \
|
||||
src/test_command_throttle.h \
|
||||
src/test_session_storer.cc \
|
||||
src/test_session_storer.h \
|
||||
src/test_setup.cc \
|
||||
src/test_setup.h \
|
||||
src/test_ui_download_list.cc \
|
||||
src/test_ui_download_list.h \
|
||||
src/test_watch_ready_queue.cc \
|
||||
src/test_watch_ready_queue.h
|
||||
|
||||
rtorrent_Test_Core_CXXFLAGS = $(CPPUNIT_CFLAGS)
|
||||
rtorrent_Test_Core_LDFLAGS = $(CPPUNIT_LIBS) -ldl
|
||||
rtorrent_Test_Rpc_CXXFLAGS = $(CPPUNIT_CFLAGS)
|
||||
rtorrent_Test_Rpc_LDFLAGS = $(CPPUNIT_LIBS) -ldl
|
||||
rtorrent_Test_Src_CXXFLAGS = $(CPPUNIT_CFLAGS)
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "test/core/test_view.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "command_helpers.h"
|
||||
#include "control.h"
|
||||
#include "core/download.h"
|
||||
#include "core/view.h"
|
||||
#include "globals.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestView);
|
||||
|
||||
namespace {
|
||||
|
||||
const char* filter_command = "test.view.filter";
|
||||
const char* added_command = "test.view.event_added";
|
||||
const char* removed_command = "test.view.event_removed";
|
||||
|
||||
// View only stores these and hands them to the command layer as RPC targets,
|
||||
// which casts the pointer without reading through it.
|
||||
struct download_stub {
|
||||
alignas(core::Download) std::byte storage[sizeof(core::Download)];
|
||||
};
|
||||
|
||||
std::deque<download_stub> stub_storage;
|
||||
std::vector<core::Download*> stub_downloads;
|
||||
|
||||
std::set<core::Download*> matches_filter;
|
||||
std::vector<core::Download*> added_events;
|
||||
std::vector<core::Download*> removed_events;
|
||||
|
||||
std::shared_ptr<core::Download>
|
||||
make_stub_download() {
|
||||
auto download = reinterpret_cast<core::Download*>(&stub_storage.emplace_back());
|
||||
|
||||
stub_downloads.push_back(download);
|
||||
|
||||
return std::shared_ptr<core::Download>(download, [](core::Download*) {});
|
||||
}
|
||||
|
||||
// Renders a dispatch list as "d0,d2" so a failure names the downloads the
|
||||
// handler saw, not just how many there were.
|
||||
std::string
|
||||
describe(const std::vector<core::Download*>& downloads) {
|
||||
std::string result;
|
||||
|
||||
for (const auto& download : downloads) {
|
||||
auto itr = std::find(stub_downloads.begin(), stub_downloads.end(), download);
|
||||
|
||||
if (!result.empty())
|
||||
result += ',';
|
||||
|
||||
result += 'd' + std::to_string(std::distance(stub_downloads.begin(), itr));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
cmd_filter(core::Download* download, const torrent::Object&) {
|
||||
return torrent::Object(static_cast<int64_t>(matches_filter.count(download)));
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
cmd_event_added(core::Download* download, const torrent::Object&) {
|
||||
added_events.push_back(download);
|
||||
return torrent::Object();
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
cmd_event_removed(core::Download* download, const torrent::Object&) {
|
||||
removed_events.push_back(download);
|
||||
return torrent::Object();
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
command_object(const char* command) {
|
||||
return torrent::Object(std::string(command) + "=");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void
|
||||
TestView::setUp() {
|
||||
TestFixtureWithMainThread::setUp();
|
||||
|
||||
if (control == nullptr)
|
||||
control = new Control;
|
||||
|
||||
if (!rpc::commands.has(filter_command)) {
|
||||
CMD2_DL(filter_command, &cmd_filter);
|
||||
CMD2_DL(added_command, &cmd_event_added);
|
||||
CMD2_DL(removed_command, &cmd_event_removed);
|
||||
}
|
||||
|
||||
stub_storage.clear();
|
||||
stub_downloads.clear();
|
||||
matches_filter.clear();
|
||||
added_events.clear();
|
||||
removed_events.clear();
|
||||
}
|
||||
|
||||
void
|
||||
TestView::tearDown() {
|
||||
TestFixtureWithMainThread::tearDown();
|
||||
}
|
||||
|
||||
// d0 and d1 start visible and stop matching, d2 and d3 start filtered out and
|
||||
// start matching. The two halves are the same size on purpose: a test that
|
||||
// only counted the dispatches would pass even if both events were sent over
|
||||
// the same half of the changed range.
|
||||
void
|
||||
TestView::test_filter_dispatches_events_to_the_right_downloads() {
|
||||
core::View view;
|
||||
view.initialize("test_view");
|
||||
|
||||
auto d0 = make_stub_download();
|
||||
auto d1 = make_stub_download();
|
||||
auto d2 = make_stub_download();
|
||||
auto d3 = make_stub_download();
|
||||
|
||||
view.insert(d0);
|
||||
view.insert(d1);
|
||||
view.insert(d2);
|
||||
view.insert(d3);
|
||||
|
||||
view.set_visible(d0.get());
|
||||
view.set_visible(d1.get());
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(core::View::size_type(2), view.size_visible());
|
||||
|
||||
matches_filter = {d2.get(), d3.get()};
|
||||
|
||||
view.set_filter(command_object(filter_command));
|
||||
view.set_event_added(command_object(added_command));
|
||||
view.set_event_removed(command_object(removed_command));
|
||||
|
||||
added_events.clear();
|
||||
removed_events.clear();
|
||||
|
||||
view.filter();
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("d2,d3"), describe(added_events));
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("d0,d1"), describe(removed_events));
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(core::View::size_type(2), view.size_visible());
|
||||
CPPUNIT_ASSERT(*view.begin_visible() == d2);
|
||||
CPPUNIT_ASSERT(*(view.begin_visible() + 1) == d3);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#include "test/helpers/test_main_thread.h"
|
||||
|
||||
class TestView : public TestFixtureWithMainThread {
|
||||
CPPUNIT_TEST_SUITE(TestView);
|
||||
|
||||
CPPUNIT_TEST(test_filter_dispatches_events_to_the_right_downloads);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
void test_filter_dispatches_events_to_the_right_downloads();
|
||||
};
|
||||
@@ -8,18 +8,12 @@
|
||||
#include "globals.h"
|
||||
#include "command_helpers.h"
|
||||
#include "rpc/command_map.h"
|
||||
#include "rpc/scgi_task.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestJsonrpc);
|
||||
|
||||
torrent::Object
|
||||
jsonrpc_cmd_test_reflect([[maybe_unused]] rpc::target_type t, const torrent::Object& obj) { return obj; }
|
||||
|
||||
torrent::Object
|
||||
jsonrpc_cmd_test_oversized([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj) {
|
||||
return torrent::Object(std::string(rpc::SCgiTask::max_response_size + (1 << 20), 'a'));
|
||||
}
|
||||
|
||||
void initialize_command_dynamic();
|
||||
|
||||
// Name, Request, Expected response
|
||||
@@ -136,10 +130,6 @@ TestJsonrpc::setUp() {
|
||||
if (rpc::commands.find("jsonrpc_reflect") == rpc::commands.end()) {
|
||||
CMD2_ANY("jsonrpc_reflect", &jsonrpc_cmd_test_reflect);
|
||||
}
|
||||
|
||||
if (rpc::commands.find("jsonrpc_oversized") == rpc::commands.end()) {
|
||||
CMD2_ANY("jsonrpc_oversized", &jsonrpc_cmd_test_oversized);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -155,100 +145,3 @@ TestJsonrpc::test_basics() {
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(std::get<0>(test), std::get<2>(test), output);
|
||||
}
|
||||
}
|
||||
|
||||
// A command whose result does not fit in the SCGI response buffer must be
|
||||
// answered with a fault, not handed to the writer. SCgiTask::receive_write
|
||||
// treats an oversized body as an internal_error, which is not caught by any
|
||||
// RPC handler and terminates the process.
|
||||
void
|
||||
TestJsonrpc::test_response_size_limit() {
|
||||
const std::string request = R"({"jsonrpc": "2.0", "method": "jsonrpc_oversized", "params": [""], "id": 1})";
|
||||
const std::string expected = R"({"error":{"code":-32000,"message":"response size exceeds maximum RPC limit"},"id":1,"jsonrpc":"2.0"})";
|
||||
|
||||
std::string output;
|
||||
m_jsonrpc.process(request.c_str(), request.size(), [&output](const char* c, uint32_t l) { output.append(c, l); return true; });
|
||||
|
||||
CPPUNIT_ASSERT_MESSAGE("response handed to the writer is " + std::to_string(output.size()) +
|
||||
" bytes, over the " + std::to_string(rpc::SCgiTask::max_response_size) + " byte SCGI limit",
|
||||
output.size() <= rpc::SCgiTask::max_response_size);
|
||||
CPPUNIT_ASSERT_EQUAL(expected, output);
|
||||
}
|
||||
|
||||
// The bound is applied while the document is parsed: json_to_object only ever
|
||||
// walks "params", and by the time it runs the whole tree already exists.
|
||||
void
|
||||
TestJsonrpc::test_depth_limit() {
|
||||
// A JSON string holding a quote and brackets that must not be counted.
|
||||
const std::string tricky = R"("\"[[[")";
|
||||
|
||||
std::vector<std::tuple<std::string, std::string, std::string>> requests = {
|
||||
std::make_tuple("Nesting under the limit is accepted",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
|
||||
std::string(1000, '[') + std::string(1000, ']') + R"(], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[)" +
|
||||
std::string(1000, '[') + std::string(1000, ']') + R"(]})"),
|
||||
|
||||
// Nesting outside "params" is never converted, so json_to_object's own
|
||||
// bound never sees it.
|
||||
std::make_tuple("Nesting outside params is rejected",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""], "id": 1, "x": )" +
|
||||
std::string(2000, '[') + std::string(2000, ']') + R"(})",
|
||||
R"({"error":{"code":-32600,"message":"maximum nesting depth exceeded"},"id":null,"jsonrpc":"2.0"})"),
|
||||
|
||||
std::make_tuple("Nesting over the limit is rejected",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
|
||||
std::string(2000, '[') + std::string(2000, ']') + R"(], "id": 1})",
|
||||
R"({"error":{"code":-32600,"message":"maximum nesting depth exceeded"},"id":null,"jsonrpc":"2.0"})"),
|
||||
|
||||
std::make_tuple("Brackets inside a string are not nesting",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", ")" +
|
||||
std::string(2000, '[') + R"("], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[")" +
|
||||
std::string(2000, '[') + R"("]})"),
|
||||
|
||||
std::make_tuple("An escaped quote does not end a string",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" + tricky + R"(, ")" +
|
||||
std::string(2000, '[') + R"("], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[)" + tricky + R"(,")" +
|
||||
std::string(2000, '[') + R"("]})"),
|
||||
|
||||
// The bound is on the whole document, so the outer object and the params
|
||||
// array are two of the 1024 containers and 1022 are left for the payload.
|
||||
std::make_tuple("Nesting one below the limit is accepted",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
|
||||
std::string(1021, '[') + std::string(1021, ']') + R"(], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[)" +
|
||||
std::string(1021, '[') + std::string(1021, ']') + R"(]})"),
|
||||
|
||||
std::make_tuple("Nesting at the limit is accepted",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
|
||||
std::string(1022, '[') + std::string(1022, ']') + R"(], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[)" +
|
||||
std::string(1022, '[') + std::string(1022, ']') + R"(]})"),
|
||||
|
||||
std::make_tuple("Nesting one over the limit is rejected",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
|
||||
std::string(1023, '[') + std::string(1023, ']') + R"(], "id": 1})",
|
||||
R"({"error":{"code":-32600,"message":"maximum nesting depth exceeded"},"id":null,"jsonrpc":"2.0"})"),
|
||||
};
|
||||
|
||||
for (auto& test : requests) {
|
||||
std::string output;
|
||||
m_jsonrpc.process(std::get<1>(test).c_str(), std::get<1>(test).size(), [&output](const char* c, uint32_t l) { output.append(c, l); return true; });
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(std::get<0>(test), std::get<2>(test), output);
|
||||
}
|
||||
}
|
||||
|
||||
// network.xmlrpc.size_limit is the only knob bounding how much input a single
|
||||
// request may spend memory on, and it has to bound the JSON path too.
|
||||
void
|
||||
TestJsonrpc::test_size_limit() {
|
||||
const std::string request = R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""], "id": 1})";
|
||||
const std::string expected = R"({"error":{"code":-32600,"message":"content size exceeds maximum RPC limit"},"id":null,"jsonrpc":"2.0"})";
|
||||
|
||||
std::string output;
|
||||
m_jsonrpc.set_size_limit(1);
|
||||
m_jsonrpc.process(request.c_str(), request.size(), [&output](const char* c, uint32_t l) { output.append(c, l); return true; });
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(expected, output);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,6 @@ class TestJsonrpc : public test_fixture {
|
||||
CPPUNIT_TEST_SUITE(TestJsonrpc);
|
||||
|
||||
CPPUNIT_TEST(test_basics);
|
||||
CPPUNIT_TEST(test_response_size_limit);
|
||||
CPPUNIT_TEST(test_depth_limit);
|
||||
CPPUNIT_TEST(test_size_limit);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
@@ -19,9 +16,6 @@ public:
|
||||
void tearDown();
|
||||
|
||||
void test_basics();
|
||||
void test_response_size_limit();
|
||||
void test_depth_limit();
|
||||
void test_size_limit();
|
||||
|
||||
private:
|
||||
std::unique_ptr<TestMainThread> m_test_main_thread;
|
||||
|
||||
@@ -49,34 +49,6 @@ 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());
|
||||
|
||||
// An over-long key must not be truncated into a match either.
|
||||
CPPUNIT_ASSERT(m_storage.find_raw_string(torrent::raw_string::from_string(key_max + 'k')) == m_storage.end());
|
||||
|
||||
// set_c_str puts the terminating nul at m_data[size()], so size() has to
|
||||
// stay below max_size for that write to land inside the array.
|
||||
rpc::object_storage::key_type key_long;
|
||||
key_long.set_c_str(std::string(rpc::object_storage::key_size + 1, 'k').c_str());
|
||||
|
||||
CPPUNIT_ASSERT(key_long.size() < rpc::object_storage::key_size);
|
||||
|
||||
m_storage.clear();
|
||||
m_storage.insert_str("test_5", torrent::Object("a"), rpc::object_storage::flag_string_type);
|
||||
|
||||
// insert rejects a key holding a nul-char, so a lookup must not match past one.
|
||||
CPPUNIT_ASSERT(m_storage.find_raw_string(torrent::raw_string("test_5\0x", 8)) == m_storage.end());
|
||||
CPPUNIT_ASSERT(m_storage.find_raw_string(torrent::raw_string::from_c_str("test_5")) != m_storage.end());
|
||||
|
||||
m_storage.clear();
|
||||
}
|
||||
|
||||
// And test many other bad/good string combos.
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "test/rpc/test_rpc_manager.h"
|
||||
|
||||
#include <torrent/exceptions.h>
|
||||
|
||||
#include "rpc/scgi_task.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestRpcManager);
|
||||
|
||||
// A size limit too small to hold any request rejects every request, including
|
||||
// the one that would put it back, so it can only be undone by a restart.
|
||||
void
|
||||
TestRpcManager::test_size_limit_bounds() {
|
||||
CPPUNIT_ASSERT_THROW(m_rpc_manager.set_size_limit(0), torrent::input_error);
|
||||
CPPUNIT_ASSERT_THROW(m_rpc_manager.set_size_limit(rpc::RpcManager::min_size_limit - 1), torrent::input_error);
|
||||
CPPUNIT_ASSERT_THROW(m_rpc_manager.set_size_limit(rpc::SCgiTask::max_content_size + 1), torrent::input_error);
|
||||
|
||||
CPPUNIT_ASSERT_NO_THROW(m_rpc_manager.set_size_limit(rpc::RpcManager::min_size_limit));
|
||||
CPPUNIT_ASSERT_NO_THROW(m_rpc_manager.set_size_limit(rpc::SCgiTask::max_content_size));
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#include "test/helpers/test_fixture.h"
|
||||
|
||||
#include "rpc/rpc_manager.h"
|
||||
|
||||
class TestRpcManager : public test_fixture {
|
||||
CPPUNIT_TEST_SUITE(TestRpcManager);
|
||||
|
||||
CPPUNIT_TEST(test_size_limit_bounds);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void test_size_limit_bounds();
|
||||
|
||||
private:
|
||||
rpc::RpcManager m_rpc_manager;
|
||||
};
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "globals.h"
|
||||
#include "command_helpers.h"
|
||||
#include "rpc/command_map.h"
|
||||
#include "rpc/scgi_task.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestXmlrpc);
|
||||
|
||||
@@ -22,11 +21,6 @@ xmlrpc_cmd_test_reflect_string([[maybe_unused]] rpc::target_type t, const std::s
|
||||
return obj;
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
xmlrpc_cmd_test_oversized([[maybe_unused]] rpc::target_type t, [[maybe_unused]] const torrent::Object& obj) {
|
||||
return torrent::Object(std::string(rpc::SCgiTask::max_response_size + (1 << 20), 'a'));
|
||||
}
|
||||
|
||||
void initialize_command_dynamic();
|
||||
|
||||
#if defined(HAVE_XMLRPC_TINYXML2) && !defined(HAVE_XMLRPC_C)
|
||||
@@ -133,9 +127,6 @@ TestXmlrpc::setUp() {
|
||||
|
||||
if (rpc::commands.find("xmlrpc_reflect_string") == rpc::commands.end())
|
||||
CMD2_ANY_STRING("xmlrpc_reflect_string", &xmlrpc_cmd_test_reflect_string);
|
||||
|
||||
if (rpc::commands.find("xmlrpc_oversized") == rpc::commands.end())
|
||||
CMD2_ANY("xmlrpc_oversized", &xmlrpc_cmd_test_oversized);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -174,72 +165,11 @@ TestXmlrpc::test_size_limit() {
|
||||
CPPUNIT_ASSERT_EQUAL(expected, output);
|
||||
}
|
||||
|
||||
// A command whose result does not fit in the SCGI response buffer must be
|
||||
// answered with a fault, not handed to the writer. SCgiTask::receive_write
|
||||
// treats an oversized body as an internal_error, which is not caught by any
|
||||
// RPC handler and terminates the process.
|
||||
void
|
||||
TestXmlrpc::test_response_size_limit() {
|
||||
std::string input = "<?xml version=\"1.0\"?><methodCall><methodName>xmlrpc_oversized</methodName><params><param><value><string></string></value></param></params></methodCall>";
|
||||
std::string expected = "<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i8>-509</i8></value></member><member><name>faultString</name><value><string>Response size exceeds maximum XML-RPC limit</string></value></member></struct></value></fault></methodResponse>";
|
||||
std::string output;
|
||||
|
||||
m_xmlrpc.process(input.c_str(), input.size(), [&output](const char* c, uint32_t l){ output.append(c, l); return true;});
|
||||
|
||||
CPPUNIT_ASSERT_MESSAGE("response handed to the writer is " + std::to_string(output.size()) +
|
||||
" bytes, over the " + std::to_string(rpc::SCgiTask::max_response_size) + " byte SCGI limit",
|
||||
output.size() <= rpc::SCgiTask::max_response_size);
|
||||
CPPUNIT_ASSERT_EQUAL(expected, output);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const std::string multicall_method_name =
|
||||
"<member><name>methodName</name><value><string>xmlrpc_reflect</string></value></member>";
|
||||
const std::string multicall_params =
|
||||
"<member><name>params</name><value><array><data>"
|
||||
"<value><string></string></value><value><string>a</string></value>"
|
||||
"</data></array></value></member>";
|
||||
|
||||
std::string
|
||||
multicall_request(const std::string& members) {
|
||||
return "<?xml version=\"1.0\"?><methodCall><methodName>system.multicall</methodName>"
|
||||
"<params><param><value><array><data><value><struct>" + members +
|
||||
"</struct></value></data></array></value></param></params></methodCall>";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
TestXmlrpc::test_multicall_member_order() {
|
||||
auto call = [this](const std::string& input) {
|
||||
std::string output;
|
||||
m_xmlrpc.process(input.c_str(), input.size(), [&output](const char* c, uint32_t l){ output.append(c, l); return true;});
|
||||
return output;
|
||||
};
|
||||
|
||||
// methodName first is the only order accepted; the positive control proves
|
||||
// the harness drives the real code path rather than a stub.
|
||||
std::string ordered = call(multicall_request(multicall_method_name + multicall_params));
|
||||
CPPUNIT_ASSERT(ordered.find("faultCode") == std::string::npos);
|
||||
|
||||
// params before methodName is rejected with a clear top-level fault,
|
||||
// instead of the "could not find expected element string" of a positional read.
|
||||
std::string expected_fault =
|
||||
"<?xml version=\"1.0\"?><methodResponse><fault><value><struct>"
|
||||
"<member><name>faultCode</name><value><i8>-503</i8></value></member>"
|
||||
"<member><name>faultString</name><value><string>multicall struct's first member must be methodName</string></value></member>"
|
||||
"</struct></value></fault></methodResponse>";
|
||||
CPPUNIT_ASSERT_EQUAL(expected_fault, call(multicall_request(multicall_params + multicall_method_name)));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void TestXmlrpc::test_invalid_utf8() {}
|
||||
void TestXmlrpc::test_basics() {}
|
||||
void TestXmlrpc::test_size_limit() {}
|
||||
void TestXmlrpc::test_response_size_limit() {}
|
||||
void TestXmlrpc::test_multicall_member_order() {}
|
||||
void TestXmlrpc::setUp() {}
|
||||
void TestXmlrpc::tearDown() {}
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ class TestXmlrpc : public test_fixture {
|
||||
CPPUNIT_TEST(test_basics);
|
||||
CPPUNIT_TEST(test_invalid_utf8);
|
||||
CPPUNIT_TEST(test_size_limit);
|
||||
CPPUNIT_TEST(test_response_size_limit);
|
||||
CPPUNIT_TEST(test_multicall_member_order);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
@@ -24,8 +22,6 @@ public:
|
||||
void test_basics();
|
||||
void test_invalid_utf8();
|
||||
void test_size_limit();
|
||||
void test_response_size_limit();
|
||||
void test_multicall_member_order();
|
||||
|
||||
private:
|
||||
std::unique_ptr<TestMainThread> m_test_main_thread;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "test/src/test_command_dynamic.h"
|
||||
|
||||
#include "helpers/assert.h"
|
||||
|
||||
#include "control.h"
|
||||
#include "globals.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
@@ -63,51 +61,3 @@ 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());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandDynamic::test_value_base() {
|
||||
auto value = [](std::initializer_list<torrent::Object> objects) {
|
||||
auto args = torrent::Object::create_list();
|
||||
|
||||
for (const auto& object : objects)
|
||||
args.as_list().push_back(object);
|
||||
|
||||
return rpc::commands.call_command("value", args).as_value();
|
||||
};
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(int64_t(10), value({"10"}));
|
||||
CPPUNIT_ASSERT_EQUAL(int64_t(255), value({"ff", int64_t(16)}));
|
||||
|
||||
// strtoll only defines base 0 and base 2 through 36.
|
||||
ASSERT_CATCH_INPUT_ERROR( { value({"10", int64_t(1)}); } );
|
||||
ASSERT_CATCH_INPUT_ERROR( { value({"10", int64_t(37)}); } );
|
||||
ASSERT_CATCH_INPUT_ERROR( { value({"10", int64_t(-1)}); } );
|
||||
|
||||
// An out-of-range base must not be narrowed into a valid one.
|
||||
ASSERT_CATCH_INPUT_ERROR( { value({"10", int64_t(1) << 40}); } );
|
||||
ASSERT_CATCH_INPUT_ERROR( { value({"ff", (int64_t(1) << 32) + 16}); } );
|
||||
|
||||
// A number too large for the result must be rejected, not clamped.
|
||||
ASSERT_CATCH_INPUT_ERROR( { value({"99999999999999999999999"}); } );
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ 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(test_value_base);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
@@ -20,8 +18,6 @@ public:
|
||||
void test_get_set();
|
||||
|
||||
void test_old_style();
|
||||
void test_insert_list();
|
||||
void test_value_base();
|
||||
|
||||
private:
|
||||
std::unique_ptr<TestMainThread> m_test_main_thread;
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "test/src/test_command_throttle.h"
|
||||
|
||||
#include <torrent/throttle.h>
|
||||
#include <torrent/torrent.h>
|
||||
|
||||
#include "core/manager.h"
|
||||
#include "control.h"
|
||||
#include "globals.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandThrottle);
|
||||
|
||||
void initialize_command_throttle();
|
||||
|
||||
static void
|
||||
call_set(const char* key, const char* value) {
|
||||
rpc::commands.call_command(key, torrent::Object(std::string(value)));
|
||||
}
|
||||
|
||||
static void
|
||||
call_named(const char* key, const char* name, const char* value) {
|
||||
torrent::Object::list_type args;
|
||||
args.push_back(torrent::Object(std::string(name)));
|
||||
args.push_back(torrent::Object(std::string(value)));
|
||||
|
||||
rpc::commands.call_command(key, torrent::Object::create_list_range(args.begin(), args.end()));
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
down_rate() {
|
||||
return torrent::down_throttle_global()->max_rate();
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::setUp() {
|
||||
torrent::initialize_main_thread();
|
||||
torrent::initialize();
|
||||
|
||||
if (control == nullptr)
|
||||
control = new Control;
|
||||
|
||||
if (!rpc::commands.has("throttle.global_down.max_rate.set_kb"))
|
||||
initialize_command_throttle();
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::tearDown() {
|
||||
torrent::cleanup();
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_global_rate_in_range() {
|
||||
call_set("throttle.global_down.max_rate.set_kb", "1024");
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
|
||||
|
||||
call_set("throttle.global_down.max_rate.set_kb", "4194303");
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{4294966272}, down_rate());
|
||||
|
||||
call_set("throttle.global_down.max_rate.set", "4294966272");
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{4294966272}, down_rate());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_global_rate_kb_out_of_range() {
|
||||
call_set("throttle.global_down.max_rate.set_kb", "1024");
|
||||
|
||||
CPPUNIT_ASSERT_THROW(call_set("throttle.global_down.max_rate.set_kb", "4194304"), torrent::input_error);
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_global_rate_bytes_out_of_range() {
|
||||
call_set("throttle.global_down.max_rate.set", "1048576");
|
||||
|
||||
CPPUNIT_ASSERT_THROW(call_set("throttle.global_down.max_rate.set", "4294967296"), torrent::input_error);
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_global_rate_negative() {
|
||||
call_set("throttle.global_down.max_rate.set", "1048576");
|
||||
|
||||
CPPUNIT_ASSERT_THROW(call_set("throttle.global_down.max_rate.set", "-1"), torrent::input_error);
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_named_rate_in_range() {
|
||||
call_named("throttle.down", "test_named_in_range", "1024");
|
||||
|
||||
auto itr = control->core()->throttles().find("test_named_in_range");
|
||||
|
||||
CPPUNIT_ASSERT(itr != control->core()->throttles().end());
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, itr->second.second->max_rate());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_named_rate_out_of_range() {
|
||||
CPPUNIT_ASSERT_THROW(call_named("throttle.down", "test_named_out_of_range", "18014398509481984"), torrent::input_error);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#include "test/helpers/test_fixture.h"
|
||||
|
||||
class TestCommandThrottle : public test_fixture {
|
||||
CPPUNIT_TEST_SUITE(TestCommandThrottle);
|
||||
|
||||
CPPUNIT_TEST(test_global_rate_in_range);
|
||||
CPPUNIT_TEST(test_global_rate_kb_out_of_range);
|
||||
CPPUNIT_TEST(test_global_rate_bytes_out_of_range);
|
||||
CPPUNIT_TEST(test_global_rate_negative);
|
||||
CPPUNIT_TEST(test_named_rate_in_range);
|
||||
CPPUNIT_TEST(test_named_rate_out_of_range);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
void test_global_rate_in_range();
|
||||
void test_global_rate_kb_out_of_range();
|
||||
void test_global_rate_bytes_out_of_range();
|
||||
void test_global_rate_negative();
|
||||
void test_named_rate_in_range();
|
||||
void test_named_rate_out_of_range();
|
||||
};
|
||||
@@ -1,152 +0,0 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "test/src/test_session_storer.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <dirent.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "session/download_storer.h"
|
||||
#include "utils/directory.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestSessionStorer);
|
||||
|
||||
namespace {
|
||||
|
||||
const char* entry_name = "0123456789ABCDEF0123456789ABCDEF01234567.torrent";
|
||||
const char* link_name = "FEDCBA9876543210FEDCBA9876543210FEDCBA98.torrent";
|
||||
|
||||
void
|
||||
write_file(const std::string& path, const std::string& content) {
|
||||
std::ofstream file(path.c_str());
|
||||
|
||||
file << content;
|
||||
file.close();
|
||||
|
||||
CPPUNIT_ASSERT(file.good());
|
||||
}
|
||||
|
||||
std::string
|
||||
read_file(const std::string& path) {
|
||||
std::ifstream file(path.c_str());
|
||||
std::stringstream buffer;
|
||||
|
||||
buffer << file.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
void
|
||||
save_session_files(const std::string& path) {
|
||||
std::stringstream torrent_stream("torrent-data");
|
||||
std::stringstream rtorrent_stream("rtorrent-data");
|
||||
std::stringstream libtorrent_stream("libtorrent-data");
|
||||
|
||||
session::DownloadStorer::save_and_move_streams(path, false, &torrent_stream, &rtorrent_stream, &libtorrent_stream);
|
||||
}
|
||||
|
||||
unsigned int
|
||||
permissions_of(const std::string& path) {
|
||||
struct stat st;
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(0, ::stat(path.c_str(), &st));
|
||||
return st.st_mode & 07777;
|
||||
}
|
||||
|
||||
void
|
||||
remove_directory(const std::string& path) {
|
||||
DIR* d = ::opendir(path.c_str());
|
||||
|
||||
if (d == NULL)
|
||||
return;
|
||||
|
||||
struct dirent* entry;
|
||||
|
||||
while ((entry = ::readdir(d)) != NULL) {
|
||||
if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0')))
|
||||
continue;
|
||||
|
||||
::unlink((path + "/" + entry->d_name).c_str());
|
||||
}
|
||||
|
||||
::closedir(d);
|
||||
::rmdir(path.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void
|
||||
TestSessionStorer::setUp() {
|
||||
test_fixture::setUp();
|
||||
|
||||
char temp_dir[] = "/tmp/rtorrent_test_session_XXXXXX";
|
||||
|
||||
CPPUNIT_ASSERT(mkdtemp(temp_dir) != nullptr);
|
||||
|
||||
m_temp_dir = temp_dir;
|
||||
m_session_dir = m_temp_dir + "/session";
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(0, ::mkdir(m_session_dir.c_str(), 0755));
|
||||
}
|
||||
|
||||
void
|
||||
TestSessionStorer::tearDown() {
|
||||
remove_directory(m_session_dir);
|
||||
remove_directory(m_temp_dir);
|
||||
|
||||
test_fixture::tearDown();
|
||||
}
|
||||
|
||||
// A symlink planted where the next temporary session file will be written must
|
||||
// not redirect the write to the file it points at.
|
||||
void
|
||||
TestSessionStorer::test_temp_file_symlink_is_not_followed() {
|
||||
auto outside = m_temp_dir + "/outside.txt";
|
||||
auto path = m_session_dir + "/" + entry_name;
|
||||
|
||||
write_file(outside, "original");
|
||||
CPPUNIT_ASSERT_EQUAL(0, ::symlink(outside.c_str(), (path + ".new").c_str()));
|
||||
|
||||
save_session_files(path);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("original"), read_file(outside));
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("torrent-data"), read_file(path));
|
||||
}
|
||||
|
||||
// Session files carry tracker announce urls, so they must not be readable by
|
||||
// other users regardless of the umask rtorrent was started with.
|
||||
void
|
||||
TestSessionStorer::test_saved_files_are_owner_only() {
|
||||
auto path = m_session_dir + "/" + entry_name;
|
||||
auto prev_umask = ::umask(0);
|
||||
|
||||
save_session_files(path);
|
||||
::umask(prev_umask);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(0600u, permissions_of(path));
|
||||
CPPUNIT_ASSERT_EQUAL(0600u, permissions_of(path + ".rtorrent"));
|
||||
CPPUNIT_ASSERT_EQUAL(0600u, permissions_of(path + ".libtorrent_resume"));
|
||||
}
|
||||
|
||||
// Entries listed for loading must report their real type so that a symlink in
|
||||
// the session directory is skipped instead of loaded.
|
||||
void
|
||||
TestSessionStorer::test_symlinked_entry_is_not_a_file() {
|
||||
write_file(m_temp_dir + "/outside.txt", "d0:e");
|
||||
write_file(m_session_dir + "/" + entry_name, "d0:e");
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(0, ::symlink((m_temp_dir + "/outside.txt").c_str(), (m_session_dir + "/" + link_name).c_str()));
|
||||
|
||||
auto entries = session::DownloadStorer::get_formated_entries(m_session_dir + "/");
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(size_t{2}, size_t{entries.size()});
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
if (entry.s_name == entry_name)
|
||||
CPPUNIT_ASSERT(entry.is_file());
|
||||
else
|
||||
CPPUNIT_ASSERT(!entry.is_file());
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#include "test/helpers/test_fixture.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class TestSessionStorer : public test_fixture {
|
||||
CPPUNIT_TEST_SUITE(TestSessionStorer);
|
||||
|
||||
CPPUNIT_TEST(test_temp_file_symlink_is_not_followed);
|
||||
CPPUNIT_TEST(test_saved_files_are_owner_only);
|
||||
CPPUNIT_TEST(test_symlinked_entry_is_not_a_file);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
void test_temp_file_symlink_is_not_followed();
|
||||
void test_saved_files_are_owner_only();
|
||||
void test_symlinked_entry_is_not_a_file();
|
||||
|
||||
private:
|
||||
std::string m_temp_dir;
|
||||
std::string m_session_dir;
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "test/src/test_setup.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <torrent/exceptions.h>
|
||||
|
||||
#include "setup.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestSetup);
|
||||
|
||||
// Linking setup.o pulls in the help printer, which lives in src/main.cc.
|
||||
void
|
||||
print_help() {}
|
||||
|
||||
namespace {
|
||||
|
||||
class temp_config_file {
|
||||
public:
|
||||
temp_config_file(const std::string& contents) {
|
||||
char path[] = "/tmp/rtorrent_test_setup_XXXXXX";
|
||||
|
||||
CPPUNIT_ASSERT(::mkstemp(path) != -1);
|
||||
m_path = path;
|
||||
|
||||
std::ofstream file(m_path);
|
||||
file << contents << '\n';
|
||||
}
|
||||
|
||||
~temp_config_file() { ::unlink(m_path.c_str()); }
|
||||
|
||||
const std::string& path() const { return m_path; }
|
||||
|
||||
private:
|
||||
std::string m_path;
|
||||
};
|
||||
|
||||
// The group name must be a valid one, else option_find_string throws before
|
||||
// the output argument is ever touched.
|
||||
void
|
||||
assert_arg_count_error(const std::string& line) {
|
||||
temp_config_file file(line);
|
||||
|
||||
try {
|
||||
parse_config_file_comments(file.path());
|
||||
} catch (torrent::input_error& e) {
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("Invalid number of arguments."), std::string(e.what()));
|
||||
return;
|
||||
}
|
||||
|
||||
CPPUNIT_FAIL("no torrent::input_error thrown for: " + line);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void
|
||||
TestSetup::test_config_comment_log_add_output() {
|
||||
temp_config_file file("# do:log.add_output=debug,test_output");
|
||||
|
||||
CPPUNIT_ASSERT_NO_THROW(parse_config_file_comments(file.path()));
|
||||
}
|
||||
|
||||
void
|
||||
TestSetup::test_config_comment_log_add_output_no_args() {
|
||||
assert_arg_count_error("# do:log.add_output=");
|
||||
}
|
||||
|
||||
void
|
||||
TestSetup::test_config_comment_log_add_output_one_arg() {
|
||||
assert_arg_count_error("# do:log.add_output=debug");
|
||||
}
|
||||
|
||||
void
|
||||
TestSetup::test_config_comment_log_add_output_too_many_args() {
|
||||
assert_arg_count_error("# do:log.add_output=debug,test_output,extra");
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#include "test/helpers/test_fixture.h"
|
||||
|
||||
class TestSetup : public test_fixture {
|
||||
CPPUNIT_TEST_SUITE(TestSetup);
|
||||
|
||||
CPPUNIT_TEST(test_config_comment_log_add_output);
|
||||
CPPUNIT_TEST(test_config_comment_log_add_output_no_args);
|
||||
CPPUNIT_TEST(test_config_comment_log_add_output_one_arg);
|
||||
CPPUNIT_TEST(test_config_comment_log_add_output_too_many_args);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void test_config_comment_log_add_output();
|
||||
void test_config_comment_log_add_output_no_args();
|
||||
void test_config_comment_log_add_output_one_arg();
|
||||
void test_config_comment_log_add_output_too_many_args();
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "test/src/test_ui_download_list.h"
|
||||
|
||||
#include "ui/download_list.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestUiDownloadList);
|
||||
|
||||
void
|
||||
TestUiDownloadList::test_filter_pattern() {
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(""), ui::create_filter_pattern(""));
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(".*linux.*"), ui::create_filter_pattern("linux"));
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("^linux.*"), ui::create_filter_pattern("^linux"));
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(".*linux$"), ui::create_filter_pattern("linux$"));
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(".*linux.*"), ui::create_filter_pattern("LiNuX"));
|
||||
}
|
||||
|
||||
void
|
||||
TestUiDownloadList::test_filter_command() {
|
||||
torrent::Object command = ui::create_filter_command(".*linux.*");
|
||||
|
||||
CPPUNIT_ASSERT(command.is_dict_key());
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("match"), command.as_dict_key());
|
||||
|
||||
const torrent::Object::list_type& args = command.as_dict_obj().as_list();
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)2, args.size());
|
||||
CPPUNIT_ASSERT(args.front().is_dict_key());
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("d.name"), args.front().as_dict_key());
|
||||
CPPUNIT_ASSERT(args.back().is_string());
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(".*linux.*"), args.back().as_string());
|
||||
}
|
||||
|
||||
// A pattern that closes the 'match' argument early and appends a second
|
||||
// command must stay a single inert string argument.
|
||||
void
|
||||
TestUiDownloadList::test_filter_command_does_not_inject() {
|
||||
const std::string pattern = ui::create_filter_pattern("zzz},$d.custom1.set={pwned");
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(".*zzz},$d.custom1.set={pwned.*"), pattern);
|
||||
|
||||
torrent::Object command = ui::create_filter_command(pattern);
|
||||
|
||||
CPPUNIT_ASSERT(command.is_dict_key());
|
||||
CPPUNIT_ASSERT_EQUAL(std::string("match"), command.as_dict_key());
|
||||
|
||||
const torrent::Object::list_type& args = command.as_dict_obj().as_list();
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL((size_t)2, args.size());
|
||||
CPPUNIT_ASSERT(args.back().is_string());
|
||||
CPPUNIT_ASSERT_EQUAL(pattern, args.back().as_string());
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "test/helpers/test_fixture.h"
|
||||
|
||||
class TestUiDownloadList : public test_fixture {
|
||||
CPPUNIT_TEST_SUITE(TestUiDownloadList);
|
||||
|
||||
CPPUNIT_TEST(test_filter_pattern);
|
||||
CPPUNIT_TEST(test_filter_command);
|
||||
CPPUNIT_TEST(test_filter_command_does_not_inject);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void test_filter_pattern();
|
||||
void test_filter_command();
|
||||
void test_filter_command_does_not_inject();
|
||||
};
|
||||
Reference in New Issue
Block a user