From fc6e57c3fa8a1068f1ff69ea7ff06f97d1c40eba Mon Sep 17 00:00:00 2001 From: noctuum <25441068+noctuum@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:50:54 +0500 Subject: [PATCH 01/12] Check pthread_sigmask by its return value `pthread_sigmask` returns the error number and does not set `errno`. --- src/signal_handler.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/signal_handler.cc b/src/signal_handler.cc index 3d5bbf27..66e89e5e 100644 --- a/src/signal_handler.cc +++ b/src/signal_handler.cc @@ -63,8 +63,10 @@ SignalHandler::set_block(unsigned int signum) { sigemptyset(&mask); sigaddset(&mask, signum); - if (pthread_sigmask(SIG_BLOCK, &mask, NULL) == -1) - throw std::logic_error("Could not block signal: " + std::string(std::strerror(errno))); + int result = pthread_sigmask(SIG_BLOCK, &mask, NULL); + + if (result != 0) + throw std::logic_error("Could not block signal: " + std::string(std::strerror(result))); } void @@ -77,8 +79,10 @@ SignalHandler::set_unblock(unsigned int signum) { sigemptyset(&mask); sigaddset(&mask, signum); - if (pthread_sigmask(SIG_UNBLOCK, &mask, NULL) == -1) - throw std::logic_error("Could not unblock signal: " + std::string(std::strerror(errno))); + int result = pthread_sigmask(SIG_UNBLOCK, &mask, NULL); + + if (result != 0) + throw std::logic_error("Could not unblock signal: " + std::string(std::strerror(result))); } void From d98a441dc4d4eaf1af8cb4b0496885a89c83e0c2 Mon Sep 17 00:00:00 2001 From: noctuum <25441068+noctuum@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:20:27 +0500 Subject: [PATCH 02/12] Sort the transfer chunks by index The default comparison ordered the pointers, not the chunk indexes. --- src/display/window_download_chunks_seen.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/display/window_download_chunks_seen.cc b/src/display/window_download_chunks_seen.cc index 88c74c7a..1ee75629 100644 --- a/src/display/window_download_chunks_seen.cc +++ b/src/display/window_download_chunks_seen.cc @@ -57,7 +57,8 @@ WindowDownloadChunksSeen::redraw() { const torrent::TransferList* transfers = m_download->download()->transfer_list(); std::vector transferChunks(transfers->begin(), transfers->end()); - std::sort(transferChunks.begin(), transferChunks.end()); + std::sort(transferChunks.begin(), transferChunks.end(), + [](const auto& left, const auto& right) { return left->index() < right->index(); }); std::vector::const_iterator itrTransfer = transferChunks.begin(); From a89ce3cd7bd69fa99f21f902fef4758f342768bb Mon Sep 17 00:00:00 2001 From: noctuum <25441068+noctuum@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:18:02 +0500 Subject: [PATCH 03/12] Check first against last in parse_object A command ending in `=` reaches it with an empty range. --- src/rpc/parse.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/parse.cc b/src/rpc/parse.cc index e49dde66..b4414665 100644 --- a/src/rpc/parse.cc +++ b/src/rpc/parse.cc @@ -161,7 +161,7 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (* if (++depth >= max_parse_depth) throw torrent::input_error("Max parse depth reached."); - if (*first == '{') { + if (first != last && *first == '{') { *dest = torrent::Object::create_list(); first = parse_list(first + 1, last, dest, &parse_is_delim_block, depth); first = parse_skip_wspace(first, last); @@ -171,7 +171,7 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (* return ++first; - } else if (*first == '(') { + } else if (first != last && *first == '(') { int32_t parentheses = 1; while (first + 1 != last && *(first + 1) == '(') { From d5ce0984fca77ca4e18db6edadab343edb9af14b Mon Sep 17 00:00:00 2001 From: noctuum <25441068+noctuum@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:24:13 +0500 Subject: [PATCH 04/12] Reset the freed pointers in XmlRpc::cleanup `is_valid()` tests `m_env`, so it stayed true after `cleanup()`. --- src/rpc/xmlrpc_c.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rpc/xmlrpc_c.cc b/src/rpc/xmlrpc_c.cc index b85142f4..b54b4542 100644 --- a/src/rpc/xmlrpc_c.cc +++ b/src/rpc/xmlrpc_c.cc @@ -440,6 +440,8 @@ XmlRpc::cleanup() { xmlrpc_registry_free((xmlrpc_registry*)m_registry); xmlrpc_env_clean((xmlrpc_env*)m_env); delete (xmlrpc_env*)m_env; + m_env = nullptr; + m_registry = nullptr; } bool From 5421a35349563e9c7d96bd30804e20bae485b26b Mon Sep 17 00:00:00 2001 From: noctuum <25441068+noctuum@users.noreply.github.com> Date: Mon, 28 Sep 2026 18:04:03 +0500 Subject: [PATCH 05/12] Remove code that has no remaining user Three declarations had no definition and the rest had no user left. --- src/Makefile.am | 2 - src/command_local.cc | 48 ---------- src/core/manager.h | 1 - src/core/range_map.h | 199 ----------------------------------------- src/display/utils.h | 6 -- src/input/bindings.h | 2 - src/ui/download.h | 1 - src/utils/list_focus.h | 158 -------------------------------- 8 files changed, 417 deletions(-) delete mode 100644 src/core/range_map.h delete mode 100644 src/utils/list_focus.h diff --git a/src/Makefile.am b/src/Makefile.am index 1c4ccfef..b21edc62 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -17,7 +17,6 @@ libsub_root_a_SOURCES = \ core/http_queue.h \ core/manager.cc \ core/manager.h \ - core/range_map.h \ core/view.cc \ core/view.h \ core/view_manager.cc \ @@ -171,7 +170,6 @@ libsub_root_a_SOURCES = \ utils/functional.h \ utils/gzip.cc \ utils/gzip.h \ - utils/list_focus.h \ utils/lockfile.cc \ utils/lockfile.h \ utils/waitpid_queue.cc \ diff --git a/src/command_local.cc b/src/command_local.cc index ab73fe15..7bc71b61 100644 --- a/src/command_local.cc +++ b/src/command_local.cc @@ -77,54 +77,6 @@ group_insert(const torrent::Object::list_type& args) { return name; } -static const int file_print_use_space = 0x1; -static const int file_print_delim_space = 0x2; - -void -file_print_list(torrent::Object::list_const_iterator first, torrent::Object::list_const_iterator last, FILE* output, int flags) { - while (first != last) { - switch (first->type()) { - case torrent::Object::TYPE_STRING: - fprintf(output, (const char*)" %s" + !(flags & file_print_use_space), first->as_string().c_str()); - break; - case torrent::Object::TYPE_VALUE: - fprintf(output, (const char*)" %" PRIi64 + !(flags & file_print_use_space), first->as_value()); - break; - case torrent::Object::TYPE_LIST: - file_print_list(first->as_list().begin(), first->as_list().end(), output, 0); - break; - case torrent::Object::TYPE_NONE: - break; - default: - throw torrent::input_error("Invalid type."); - } - - flags |= (flags & file_print_delim_space) >> 1; - first++; - } -} - -torrent::Object -cmd_file_append(const torrent::Object::list_type& args) { - if (args.empty()) - throw torrent::input_error("Invalid number of arguments."); - - FILE* output = fopen(args.front().as_string().c_str(), "a"); - - if (output == nullptr) - throw torrent::input_error("Could not append to file '" + args.front().as_string() + "': " + std::strerror(errno)); - - try { - file_print_list(++args.begin(), args.end(), output, file_print_delim_space); - fprintf(output, "\n"); - } catch (...) { - fclose(output); - throw; - } - fclose(output); - return torrent::Object(); -} - void initialize_command_local() { core::DownloadList* dList = control->core()->download_list(); diff --git a/src/core/manager.h b/src/core/manager.h index 942c4088..6d7b17e9 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -8,7 +8,6 @@ #include #include "download_list.h" -#include "range_map.h" namespace torrent { class Bencode; diff --git a/src/core/range_map.h b/src/core/range_map.h deleted file mode 100644 index c6cb0281..00000000 --- a/src/core/range_map.h +++ /dev/null @@ -1,199 +0,0 @@ -// rTorrent - BitTorrent client -// Copyright (C) 2005-2008, Jari Sundell -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// In addition, as a special exception, the copyright holders give -// permission to link the code of portions of this program with the -// OpenSSL library under certain conditions as described in each -// individual source file, and distribute linked combinations -// including the two. -// -// You must obey the GNU General Public License in all respects for -// all of the code used other than OpenSSL. If you modify file(s) -// with this exception, you may extend this exception to your version -// of the file(s), but you are not obligated to do so. If you do not -// wish to do so, delete this exception statement from your version. -// If you delete this exception statement from all source files in the -// program, then also delete it here. -// -// Contact: Jari Sundell - - -#ifndef RTORRENT_CORE_RANGE_MAP_H -#define RTORRENT_CORE_RANGE_MAP_H - -#include -#include - -namespace core { - -// Associate values with a range of keys, and retrieve for any key in the range. - -// The template arguments have the same semantics as std::map. -// Exception: if set_merge is used, the value type must have a defined operator ==. -template, - typename Alloc = std::allocator > > -class RangeMap : private std::map, Compare, - typename std::allocator_traits::template rebind_alloc>>> { - - typedef std::map, Compare, - typename std::allocator_traits::template rebind_alloc>>> base_type; - - //std::allocator_traits::template rebind_alloc>> - -public: - RangeMap() = default; - RangeMap(const Compare& c) : base_type(c) {} - - typedef typename base_type::iterator iterator; - typedef typename base_type::reverse_iterator reverse_iterator; - typedef typename base_type::const_iterator const_iterator; - typedef typename base_type::const_reverse_iterator const_reverse_iterator; - - // using typename base_type::const_iterator; - // using typename base_type::const_reverse_iterator; - - using base_type::clear; - using base_type::swap; - - using base_type::size; - using base_type::empty; - - using base_type::begin; - using base_type::end; - using base_type::rbegin; - using base_type::rend; - - using base_type::key_comp; - using base_type::value_comp; - - // Store a value for the range [begin, end). Returns iterator for the range. - const_iterator set_range(const Key& begin, const Key& end, const T& value); - - // Same, but merge adjacent ranges having the same value. Returns iterator for the merged range. - const_iterator set_merge(Key begin, const Key& end, const T& value); - - // Find range containing the given key, or end(). - const_iterator find(const Key& key) const; - - // Retrieve value for key in a range, throw std::out_of_range if range does not exist. - const T& get(const Key& key) const; - - // Retrieve value for key in a range, return def if range does not exist. - T get(const Key& key, T def) const; - -private: - iterator crop_overlap(const Key& begin, const Key& end); -}; - -// Semantics of an entry: -// .first End of range (exclusive), map key. -// .second.first Beginning of range. -// .second.second Value. - -template -inline typename RangeMap::iterator -RangeMap::crop_overlap(const Key& _begin, const Key& _end) { - typename RangeMap::iterator itr = base_type::upper_bound(_begin); - - while (itr != end() && key_comp()(itr->second.first, _end)) { - // There's a subrange before the new begin: need new entry (new range end means new key). - if (key_comp()(itr->second.first, _begin)) - base_type::insert(itr, typename RangeMap::value_type(_begin, itr->second)); - - // Old end is within our range: erase entry. - if (!key_comp()(_end, itr->first)) { - base_type::erase(itr++); - - // Otherwise simply set the new begin of the old range. - } else { - itr->second.first = _end; - ++itr; - } - } - - return itr; -} - -template -inline typename RangeMap::const_iterator -RangeMap::set_merge(Key _begin, const Key& _end, const T& value) { - if (!key_comp()(_begin, _end)) - return end(); - - // Crop overlapping ranges and return iterator to first range after the one we're inserting. - typename RangeMap::iterator itr = crop_overlap(_begin, _end); - - // Check if range before new one is adjacent and has same value: if so erase it and use its beginning. - if (itr != begin()) { - typename RangeMap::iterator prev = itr; - if (!key_comp()((--prev)->first, _begin) && prev->second.second == value) { - _begin = prev->second.first; - base_type::erase(prev); - } - } - - // Range after new one is adjacent and has same value: set new beginning. - if (itr != end() && !key_comp()(_end, itr->second.first) && itr->second.second == value) { - itr->second.first = _begin; - return itr; - } - - // Otherwise, this range isn't mergeable, make new entry. - return base_type::insert(itr, typename RangeMap::value_type(_end, typename RangeMap::mapped_type(_begin, value))); -} - -template -inline typename RangeMap::const_iterator -RangeMap::set_range(const Key& _begin, const Key& _end, const T& value) { - if (!key_comp()(_begin, _end)) - return end(); - - return base_type::insert(crop_overlap(_begin, _end), typename RangeMap::value_type(_end, typename RangeMap::mapped_type(_begin, value))); -} - -template -inline typename RangeMap::const_iterator -RangeMap::find(const Key& key) const { - typename RangeMap::const_iterator itr = base_type::upper_bound(key); - - if (itr != end() && key_comp()(key, itr->second.first)) - itr = end(); - - return itr; -} - -template -inline const T& -RangeMap::get(const Key& key) const { - typename RangeMap::const_iterator itr = find(key); - - if (itr == end()) - throw std::out_of_range("RangeMap::get"); - - return itr->second.second; -} - -template -inline T -RangeMap::get(const Key& key, T def) const { - typename RangeMap::const_iterator itr = find(key); - return (itr == end() ? def : itr->second.second); -} - -} - -#endif diff --git a/src/display/utils.h b/src/display/utils.h index 69d89281..2180a89f 100644 --- a/src/display/utils.h +++ b/src/display/utils.h @@ -16,15 +16,12 @@ namespace utils { namespace torrent { class ClientInfo; - class Entry; } class Control; namespace display { -char* print_string(char* first, char* last, char* str); - char* print_hhmmss(char* first, char* last, time_t t); char* print_hhmmss_local(char* first, char* last, time_t t); char* print_ddhhmm(char* first, char* last, time_t t); @@ -42,9 +39,6 @@ char* print_download_percentage_done(char* first, char* last, core::Downlo char* print_client_version(char* first, char* last, const torrent::ClientInfo& clientInfo); -char* print_entry_tags(char* first, char* last); -char* print_entry_file(char* first, char* last, const torrent::Entry& entry); - char* print_status_throttle_limit(char* first, char* last, bool up, const std::vector& throttle_names); char* print_status_throttle_rate(char* first, char* last, bool up, const std::vector& throttle_names, const double& global_rate); diff --git a/src/input/bindings.h b/src/input/bindings.h index f5080f32..54038e84 100644 --- a/src/input/bindings.h +++ b/src/input/bindings.h @@ -69,8 +69,6 @@ public: bool pressed(int key); - void ignore(int key) { (*this)[key] = slot_void(); } - private: bool m_enabled{true}; }; diff --git a/src/ui/download.h b/src/ui/download.h index bb7a39ed..fdfb7a10 100644 --- a/src/ui/download.h +++ b/src/ui/download.h @@ -6,7 +6,6 @@ #include #include "display/manager.h" -#include "utils/list_focus.h" #include "element_base.h" diff --git a/src/utils/list_focus.h b/src/utils/list_focus.h deleted file mode 100644 index 068d1496..00000000 --- a/src/utils/list_focus.h +++ /dev/null @@ -1,158 +0,0 @@ -// rTorrent - BitTorrent client -// Copyright (C) 2005-2011, Jari Sundell -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// In addition, as a special exception, the copyright holders give -// permission to link the code of portions of this program with the -// OpenSSL library under certain conditions as described in each -// individual source file, and distribute linked combinations -// including the two. -// -// You must obey the GNU General Public License in all respects for -// all of the code used other than OpenSSL. If you modify file(s) -// with this exception, you may extend this exception to your version -// of the file(s), but you are not obligated to do so. If you do not -// wish to do so, delete this exception statement from your version. -// If you delete this exception statement from all source files in the -// program, then also delete it here. -// -// Contact: Jari Sundell - - -#ifndef RTORRENT_UTILS_LIST_FOCUS_H -#define RTORRENT_UTILS_LIST_FOCUS_H - -#include -#include - -namespace utils { - -// Can't make this class inherit privately due to gcc PR 14258. - -template -class ListFocus { -public: - typedef Base base_type; - typedef std::function slot_void; - typedef std::list signal_void; - - typedef typename base_type::iterator iterator; - typedef typename base_type::const_iterator const_iterator; - typedef typename base_type::reverse_iterator reverse_iterator; - typedef typename base_type::const_reverse_iterator const_reverse_iterator; - - typedef typename base_type::value_type value_type; - - ListFocus(base_type* b = nullptr) : m_base(b) { if (b) m_focus = b->end(); } - - // Convinience functions, would have added more through using, but - // can't. - iterator begin() { return m_base->begin(); } - iterator end() { return m_base->end(); } - reverse_iterator rbegin() { return m_base->rbegin(); } - reverse_iterator rend() { return m_base->rend(); } - - // Don't do erase on this object without making sure focus is right. - base_type& base() { return *m_base; } - - iterator get_focus() { return m_focus; } - void set_focus(iterator itr); - - // These are looping increment/decrements. - iterator inc_focus(); - iterator dec_focus(); - - iterator erase(iterator itr); - void remove(const value_type& v); - - // Be careful with copying signals. - signal_void& signal_changed() { return m_signal_changed; } - -private: - void emit_changed(); - - base_type* m_base; - iterator m_focus; - - signal_void m_signal_changed; -}; - -template -void -ListFocus::set_focus(iterator itr) { - m_focus = itr; - emit_changed(); -} - -template -typename ListFocus::iterator -ListFocus::inc_focus() { - if (m_focus != end()) - ++m_focus; - else - m_focus = begin(); - - emit_changed(); - return m_focus; -} - -template -typename ListFocus::iterator -ListFocus::dec_focus() { - if (m_focus != begin()) - --m_focus; - else - m_focus = end(); - - emit_changed(); - return m_focus; -} - -template -typename ListFocus::iterator -ListFocus::erase(iterator itr) { - if (itr == m_focus) { - m_focus = m_base->erase(itr); - emit_changed(); - return m_focus; - } - - return m_base->erase(itr); -} - -template -void -ListFocus::remove(const value_type& v) { - iterator first = begin(); - iterator last = end(); - - while (first != last) - if (*first == v) - first = erase(first); - else - ++first; -} - -template -void -ListFocus::emit_changed() { - for (const auto& itr : m_signal_changed) - itr(); -} - -} - -#endif From 8bcc4c6e9228c98c1d3a05a5667dad4a86378707 Mon Sep 17 00:00:00 2001 From: noctuum <25441068+noctuum@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:02:42 +0500 Subject: [PATCH 06/12] Read the unpacked object in the raw string case A list holding one raw string reached this case and threw on `src`. --- src/rpc/parse.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/parse.cc b/src/rpc/parse.cc index b4414665..9e8b4d58 100644 --- a/src/rpc/parse.cc +++ b/src/rpc/parse.cc @@ -393,7 +393,7 @@ convert_to_value_nothrow(const torrent::Object& src, int64_t* value, int base, i == unpacked.as_string().c_str() + unpacked.as_string().size(); case torrent::Object::TYPE_RAW_STRING: { - const torrent::raw_string& str = src.as_raw_string(); + const torrent::raw_string& str = unpacked.as_raw_string(); auto buffer = std::make_unique(str.size() + 1); std::memcpy(buffer.get(), str.data(), str.size()); From 366b936dedaf9111ea45a766f12efdd6b447c5c4 Mon Sep 17 00:00:00 2001 From: noctuum <25441068+noctuum@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:34:21 +0500 Subject: [PATCH 07/12] Check m_entry against size() in set_entry --- src/ui/element_menu.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/element_menu.cc b/src/ui/element_menu.cc index b9a9ad8f..b27507fa 100644 --- a/src/ui/element_menu.cc +++ b/src/ui/element_menu.cc @@ -179,7 +179,7 @@ ElementMenu::set_entry(size_type idx, bool triggerSlot) { m_entry = idx; focus_entry(m_entry); - if (triggerSlot) + if (triggerSlot && m_entry < size()) base_type::operator[](m_entry).m_slotFocus(); m_window->mark_dirty(); From dc916278c26c88da190919959c92906ff8164f89 Mon Sep 17 00:00:00 2001 From: noctuum <25441068+noctuum@users.noreply.github.com> Date: Tue, 29 Sep 2026 09:43:00 +0500 Subject: [PATCH 08/12] Do not switch an active download to initial_seed `confirm_finished` did, and the `input_error` reached `main`. --- src/core/download_list.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/download_list.cc b/src/core/download_list.cc index 6006adb2..12b0e814 100644 --- a/src/core/download_list.cc +++ b/src/core/download_list.cc @@ -709,6 +709,10 @@ DownloadList::confirm_finished(Download* download) { if (choke_up.is_string_empty()) choke_up = rpc::call_command("protocol.choke_heuristics.up.seed", torrent::Object(), rpc::make_target(download)); if (choke_down.is_string_empty()) choke_down = rpc::call_command("protocol.choke_heuristics.down.seed", torrent::Object(), rpc::make_target(download)); + // libtorrent refuses initial seeding on an active download. + if (download->is_active() && conn_current.as_string() == "initial_seed") + conn_current = "seed"; + rpc::call_command("d.connection_current.set", conn_current, rpc::make_target(download)); rpc::call_command("d.up.choke_heuristics.set", choke_up, rpc::make_target(download)); rpc::call_command("d.down.choke_heuristics.set", choke_down, rpc::make_target(download)); From 44d51171e12d29be8ac804053f2088d7d7c313a3 Mon Sep 17 00:00:00 2001 From: Silas Mariusz Date: Mon, 28 Sep 2026 10:26:57 +0000 Subject: [PATCH 09/12] Mark the system.file.allocate getter untrusted-safe, because d.open reads it. --- src/command_system.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/command_system.cc b/src/command_system.cc index 4615acd2..d530862a 100644 --- a/src/command_system.cc +++ b/src/command_system.cc @@ -167,6 +167,7 @@ initialize_command_system() { rpc::rpc.mark_safe("system.time"); rpc::rpc.mark_safe("system.time_seconds"); rpc::rpc.mark_safe("system.time_usec"); + rpc::rpc.mark_safe("system.file.allocate"); rpc::rpc.mark_safe("system.file.max_size"); rpc::rpc.mark_safe("system.file.split_size"); rpc::rpc.mark_safe("system.file.split_suffix"); From c5d54fbb971effb287d64121c72bd6805faf8cda Mon Sep 17 00:00:00 2001 From: rakshasa Date: Wed, 30 Sep 2026 12:01:13 +0200 Subject: [PATCH 10/12] Restrict parse value to strtoll with restrictions on input. --- src/command_groups.cc | 4 ++ src/command_helpers.h | 13 +++++ src/command_network.cc | 16 ++---- src/command_tracker.cc | 2 +- src/core/download_list.cc | 2 +- src/rpc/parse.cc | 34 ++++++++++-- src/rpc/parse.h | 34 ------------ test/Makefile.am | 16 +++--- test/rpc/test_parse.cc | 95 ++++++++++++++++++++++++++++++++ test/rpc/test_parse.h | 18 ++++++ test/src/test_command_groups.cc | 65 ++++++++++++++++++++++ test/src/test_command_groups.h | 17 ++++++ test/src/test_command_tracker.cc | 73 ++++++++++++++++++++++++ test/src/test_command_tracker.h | 19 +++++++ test/src/test_download_list.cc | 68 +++++++++++++++++++++++ test/src/test_download_list.h | 24 ++++++++ 16 files changed, 440 insertions(+), 60 deletions(-) create mode 100644 test/rpc/test_parse.cc create mode 100644 test/rpc/test_parse.h create mode 100644 test/src/test_command_groups.cc create mode 100644 test/src/test_command_groups.h create mode 100644 test/src/test_command_tracker.cc create mode 100644 test/src/test_command_tracker.h create mode 100644 test/src/test_download_list.cc create mode 100644 test/src/test_download_list.h diff --git a/src/command_groups.cc b/src/command_groups.cc index 0b52905b..69bf326d 100644 --- a/src/command_groups.cc +++ b/src/command_groups.cc @@ -1,5 +1,6 @@ #include "config.h" +#include #include #include @@ -232,6 +233,9 @@ apply_cg_max_set(const torrent::Object::list_type& args, bool is_up) { int64_t second_arg = 0; rpc::parse_whole_value(args.back().as_string().c_str(), &second_arg); + if (second_arg < -1 || second_arg > std::numeric_limits::max()) + throw torrent::input_error("Max unchoked must be between -1 and 4294967295."); + if (is_up) cg_get_group(args.front())->up_queue()->set_max_unchoked(second_arg); else diff --git a/src/command_helpers.h b/src/command_helpers.h index b6d47f71..6ddb32c9 100644 --- a/src/command_helpers.h +++ b/src/command_helpers.h @@ -1,12 +1,25 @@ #ifndef RTORRENT_UTILS_COMMAND_HELPERS_H #define RTORRENT_UTILS_COMMAND_HELPERS_H +#include +#include +#include +#include + #include "rpc/command.h" #include "rpc/parse_commands.h" #include "rpc/object_storage.h" void initialize_commands(); +inline uint16_t +checked_port_value(int64_t value, const char* label) { + if (value < 0 || value > std::numeric_limits::max()) + throw torrent::input_error(std::string("Invalid ") + label + " port number."); + + return static_cast(value); +} + // // Aliases with CMD_* for the below // diff --git a/src/command_network.cc b/src/command_network.cc index 2c242d14..275a4e91 100644 --- a/src/command_network.cc +++ b/src/command_network.cc @@ -54,14 +54,6 @@ set_listen_port_range(const std::string& arg) { torrent::runtime::client_config()->set_listen_port_range(port_first, port_last); } -uint16_t -checked_local_port_value(int64_t value, const char* label) { - if (value < 0 || value > 65535) - throw torrent::input_error(std::string("Invalid ") + label + " port number."); - - return static_cast(value); -} - torrent::Object get_encryption() { auto encryption_modes = torrent::runtime::network_config()->encryption_modes(); @@ -325,7 +317,7 @@ initialize_command_network() { auto nw_config = torrent::runtime::network_config(); CMD_ANY ("network.listen.port", [](auto, auto) { return torrent::runtime::network_manager()->listen_port(); }); - CMD_ANY_VALUE_V ("network.listen.port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_listen_port(value); }); + CMD_ANY_VALUE_V ("network.listen.port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_listen_port(checked_port_value(value, "listen")); }); CMD_ANY ("network.listen.port.random", [](auto, auto) { return torrent::runtime::client_config()->listen_port_random(); }); CMD_ANY_VALUE_V ("network.listen.port.random.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_listen_port_random(value); }); CMD_ANY ("network.listen.port.range", [](auto, auto) { return listen_port_range(); }); @@ -394,11 +386,11 @@ initialize_command_network() { CMD_ANY_STRING_V("network.local_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet6_address(str); }); CMD_ANY ("network.local_port", [nw_config](auto, auto) { return nw_config->local_port_best_match(); }); - CMD_ANY_VALUE_V ("network.local_port.set", [nw_config](auto, auto& value) { return nw_config->set_local_port(checked_local_port_value(value, "local")); }); + CMD_ANY_VALUE_V ("network.local_port.set", [nw_config](auto, auto& value) { return nw_config->set_local_port(checked_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_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_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); }); diff --git a/src/command_tracker.cc b/src/command_tracker.cc index 49f851b1..3a53cc47 100644 --- a/src/command_tracker.cc +++ b/src/command_tracker.cc @@ -142,7 +142,7 @@ initialize_command_tracker() { lt_log_print(torrent::LOG_DHT_ERROR, "dht.port.set is no longer supported, use dht.override_port.set", 0); }); CMD2_ANY ("dht.override_port", [](auto, auto) { return torrent::runtime::network_config()->override_dht_port(); }); - CMD2_ANY_VALUE_V ("dht.override_port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_dht_port(value); }); + CMD2_ANY_VALUE_V ("dht.override_port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_dht_port(checked_port_value(value, "DHT override")); }); CMD2_ANY_STRING ("dht.add_node", [](auto, auto& str) { return apply_dht_add_node(str); }); CMD2_ANY ("dht.statistics", [](auto, auto) { return control->dht_manager()->dht_statistics(); }); diff --git a/src/core/download_list.cc b/src/core/download_list.cc index 12b0e814..6fd24664 100644 --- a/src/core/download_list.cc +++ b/src/core/download_list.cc @@ -82,7 +82,7 @@ DownloadList::find(const torrent::HashString& hash) { DownloadList::iterator DownloadList::find_hex(const char* hash) { - if (strlen(hash) < 40) + if (strlen(hash) != 40) return end(); torrent::HashString key; diff --git a/src/rpc/parse.cc b/src/rpc/parse.cc index 9e8b4d58..f1e0b3bb 100644 --- a/src/rpc/parse.cc +++ b/src/rpc/parse.cc @@ -1,5 +1,7 @@ #include "config.h" +#include +#include #include #include #include @@ -113,13 +115,37 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { if (unit <= 0) throw torrent::input_error("Command::string_to_value_unit(...) received unit <= 0."); - char* last; + if (base != 0 && base != 8 && base != 10 && base != 16) + throw torrent::input_error("Command::string_to_value_unit(...) received invalid base."); + + while (parse_is_space(*src)) + src++; + + if (src[0] == '+') + return src; + + if (src[0] == '-') { + if (base == 8 || base == 16) + return src; + + if (src[1] == '0') + return src; + } + + char* last{}; + + errno = 0; *value = strtoll(src, &last, base); + if (errno == ERANGE) + return src; + if (last == src) { - if (strcasecmp(src, "no") == 0) { *value = 0; return src + strlen("no"); } - if (strcasecmp(src, "yes") == 0) { *value = 1; return src + strlen("yes"); } - if (strcasecmp(src, "true") == 0) { *value = 1; return src + strlen("true"); } + *value = 0; + + if (strcasecmp(src, "no") == 0) { *value = 0; return src + strlen("no"); } + if (strcasecmp(src, "yes") == 0) { *value = 1; return src + strlen("yes"); } + if (strcasecmp(src, "true") == 0) { *value = 1; return src + strlen("true"); } if (strcasecmp(src, "false") == 0) { *value = 0; return src + strlen("false"); } return src; diff --git a/src/rpc/parse.h b/src/rpc/parse.h index a7b8f422..7e79f169 100644 --- a/src/rpc/parse.h +++ b/src/rpc/parse.h @@ -1,37 +1,3 @@ -// rTorrent - BitTorrent client -// Copyright (C) 2005-2011, Jari Sundell -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// In addition, as a special exception, the copyright holders give -// permission to link the code of portions of this program with the -// OpenSSL library under certain conditions as described in each -// individual source file, and distribute linked combinations -// including the two. -// -// You must obey the GNU General Public License in all respects for -// all of the code used other than OpenSSL. If you modify file(s) -// with this exception, you may extend this exception to your version -// of the file(s), but you are not obligated to do so. If you do not -// wish to do so, delete this exception statement from your version. -// If you delete this exception statement from all source files in the -// program, then also delete it here. -// -// Contact: Jari Sundell - - #ifndef RTORRENT_RPC_PARSE_H #define RTORRENT_RPC_PARSE_H diff --git a/test/Makefile.am b/test/Makefile.am index 98eb8bc0..985163b4 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -51,6 +51,8 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \ rpc/test_command_slot.h \ rpc/test_object_storage.cc \ rpc/test_object_storage.h \ + rpc/test_parse.cc \ + rpc/test_parse.h \ rpc/test_parse_options.cc \ rpc/test_parse_options.h \ rpc/test_rpc_manager.cc \ @@ -59,6 +61,8 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \ src/test_command_dynamic.cc \ src/test_command_dynamic.h \ + src/test_command_groups.cc \ + src/test_command_groups.h \ src/test_command_ip.cc \ src/test_command_ip.h \ src/test_command_system.cc \ @@ -67,14 +71,10 @@ 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_input_path_input.cc \ - src/test_input_path_input.h \ - src/test_session_commit.cc \ - src/test_session_commit.h \ - src/test_session_storer.cc \ - src/test_session_storer.h \ + src/test_command_tracker.cc \ + src/test_command_tracker.h \ + src/test_download_list.cc \ + src/test_download_list.h \ src/test_setup.cc \ src/test_setup.h \ src/test_ui_download_list.cc \ diff --git a/test/rpc/test_parse.cc b/test/rpc/test_parse.cc new file mode 100644 index 00000000..75251520 --- /dev/null +++ b/test/rpc/test_parse.cc @@ -0,0 +1,95 @@ +#include "config.h" + +#include "test/rpc/test_parse.h" + +#include +#include + +#include "rpc/parse.h" + +CPPUNIT_TEST_SUITE_REGISTRATION(TestParse); + +void +TestParse::test_whole_value_in_range() { + int64_t value = 0; + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("9223372036854775807", &value)); + CPPUNIT_ASSERT_EQUAL(std::numeric_limits::max(), value); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("-9223372036854775808", &value)); + CPPUNIT_ASSERT_EQUAL(std::numeric_limits::min(), value); +} + +void +TestParse::test_whole_value_out_of_range() { + int64_t value = 0; + + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("9223372036854775808", &value)); + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("-9223372036854775809", &value)); + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("99999999999999999999999999", &value)); +} + +void +TestParse::test_whole_value_bases() { + int64_t value = 0; + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0x1f", &value)); + CPPUNIT_ASSERT_EQUAL(int64_t{31}, value); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0X1F", &value)); + CPPUNIT_ASSERT_EQUAL(int64_t{31}, value); + + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("-0x1f", &value)); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0022", &value)); + CPPUNIT_ASSERT_EQUAL(int64_t{022}, value); + + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("0028", &value)); + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("+022", &value)); + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("-022", &value)); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("22", &value)); + CPPUNIT_ASSERT_EQUAL(int64_t{22}, value); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("1f", &value, 16)); + CPPUNIT_ASSERT_EQUAL(int64_t{31}, value); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0x1f", &value, 16)); + CPPUNIT_ASSERT_EQUAL(int64_t{31}, value); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("22", &value, 8)); + CPPUNIT_ASSERT_EQUAL(int64_t{022}, value); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("022", &value, 8)); + CPPUNIT_ASSERT_EQUAL(int64_t{022}, value); + + const char* no_digits = "0x"; + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow(no_digits, &value)); + CPPUNIT_ASSERT_EQUAL(no_digits + 1, rpc::parse_value_nothrow(no_digits, &value)); + + const char* bad_digits = "0xzz"; + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow(bad_digits, &value)); + CPPUNIT_ASSERT_EQUAL(bad_digits + 1, rpc::parse_value_nothrow(bad_digits, &value)); +} + +void +TestParse::test_whole_value_prefixes() { + int64_t value = 0; + + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("+5", &value)); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow(" 5 ", &value)); + CPPUNIT_ASSERT_EQUAL(int64_t{5}, value); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("5k", &value)); + CPPUNIT_ASSERT_EQUAL(int64_t{5120}, value); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("yes", &value)); + CPPUNIT_ASSERT_EQUAL(int64_t{1}, value); + + CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("false", &value)); + CPPUNIT_ASSERT_EQUAL(int64_t{0}, value); + + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("junk", &value)); + CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("", &value)); +} diff --git a/test/rpc/test_parse.h b/test/rpc/test_parse.h new file mode 100644 index 00000000..586bed94 --- /dev/null +++ b/test/rpc/test_parse.h @@ -0,0 +1,18 @@ +#include "test/helpers/test_fixture.h" + +class TestParse : public test_fixture { + CPPUNIT_TEST_SUITE(TestParse); + + CPPUNIT_TEST(test_whole_value_in_range); + CPPUNIT_TEST(test_whole_value_out_of_range); + CPPUNIT_TEST(test_whole_value_bases); + CPPUNIT_TEST(test_whole_value_prefixes); + + CPPUNIT_TEST_SUITE_END(); + +public: + void test_whole_value_in_range(); + void test_whole_value_out_of_range(); + void test_whole_value_bases(); + void test_whole_value_prefixes(); +}; diff --git a/test/src/test_command_groups.cc b/test/src/test_command_groups.cc new file mode 100644 index 00000000..8769dc3b --- /dev/null +++ b/test/src/test_command_groups.cc @@ -0,0 +1,65 @@ +#include "config.h" + +#include "test/src/test_command_groups.h" + +#include + +#include "control.h" +#include "globals.h" +#include "rpc/parse_commands.h" + +CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandGroups); + +void initialize_command_groups(); + +static void +call_set(const char* value) { + torrent::Object::list_type args; + args.push_back(torrent::Object(int64_t{0})); + args.push_back(torrent::Object(std::string(value))); + + rpc::commands.call_command("choke_group.up.max.set", torrent::Object::create_list_range(args.begin(), args.end())); +} + +static int64_t +call_get(const char* key) { + return rpc::commands.call_command(key, torrent::Object(int64_t{0})).as_value(); +} + +void +TestCommandGroups::setUp() { + torrent::initialize_main_thread(); + torrent::initialize(); + + if (control == nullptr) + control = new Control; + + if (!rpc::commands.has("choke_group.up.max.set")) + initialize_command_groups(); +} + +void +TestCommandGroups::tearDown() { + torrent::cleanup(); +} + +void +TestCommandGroups::test_max_unchoked_in_range() { + call_set("50"); + CPPUNIT_ASSERT_EQUAL(int64_t{50}, call_get("choke_group.up.max")); + CPPUNIT_ASSERT_EQUAL(int64_t{0}, call_get("choke_group.up.max.unlimited")); + + call_set("-1"); + CPPUNIT_ASSERT_EQUAL(int64_t{1}, call_get("choke_group.up.max.unlimited")); +} + +void +TestCommandGroups::test_max_unchoked_out_of_range() { + call_set("50"); + + CPPUNIT_ASSERT_THROW(call_set("4294967296"), torrent::input_error); + CPPUNIT_ASSERT_EQUAL(int64_t{50}, call_get("choke_group.up.max")); + + CPPUNIT_ASSERT_THROW(call_set("-2"), torrent::input_error); + CPPUNIT_ASSERT_EQUAL(int64_t{50}, call_get("choke_group.up.max")); +} diff --git a/test/src/test_command_groups.h b/test/src/test_command_groups.h new file mode 100644 index 00000000..dbc41479 --- /dev/null +++ b/test/src/test_command_groups.h @@ -0,0 +1,17 @@ +#include "test/helpers/test_fixture.h" + +class TestCommandGroups : public test_fixture { + CPPUNIT_TEST_SUITE(TestCommandGroups); + + CPPUNIT_TEST(test_max_unchoked_in_range); + CPPUNIT_TEST(test_max_unchoked_out_of_range); + + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp(); + void tearDown(); + + void test_max_unchoked_in_range(); + void test_max_unchoked_out_of_range(); +}; diff --git a/test/src/test_command_tracker.cc b/test/src/test_command_tracker.cc new file mode 100644 index 00000000..42fcf156 --- /dev/null +++ b/test/src/test_command_tracker.cc @@ -0,0 +1,73 @@ +#include "config.h" + +#include "test/src/test_command_tracker.h" + +#include +#include + +#include "control.h" +#include "globals.h" +#include "command_helpers.h" +#include "rpc/parse_commands.h" + +CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandTracker); + +void initialize_command_tracker(); + +static void +call_set(const char* value) { + rpc::commands.call_command("dht.override_port.set", torrent::Object(std::string(value))); +} + +static uint16_t +override_port() { + return torrent::runtime::network_config()->override_dht_port(); +} + +void +TestCommandTracker::setUp() { + torrent::initialize_main_thread(); + torrent::initialize(); + + if (control == nullptr) + control = new Control; + + if (!rpc::commands.has("dht.override_port.set")) + initialize_command_tracker(); +} + +void +TestCommandTracker::tearDown() { + torrent::cleanup(); +} + +void +TestCommandTracker::test_dht_override_port_in_range() { + call_set("6881"); + CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, override_port()); + + call_set("65535"); + CPPUNIT_ASSERT_EQUAL(uint16_t{65535}, override_port()); +} + +void +TestCommandTracker::test_dht_override_port_out_of_range() { + call_set("6881"); + + CPPUNIT_ASSERT_THROW(call_set("70000"), torrent::input_error); + CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, override_port()); + + CPPUNIT_ASSERT_THROW(call_set("-1"), torrent::input_error); + CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, override_port()); +} + +void +TestCommandTracker::test_checked_port_value() { + CPPUNIT_ASSERT_EQUAL(uint16_t{0}, checked_port_value(0, "test")); + CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, checked_port_value(6881, "test")); + CPPUNIT_ASSERT_EQUAL(uint16_t{65535}, checked_port_value(65535, "test")); + + CPPUNIT_ASSERT_THROW(checked_port_value(-1, "test"), torrent::input_error); + CPPUNIT_ASSERT_THROW(checked_port_value(65536, "test"), torrent::input_error); + CPPUNIT_ASSERT_THROW(checked_port_value(4294967296, "test"), torrent::input_error); +} diff --git a/test/src/test_command_tracker.h b/test/src/test_command_tracker.h new file mode 100644 index 00000000..f5d1d8a3 --- /dev/null +++ b/test/src/test_command_tracker.h @@ -0,0 +1,19 @@ +#include "test/helpers/test_fixture.h" + +class TestCommandTracker : public test_fixture { + CPPUNIT_TEST_SUITE(TestCommandTracker); + + CPPUNIT_TEST(test_dht_override_port_in_range); + CPPUNIT_TEST(test_dht_override_port_out_of_range); + CPPUNIT_TEST(test_checked_port_value); + + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp(); + void tearDown(); + + void test_dht_override_port_in_range(); + void test_dht_override_port_out_of_range(); + void test_checked_port_value(); +}; diff --git a/test/src/test_download_list.cc b/test/src/test_download_list.cc new file mode 100644 index 00000000..2bd43cb7 --- /dev/null +++ b/test/src/test_download_list.cc @@ -0,0 +1,68 @@ +#include "config.h" + +#include "test/src/test_download_list.h" + +#include +#include +#include +#include +#include +#include + +#include "control.h" +#include "core/download.h" +#include "globals.h" + +CPPUNIT_TEST_SUITE_REGISTRATION(TestDownloadList); + +static std::string +insert_download(core::DownloadList* list) { + torrent::Object info = torrent::Object::create_map(); + info.insert_key("name", std::string("test_download_list")); + info.insert_key("length", int64_t{16}); + info.insert_key("piece length", int64_t{262144}); + info.insert_key("pieces", std::string(20, char(0))); + + auto* object = new torrent::Object(torrent::Object::create_map()); + object->insert_key("info", info); + + auto download = torrent::download_add(object, 0); + list->insert(new core::Download(download)); + + char buffer[41]; + + for (unsigned int i = 0; i < torrent::HashString::size_data; i++) + snprintf(buffer + i * 2, 3, "%02x", static_cast(download.info()->hash()[i])); + + return std::string(buffer, 40); +} + +void +TestDownloadList::setUp() { + torrent::initialize_main_thread(); + torrent::initialize(); + + if (control == nullptr) + control = new Control; + + m_hex = insert_download(&m_list); +} + +void +TestDownloadList::tearDown() { + m_list.clear(); + + torrent::cleanup(); +} + +void +TestDownloadList::test_find_hex() { + CPPUNIT_ASSERT(m_list.find_hex(m_hex.c_str()) != m_list.end()); +} + +void +TestDownloadList::test_find_hex_wrong_length() { + CPPUNIT_ASSERT(m_list.find_hex((m_hex + "f").c_str()) == m_list.end()); + CPPUNIT_ASSERT(m_list.find_hex((m_hex + m_hex).c_str()) == m_list.end()); + CPPUNIT_ASSERT(m_list.find_hex(m_hex.substr(0, 39).c_str()) == m_list.end()); +} diff --git a/test/src/test_download_list.h b/test/src/test_download_list.h new file mode 100644 index 00000000..63799697 --- /dev/null +++ b/test/src/test_download_list.h @@ -0,0 +1,24 @@ +#include + +#include "core/download_list.h" +#include "test/helpers/test_fixture.h" + +class TestDownloadList : public test_fixture { + CPPUNIT_TEST_SUITE(TestDownloadList); + + CPPUNIT_TEST(test_find_hex); + CPPUNIT_TEST(test_find_hex_wrong_length); + + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp(); + void tearDown(); + + void test_find_hex(); + void test_find_hex_wrong_length(); + +private: + core::DownloadList m_list; + std::string m_hex; +}; From 3c94b0332341c68ae6dada998041e3862e5ac633 Mon Sep 17 00:00:00 2001 From: rakshasa Date: Wed, 30 Sep 2026 12:26:33 +0200 Subject: [PATCH 11/12] Restrict parse value to strtoll with restrictions on input. --- src/rpc/parse.cc | 20 +++++++++++--------- test/Makefile.am | 8 ++++++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/rpc/parse.cc b/src/rpc/parse.cc index f1e0b3bb..83ab5932 100644 --- a/src/rpc/parse.cc +++ b/src/rpc/parse.cc @@ -118,18 +118,20 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { if (base != 0 && base != 8 && base != 10 && base != 16) throw torrent::input_error("Command::string_to_value_unit(...) received invalid base."); + const char* first = src; + while (parse_is_space(*src)) src++; if (src[0] == '+') - return src; + return first; if (src[0] == '-') { if (base == 8 || base == 16) - return src; + return first; if (src[1] == '0') - return src; + return first; } char* last{}; @@ -138,7 +140,7 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { *value = strtoll(src, &last, base); if (errno == ERANGE) - return src; + return first; if (last == src) { *value = 0; @@ -148,7 +150,7 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { if (strcasecmp(src, "true") == 0) { *value = 1; return src + strlen("true"); } if (strcasecmp(src, "false") == 0) { *value = 0; return src + strlen("false"); } - return src; + return first; } switch (*last) { @@ -156,15 +158,15 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { case 'B': ++last; break; case 'k': case 'K': - if (!value_fits_shifted(*value, 10)) return src; // overflow guard + if (!value_fits_shifted(*value, 10)) return first; // overflow guard *value = *value << 10; ++last; break; case 'm': case 'M': - if (!value_fits_shifted(*value, 20)) return src; // overflow guard + if (!value_fits_shifted(*value, 20)) return first; // overflow guard *value = *value << 20; ++last; break; case 'g': case 'G': - if (!value_fits_shifted(*value, 30)) return src; // overflow guard + if (!value_fits_shifted(*value, 30)) return first; // overflow guard *value = *value << 30; ++last; break; // case ' ': // case '\0': *value = *value * unit; break; @@ -172,7 +174,7 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { default: if (*value > std::numeric_limits::max() / unit || *value < std::numeric_limits::min() / unit) - return src; // overflow guard + return first; // overflow guard *value = *value * unit; break; diff --git a/test/Makefile.am b/test/Makefile.am index 985163b4..d756bd1c 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -71,10 +71,18 @@ 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_command_tracker.cc \ src/test_command_tracker.h \ src/test_download_list.cc \ src/test_download_list.h \ + src/test_input_path_input.cc \ + src/test_input_path_input.h \ + src/test_session_commit.cc \ + src/test_session_commit.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 \ From 3af0410a6e90fea0397272a21255c949a4e130f9 Mon Sep 17 00:00:00 2001 From: rakshasa Date: Wed, 30 Sep 2026 12:26:37 +0200 Subject: [PATCH 12/12] Restrict parse value to strtoll with restrictions on input. --- src/rpc/parse.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rpc/parse.cc b/src/rpc/parse.cc index 83ab5932..7a41f9ea 100644 --- a/src/rpc/parse.cc +++ b/src/rpc/parse.cc @@ -134,6 +134,9 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) { return first; } + if (base == 10 && src[0] == '0' && (src[1] >= '0' && src[1] <= '9')) + return first; + char* last{}; errno = 0;