diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc index 17c9eb97..18e8b4b7 100644 --- a/src/ui/download_list.cc +++ b/src/ui/download_list.cc @@ -30,6 +30,38 @@ 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(); @@ -319,16 +351,12 @@ DownloadList::receive_exit_input(Input type) { current_view()->filter(); current_view()->sort(); } else { - 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 + "}"; + std::string pattern = create_filter_pattern(input->str()); + 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(temp_filter); + + current_view()->set_filter_temp(create_filter_command(pattern)); current_view()->filter(); } break; diff --git a/src/ui/download_list.h b/src/ui/download_list.h index b5e4d406..f998d944 100644 --- a/src/ui/download_list.h +++ b/src/ui/download_list.h @@ -1,6 +1,9 @@ #ifndef RTORRENT_UI_DOWNLOAD_LIST_H #define RTORRENT_UI_DOWNLOAD_LIST_H +#include +#include + #include "element_base.h" #include "globals.h" #include "display/manager.h" @@ -29,6 +32,12 @@ 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; diff --git a/test/Makefile.am b/test/Makefile.am index cb4cd141..bec78661 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -69,6 +69,8 @@ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \ src/test_command_string.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 diff --git a/test/src/test_ui_download_list.cc b/test/src/test_ui_download_list.cc new file mode 100644 index 00000000..09772b6f --- /dev/null +++ b/test/src/test_ui_download_list.cc @@ -0,0 +1,52 @@ +#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()); +} diff --git a/test/src/test_ui_download_list.h b/test/src/test_ui_download_list.h new file mode 100644 index 00000000..89bb8756 --- /dev/null +++ b/test/src/test_ui_download_list.h @@ -0,0 +1,16 @@ +#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(); +};