mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-08-12 05:02:31 +00:00
committed by
Jari Sundell
parent
cfb9e083d2
commit
31196ff5f0
@@ -655,6 +655,8 @@ initialize_command_download() {
|
||||
CMD2_DL("d.base_path.as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_as_binary(); });
|
||||
CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_base64(); });
|
||||
CMD2_DL("d.base_path.or_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_as_binary(); });
|
||||
CMD2_DL("d.base_path.realpath.or_empty", [](auto* download, auto) { return resolve_path(retrieve_d_base_path(download).str()); });
|
||||
CMD2_DL("d.base_path.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(retrieve_d_base_path(download).str()); });
|
||||
CMD2_DL("d.base_filename", [](auto* download, auto) { return retrieve_d_base_filename(download).str(); });
|
||||
CMD2_DL("d.base_filename.hex", [](auto* download, auto) { return retrieve_d_base_filename(download).object_hex(); });
|
||||
CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); });
|
||||
@@ -771,6 +773,11 @@ initialize_command_download() {
|
||||
CMD2_DL_VAR_STRING_PUBLIC("d.tied_to_file", "rtorrent", "tied_to_file");
|
||||
CMD2_DL_VAR_STRING("d.loaded_file", "rtorrent", "loaded_file");
|
||||
|
||||
CMD2_DL("d.tied_to_file.realpath.or_empty", [](auto* download, auto) { return resolve_path(rpc::convert_to_string(download_get_variable(download, "rtorrent", "tied_to_file"))); });
|
||||
CMD2_DL("d.tied_to_file.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(rpc::convert_to_string(download_get_variable(download, "rtorrent", "tied_to_file"))); });
|
||||
CMD2_DL("d.loaded_file.realpath.or_empty", [](auto* download, auto) { return resolve_path(rpc::convert_to_string(download_get_variable(download, "rtorrent", "loaded_file"))); });
|
||||
CMD2_DL("d.loaded_file.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(rpc::convert_to_string(download_get_variable(download, "rtorrent", "loaded_file"))); });
|
||||
|
||||
// The "state_changed" variable is required to be a valid unix time
|
||||
// value, it indicates the last time the torrent changed its state,
|
||||
// resume/pause.
|
||||
@@ -878,6 +885,8 @@ initialize_command_download() {
|
||||
CMD2_DL_VALUE_V ("d.tracker.send_scrape", [](auto download, uint64_t arg) { download->tracker_controller().scrape_request(arg); });
|
||||
|
||||
CMD2_DL ("d.directory", CMD2_ON_FL(root_dir));
|
||||
CMD2_DL ("d.directory.realpath.or_empty", [](auto* download, auto) { return resolve_path(download->file_list()->root_dir()); });
|
||||
CMD2_DL ("d.directory.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(download->file_list()->root_dir()); });
|
||||
CMD2_DL_STRING_V("d.directory.set", std::bind(&apply_d_directory, std::placeholders::_1, std::placeholders::_2));
|
||||
CMD2_DL ("d.directory_base", CMD2_ON_FL(root_dir));
|
||||
CMD2_DL_STRING_V("d.directory_base.set", std::bind(&core::Download::set_root_directory, std::placeholders::_1, std::placeholders::_2));
|
||||
@@ -912,6 +921,14 @@ initialize_command_download() {
|
||||
rpc::rpc.mark_safe("d.local_id_html");
|
||||
rpc::rpc.mark_safe("d.bitfield");
|
||||
rpc::rpc.mark_safe("d.base_path");
|
||||
rpc::rpc.mark_safe("d.base_path.realpath.or_empty");
|
||||
rpc::rpc.mark_safe("d.base_path.realpath.or_throw");
|
||||
rpc::rpc.mark_safe("d.directory.realpath.or_empty");
|
||||
rpc::rpc.mark_safe("d.directory.realpath.or_throw");
|
||||
rpc::rpc.mark_safe("d.tied_to_file.realpath.or_empty");
|
||||
rpc::rpc.mark_safe("d.tied_to_file.realpath.or_throw");
|
||||
rpc::rpc.mark_safe("d.loaded_file.realpath.or_empty");
|
||||
rpc::rpc.mark_safe("d.loaded_file.realpath.or_throw");
|
||||
rpc::rpc.mark_safe("d.base_path.hex");
|
||||
rpc::rpc.mark_safe("d.base_path.base64");
|
||||
rpc::rpc.mark_safe("d.base_path.base64_as_binary");
|
||||
|
||||
@@ -116,6 +116,8 @@ initialize_command_file() {
|
||||
CMD2_FILE("f.frozen_path.as_binary", [](auto* file, auto) { return file->frozen_path().object_as_binary(); });
|
||||
CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_or_base64(); });
|
||||
CMD2_FILE("f.frozen_path.or_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_or_as_binary(); });
|
||||
CMD2_FILE("f.frozen_path.realpath.or_empty", [](auto* file, auto) { return resolve_path(file->frozen_path().str()); });
|
||||
CMD2_FILE("f.frozen_path.realpath.or_throw", [](auto* file, auto) { return resolve_path_or_throw(file->frozen_path().str()); });
|
||||
|
||||
CMD2_FILE("f.match_depth_prev", std::bind(&torrent::File::match_depth_prev, std::placeholders::_1));
|
||||
CMD2_FILE("f.match_depth_next", std::bind(&torrent::File::match_depth_next, std::placeholders::_1));
|
||||
@@ -129,6 +131,8 @@ initialize_command_file() {
|
||||
rpc::rpc.mark_safe("f.path_components");
|
||||
rpc::rpc.mark_safe("f.path_depth");
|
||||
rpc::rpc.mark_safe("f.frozen_path");
|
||||
rpc::rpc.mark_safe("f.frozen_path.realpath.or_empty");
|
||||
rpc::rpc.mark_safe("f.frozen_path.realpath.or_throw");
|
||||
rpc::rpc.mark_safe("f.frozen_path.hex");
|
||||
rpc::rpc.mark_safe("f.frozen_path.base64");
|
||||
rpc::rpc.mark_safe("f.frozen_path.base64_as_binary");
|
||||
|
||||
@@ -296,9 +296,13 @@ initialize_command_local() {
|
||||
CMD_VAR_BOOL ("pieces.hash.on_completion", true);
|
||||
|
||||
CMD_VAR_STRING ("directory.default", "./");
|
||||
CMD_ANY ("directory.default.realpath.or_empty", [](auto, auto) { return resolve_path(rpc::call_command_string("directory.default")); });
|
||||
CMD_ANY ("directory.default.realpath.or_throw", [](auto, auto) { return resolve_path_or_throw(rpc::call_command_string("directory.default")); });
|
||||
|
||||
CMD_VAR_STRING ("session.name", "");
|
||||
CMD_ANY ("session.path", [](auto, auto) { return session_thread::manager()->path(); });
|
||||
CMD_ANY ("session.path.realpath.or_empty", [](auto, auto) { return resolve_path(session_thread::manager()->path()); });
|
||||
CMD_ANY ("session.path.realpath.or_throw", [](auto, auto) { return resolve_path_or_throw(session_thread::manager()->path()); });
|
||||
CMD_ANY_STRING_V("session.path.set", [](auto, auto& str) { return session_thread::manager()->set_path(str); });
|
||||
CMD_ANY ("session.use_lock", [](auto, auto) { return session_thread::manager()->use_lock(); });
|
||||
CMD_ANY_VALUE_V ("session.use_lock.set", [](auto, auto& value) { return session_thread::manager()->set_use_lock(value); });
|
||||
@@ -372,6 +376,10 @@ initialize_command_local() {
|
||||
|
||||
rpc::rpc.mark_safe("directory.default");
|
||||
rpc::rpc.mark_safe("session.path");
|
||||
rpc::rpc.mark_safe("session.path.realpath.or_empty");
|
||||
rpc::rpc.mark_safe("session.path.realpath.or_throw");
|
||||
rpc::rpc.mark_safe("directory.default.realpath.or_empty");
|
||||
rpc::rpc.mark_safe("directory.default.realpath.or_throw");
|
||||
rpc::rpc.mark_safe("session.use_lock");
|
||||
rpc::rpc.mark_safe("session.on_completion");
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "globals.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdlib.h>
|
||||
#include <torrent/exceptions.h>
|
||||
|
||||
rpc::ip_table_list ip_tables;
|
||||
@@ -28,3 +29,32 @@ expand_path(const std::string& path) {
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// Resolves a path to a canonical one with no symlinks or relative components,
|
||||
// so it can safely be handed to an external script. Returns an empty string if
|
||||
// the path does not name an existing file or directory.
|
||||
std::string
|
||||
resolve_path(const std::string& path) {
|
||||
if (path.empty())
|
||||
return std::string();
|
||||
|
||||
char* resolved = ::realpath(expand_path(path).c_str(), nullptr);
|
||||
|
||||
if (resolved == nullptr)
|
||||
return std::string();
|
||||
|
||||
std::string result(resolved);
|
||||
std::free(resolved);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string
|
||||
resolve_path_or_throw(const std::string& path) {
|
||||
auto result = resolve_path(path);
|
||||
|
||||
if (result.empty())
|
||||
throw torrent::input_error("Could not resolve path: '" + path + "'.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ extern rpc::ip_table_list ip_tables;
|
||||
extern Control* control;
|
||||
|
||||
std::string expand_path(const std::string& path);
|
||||
std::string resolve_path(const std::string& path);
|
||||
std::string resolve_path_or_throw(const std::string& path);
|
||||
|
||||
namespace rpc {
|
||||
class SCgi;
|
||||
|
||||
@@ -51,6 +51,8 @@ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
|
||||
src/test_command_dynamic.h \
|
||||
src/test_command_local.cc \
|
||||
src/test_command_local.h \
|
||||
src/test_command_path.cc \
|
||||
src/test_command_path.h \
|
||||
src/test_command_string.cc \
|
||||
src/test_command_string.h \
|
||||
src/test_watch_ready_queue.cc \
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "test/src/test_command_path.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <torrent/exceptions.h>
|
||||
#include <torrent/torrent.h>
|
||||
|
||||
#include "control.h"
|
||||
#include "globals.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandPath);
|
||||
|
||||
void initialize_command_local();
|
||||
|
||||
void
|
||||
TestCommandPath::setUp() {
|
||||
char temp_dir[] = "/tmp/rtorrent_test_path_XXXXXX";
|
||||
|
||||
CPPUNIT_ASSERT(mkdtemp(temp_dir) != nullptr);
|
||||
|
||||
// The temporary directory itself may sit behind a symlink, as /tmp does on
|
||||
// macOS, so resolve it up front to keep the expected values exact.
|
||||
m_temp_dir = resolve_path(temp_dir);
|
||||
|
||||
CPPUNIT_ASSERT(!m_temp_dir.empty());
|
||||
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/data").c_str(), 0755));
|
||||
CPPUNIT_ASSERT_EQUAL(0, symlink((m_temp_dir + "/data").c_str(), (m_temp_dir + "/link").c_str()));
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandPath::tearDown() {
|
||||
unlink((m_temp_dir + "/link").c_str());
|
||||
rmdir((m_temp_dir + "/data").c_str());
|
||||
rmdir(m_temp_dir.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandPath::test_resolves_symlink() {
|
||||
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/link"));
|
||||
|
||||
// A path that lies below a symlinked directory is resolved as well.
|
||||
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/data/below").c_str(), 0755));
|
||||
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data/below", resolve_path(m_temp_dir + "/link/below"));
|
||||
rmdir((m_temp_dir + "/data/below").c_str());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandPath::test_removes_relative_components() {
|
||||
CPPUNIT_ASSERT_EQUAL(m_temp_dir, resolve_path(m_temp_dir + "/data/.."));
|
||||
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/./data"));
|
||||
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/data/"));
|
||||
|
||||
// Trailing slashes and duplicated separators collapse.
|
||||
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "//data//"));
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandPath::test_expands_tilde() {
|
||||
const char* home = std::getenv("HOME");
|
||||
|
||||
if (home == nullptr || *home == '\0')
|
||||
return;
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(resolve_path(home), resolve_path("~"));
|
||||
CPPUNIT_ASSERT_THROW(resolve_path("~root/somewhere"), torrent::input_error);
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandPath::test_missing_path_throws() {
|
||||
CPPUNIT_ASSERT_THROW(resolve_path_or_throw(""), torrent::input_error);
|
||||
CPPUNIT_ASSERT_THROW(resolve_path_or_throw(m_temp_dir + "/does_not_exist"), torrent::input_error);
|
||||
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path_or_throw(m_temp_dir + "/link"));
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandPath::test_missing_path_is_empty() {
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(""));
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/does_not_exist"));
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/does_not_exist/below"));
|
||||
|
||||
// A dangling symlink does not name an existing path either.
|
||||
CPPUNIT_ASSERT_EQUAL(0, symlink((m_temp_dir + "/gone").c_str(), (m_temp_dir + "/dangling").c_str()));
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/dangling"));
|
||||
unlink((m_temp_dir + "/dangling").c_str());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandPath::test_commands() {
|
||||
torrent::initialize_main_thread();
|
||||
torrent::initialize();
|
||||
|
||||
if (control == nullptr)
|
||||
control = new Control;
|
||||
|
||||
if (!rpc::commands.has("directory.default.realpath.or_empty"))
|
||||
initialize_command_local();
|
||||
|
||||
rpc::commands.call_command("directory.default.set", m_temp_dir + "/link");
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/link", rpc::commands.call_command("directory.default", torrent::Object()).as_string());
|
||||
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", rpc::commands.call_command("directory.default.realpath.or_empty", torrent::Object()).as_string());
|
||||
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", rpc::commands.call_command("directory.default.realpath.or_throw", torrent::Object()).as_string());
|
||||
|
||||
// A directory that has not been created yet resolves to nothing, and the
|
||||
// or_throw variant reports it instead.
|
||||
rpc::commands.call_command("directory.default.set", m_temp_dir + "/missing");
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(std::string(), rpc::commands.call_command("directory.default.realpath.or_empty", torrent::Object()).as_string());
|
||||
CPPUNIT_ASSERT_THROW(rpc::commands.call_command("directory.default.realpath.or_throw", torrent::Object()), torrent::input_error);
|
||||
|
||||
torrent::cleanup();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "test/helpers/test_fixture.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class TestCommandPath : public test_fixture {
|
||||
CPPUNIT_TEST_SUITE(TestCommandPath);
|
||||
|
||||
CPPUNIT_TEST(test_resolves_symlink);
|
||||
CPPUNIT_TEST(test_removes_relative_components);
|
||||
CPPUNIT_TEST(test_expands_tilde);
|
||||
CPPUNIT_TEST(test_missing_path_is_empty);
|
||||
CPPUNIT_TEST(test_missing_path_throws);
|
||||
CPPUNIT_TEST(test_commands);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
void test_resolves_symlink();
|
||||
void test_removes_relative_components();
|
||||
void test_expands_tilde();
|
||||
void test_missing_path_is_empty();
|
||||
void test_missing_path_throws();
|
||||
void test_commands();
|
||||
|
||||
private:
|
||||
std::string m_temp_dir;
|
||||
};
|
||||
Reference in New Issue
Block a user