Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libtorrent for openSUSE:Factory checked in at 2026-08-27 18:51:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libtorrent (Old) and /work/SRC/openSUSE:Factory/.libtorrent.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libtorrent" Thu Aug 27 18:51:47 2026 rev:41 rq:1373889 version:0.16.21 Changes: -------- --- /work/SRC/openSUSE:Factory/libtorrent/libtorrent.changes 2026-08-09 21:36:52.965395270 +0200 +++ /work/SRC/openSUSE:Factory/.libtorrent.new.1265/libtorrent.changes 2026-08-27 18:55:16.368742303 +0200 @@ -1,0 +2,8 @@ +Wed Aug 26 19:28:47 UTC 2026 - Jan Engelhardt <[email protected]> + +- Update to release 0.16.21 + * This release includes various bugfixes and minor improvements. + * Check for empty paths, zero-length datagrams, + file lengths. Restrict lengths of DHT tokens. + +------------------------------------------------------------------- Old: ---- libtorrent-0.16.20.tar.gz New: ---- libtorrent-0.16.21.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libtorrent.spec ++++++ --- /var/tmp/diff_new_pack.sOeJxG/_old 2026-08-27 18:55:17.100767860 +0200 +++ /var/tmp/diff_new_pack.sOeJxG/_new 2026-08-27 18:55:17.102767930 +0200 @@ -18,7 +18,7 @@ %define lname libtorrent49 Name: libtorrent -Version: 0.16.20 +Version: 0.16.21 Release: 0 Summary: A BitTorrent library written in C++ License: SUSE-GPL-2.0+-with-openssl-exception ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.sOeJxG/_old 2026-08-27 18:55:17.133769012 +0200 +++ /var/tmp/diff_new_pack.sOeJxG/_new 2026-08-27 18:55:17.136769117 +0200 @@ -1,5 +1,5 @@ -mtime: 1786032516 -commit: 9b4007009a13e6473225e993783a5543fd8169bfacb11a9c42acefe12867b5a8 +mtime: 1787772701 +commit: ea673a9befb1a9b3bcdfbab83956c209cb936f34e8b830e90f7cd564ecc8c5db url: https://src.opensuse.org/jengelh/libtorrent revision: master ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-08-26 21:31:41.000000000 +0200 @@ -0,0 +1 @@ +.osc ++++++ libtorrent-0.16.20.tar.gz -> libtorrent-0.16.21.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/configure new/libtorrent-0.16.21/configure --- old/libtorrent-0.16.20/configure 2026-08-06 10:14:25.000000000 +0200 +++ new/libtorrent-0.16.21/configure 2026-08-26 09:17:56.000000000 +0200 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.72 for libtorrent 0.16.20. +# Generated by GNU Autoconf 2.72 for libtorrent 0.16.21. # # Report bugs to <[email protected]>. # @@ -614,8 +614,8 @@ # Identity of this package. PACKAGE_NAME='libtorrent' PACKAGE_TARNAME='libtorrent' -PACKAGE_VERSION='0.16.20' -PACKAGE_STRING='libtorrent 0.16.20' +PACKAGE_VERSION='0.16.21' +PACKAGE_STRING='libtorrent 0.16.21' PACKAGE_BUGREPORT='[email protected]' PACKAGE_URL='' @@ -1408,7 +1408,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -'configure' configures libtorrent 0.16.20 to adapt to many kinds of systems. +'configure' configures libtorrent 0.16.21 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1479,7 +1479,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of libtorrent 0.16.20:";; + short | recursive ) echo "Configuration of libtorrent 0.16.21:";; esac cat <<\_ACEOF @@ -1634,7 +1634,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -libtorrent configure 0.16.20 +libtorrent configure 0.16.21 generated by GNU Autoconf 2.72 Copyright (C) 2023 Free Software Foundation, Inc. @@ -2352,7 +2352,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by libtorrent $as_me 0.16.20, which was +It was created by libtorrent $as_me 0.16.21, which was generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw @@ -4045,7 +4045,7 @@ # Define the identity of the package. PACKAGE='libtorrent' - VERSION='0.16.20' + VERSION='0.16.21' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -23348,7 +23348,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by libtorrent $as_me 0.16.20, which was +This file was extended by libtorrent $as_me 0.16.21, which was generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -23416,7 +23416,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -libtorrent config.status 0.16.20 +libtorrent config.status 0.16.21 configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/configure.ac new/libtorrent-0.16.21/configure.ac --- old/libtorrent-0.16.20/configure.ac 2026-08-06 10:14:07.000000000 +0200 +++ new/libtorrent-0.16.21/configure.ac 2026-08-26 09:17:41.000000000 +0200 @@ -1,4 +1,4 @@ -AC_INIT([[libtorrent]],[[0.16.20]],[[[email protected]]]) +AC_INIT([[libtorrent]],[[0.16.21]],[[[email protected]]]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIRS([scripts]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/data/chunk.cc new/libtorrent-0.16.21/src/data/chunk.cc --- old/libtorrent-0.16.20/src/data/chunk.cc 2026-08-06 10:14:07.000000000 +0200 +++ new/libtorrent-0.16.21/src/data/chunk.cc 2026-08-26 09:17:41.000000000 +0200 @@ -48,13 +48,34 @@ #include "chunk_iterator.h" namespace { -jmp_buf jmp_disk_full; +thread_local jmp_buf jmp_disk_full; void bus_handler(int, siginfo_t* si, void*) { if (si && si->si_code == BUS_ADRERR) longjmp(jmp_disk_full, 1); } + +class bus_handler_guard { +public: + bus_handler_guard() { + struct sigaction sa{}; + + sa.sa_sigaction = &bus_handler; + sa.sa_flags = SA_SIGINFO; + sigfillset(&sa.sa_mask); + + sigaction(SIGBUS, &sa, &m_oldact); + } + + ~bus_handler_guard() { sigaction(SIGBUS, &m_oldact, nullptr); } + + bus_handler_guard(const bus_handler_guard&) = delete; + bus_handler_guard& operator=(const bus_handler_guard&) = delete; + +private: + struct sigaction m_oldact; +}; } // namespace namespace torrent { @@ -236,12 +257,6 @@ // matching. bool Chunk::from_buffer(const void* buffer, uint32_t position, uint32_t length) { - struct sigaction sa{}, oldact; - sa.sa_sigaction = &bus_handler; - sa.sa_flags = SA_SIGINFO; - sigfillset(&sa.sa_mask); - sigaction(SIGBUS, &sa, &oldact); - if (position + length > m_chunkSize) throw internal_error("Chunk::from_buffer(...) position + length > m_chunkSize."); @@ -251,6 +266,8 @@ Chunk::data_type data; ChunkIterator itr(this, position, position + length); + bus_handler_guard guard; + if (setjmp(jmp_disk_full) == 0) { do { data = itr.data(); @@ -262,8 +279,6 @@ throw storage_error("no space left on disk"); } - sigaction(SIGBUS, &oldact, NULL); - return true; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/dht/dht_hash_map.h new/libtorrent-0.16.21/src/dht/dht_hash_map.h --- old/libtorrent-0.16.20/src/dht/dht_hash_map.h 2026-08-06 10:14:07.000000000 +0200 +++ new/libtorrent-0.16.21/src/dht/dht_hash_map.h 2026-08-26 09:17:41.000000000 +0200 @@ -39,6 +39,7 @@ #include "config.h" +#include <cstring> #include <unordered_map> #include "dht_node.h" @@ -60,35 +61,21 @@ struct hashstring_ptr_hash { size_t operator () (const HashString* n) const { -#if USE_ALIGNED - size_t result = 0; - const char *first = n->data() + hashstring_hash_ofs; - const char *last = first + sizeof(size_t); - - while (first != last) - result = (result << 8) + *first++; - + size_t result; + + std::memcpy(&result, n->data() + hashstring_hash_ofs, sizeof(result)); + return result; -#else - return *reinterpret_cast<const size_t*>(n->data() + hashstring_hash_ofs); -#endif } }; struct hashstring_hash { size_t operator () (const HashString& n) const { -#if USE_ALIGNED - size_t result = 0; - const char *first = n.data() + hashstring_hash_ofs; - const char *last = first + sizeof(size_t); - - while (first != last) - result = (result << 8) + *first++; - + size_t result; + + std::memcpy(&result, n.data() + hashstring_hash_ofs, sizeof(result)); + return result; -#else - return *reinterpret_cast<const size_t*>(n.data() + hashstring_hash_ofs); -#endif } }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/dht/dht_router.cc new/libtorrent-0.16.21/src/dht/dht_router.cc --- old/libtorrent-0.16.20/src/dht/dht_router.cc 2026-08-06 10:14:07.000000000 +0200 +++ new/libtorrent-0.16.21/src/dht/dht_router.cc 2026-08-26 09:17:41.000000000 +0200 @@ -231,8 +231,10 @@ if (sa_tmp->sa_family != AF_INET) return; - if (sap_is_any(sa_tmp)) - throw input_error("DhtRouter::contact() called with any address."); + if (sap_is_any(sa_tmp)) { + LT_LOG_THIS("not contacting node, any address : %s", sa_addr_str(sa_tmp.get()).c_str()); + return; + } sap_set_port(sa_tmp, port); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/dht/dht_server.cc new/libtorrent-0.16.21/src/dht/dht_server.cc --- old/libtorrent-0.16.20/src/dht/dht_server.cc 2026-08-06 10:14:07.000000000 +0200 +++ new/libtorrent-0.16.21/src/dht/dht_server.cc 2026-08-26 09:17:41.000000000 +0200 @@ -490,6 +490,11 @@ if (response[key_r_values].is_raw_list()) announce->receive_peers(response[key_r_values].as_raw_list()); + // Restrict the length of tokens. We echo them back in announce_peer, and the + // query has to fit in a single packet. + if (response[key_r_token].is_raw_string() && response[key_r_token].as_raw_string().size() > 64) + throw dht_error(dht_error_protocol, "Token length too long"); + if (response[key_r_token].is_raw_string()) add_transaction(std::unique_ptr<DhtTransaction>(new DhtTransactionAnnouncePeer(transaction->id(), transaction->address(), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/download/download_constructor.cc new/libtorrent-0.16.21/src/download/download_constructor.cc --- old/libtorrent-0.16.20/src/download/download_constructor.cc 2026-08-06 10:14:07.000000000 +0200 +++ new/libtorrent-0.16.21/src/download/download_constructor.cc 2026-08-26 09:17:41.000000000 +0200 @@ -4,6 +4,7 @@ #include <algorithm> #include <cstring> +#include <limits> #include "manager.h" #include "download/download_wrapper.h" @@ -37,12 +38,7 @@ if (is_invalid_path_element(b.get_key("name"))) throw input_error("Bad torrent file, \"name\" is an invalid path name."); - auto& name = b.get_key_string("name"); - - if (name.empty()) - throw internal_error("DownloadConstructor::parse_name(...) Ended up with an empty Path."); - - m_download->info()->set_name(name); + m_download->info()->set_name(b.get_key_string("name")); } void @@ -61,6 +57,9 @@ m_download->info()->set_flags(DownloadInfo::flag_meta_download); if (m_download->info()->is_meta_download()) { + if (b.has_key("length") || b.has_key("files")) + throw input_error("Meta-download has file entries."); + if (b.get_key_string("pieces").length() != HashString::size_data) throw input_error("Meta-download has invalid piece data."); @@ -68,10 +67,12 @@ parse_single_file(b, chunkSize); } else { - chunkSize = b.get_key_value("piece length"); + int64_t piece_length = b.get_key_value("piece length"); - if (chunkSize <= (1 << 10) || chunkSize > (512 << 20)) + if (piece_length <= (1 << 10) || piece_length > (512 << 20)) throw input_error("Torrent has an invalid \"piece length\"."); + + chunkSize = piece_length; } if (b.has_key("length")) { @@ -151,6 +152,7 @@ DownloadConstructor::is_valid_path_element(const Object& b) { return b.is_string() && + !b.as_string().empty() && b.as_string() != "." && b.as_string() != ".." && std::find(b.as_string().begin(), b.as_string().end(), '/') == b.as_string().end() && @@ -162,8 +164,13 @@ if (is_invalid_path_element(b.get_key("name"))) throw input_error("Bad torrent file, \"name\" is an invalid path name."); + int64_t length = chunkSize == 1 ? 1 : b.get_key_value("length"); + + if (length < 0) + throw input_error("Bad torrent file, invalid length for file."); + FileList* fileList = m_download->main()->file_list(); - fileList->initialize(chunkSize == 1 ? 1 : b.get_key_value("length"), chunkSize); + fileList->initialize(length, chunkSize); fileList->set_multi_file(false); Path path; @@ -199,7 +206,7 @@ int64_t length = object.get_key_value("length"); - if (length < 0 || torrent_size + length < 0) + if (length < 0 || length > std::numeric_limits<int64_t>::max() - torrent_size) throw input_error("Bad torrent file, invalid length for file."); torrent_size += length; @@ -214,6 +221,17 @@ split_list.emplace_back(length, path, attr_flags); } + std::vector<const Path*> sorted_paths; + sorted_paths.reserve(split_list.size()); + + for (const auto& split : split_list) + sorted_paths.push_back(&std::get<1>(split)); + + std::sort(sorted_paths.begin(), sorted_paths.end(), &Path::compare_less); + + if (std::adjacent_find(sorted_paths.begin(), sorted_paths.end(), &Path::is_prefix) != sorted_paths.end()) + throw input_error("Bad torrent file, a file path is a duplicate or the prefix of another."); + FileList* file_list = m_download->main()->file_list(); file_list->set_multi_file(true); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/net/curl_stack.cc new/libtorrent-0.16.21/src/net/curl_stack.cc --- old/libtorrent-0.16.20/src/net/curl_stack.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/net/curl_stack.cc 2026-08-26 09:17:41.000000000 +0200 @@ -1,5 +1,7 @@ #include "config.h" +#include "torrent/runtime/socket_manager.h" + #include "curl_stack.h" #include <algorithm> @@ -54,7 +56,7 @@ void CurlStack::set_max_cache_connections(unsigned int value) { if (value > 1024) - throw torrent::internal_error("CurlStack::set_max_cache_connections() called with a value greater than 1024."); + throw torrent::input_error("CurlStack::set_max_cache_connections() called with a value greater than 1024."); auto guard = lock_guard(); @@ -66,7 +68,7 @@ void CurlStack::set_max_host_connections(unsigned int value) { if (value > 1024) - throw torrent::internal_error("CurlStack::set_max_host_connections() called with a value greater than 1024."); + throw torrent::input_error("CurlStack::set_max_host_connections() called with a value greater than 1024."); auto guard = lock_guard(); @@ -77,8 +79,9 @@ void CurlStack::set_max_total_connections(unsigned int value) { - if (value > 4096) - throw torrent::internal_error("CurlStack::set_max_total_connections() called with a value greater than 4096."); + if (value > torrent::runtime::SocketManager::http_max_alloc) + throw torrent::internal_error("CurlStack::set_max_total_connections() called with a value greater than " + + std::to_string(torrent::runtime::SocketManager::http_max_alloc) + "."); auto guard = lock_guard(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/net/udns_resolver.cc new/libtorrent-0.16.21/src/net/udns_resolver.cc --- old/libtorrent-0.16.20/src/net/udns_resolver.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/net/udns_resolver.cc 2026-08-26 09:17:41.000000000 +0200 @@ -160,7 +160,7 @@ // Unrecoverable errors, like ENOMEM. if (::dns_status(m_ctx) != DNS_E_BADQUERY) - throw new internal_error("dns_submit_a4 failed"); + throw internal_error("dns_submit_a4 failed"); // UDNS will fail immediately during submission of malformed domain names, // e.g., `..`. In order to maintain a clean interface, keep track of this @@ -192,7 +192,7 @@ } if (::dns_status(m_ctx) != DNS_E_BADQUERY) - throw new internal_error("dns_submit_a6 failed"); + throw internal_error("dns_submit_a6 failed"); query->error_sin = EAI_NONAME; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/protocol/extensions.cc new/libtorrent-0.16.21/src/protocol/extensions.cc --- old/libtorrent-0.16.20/src/protocol/extensions.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/protocol/extensions.cc 2026-08-26 09:17:41.000000000 +0200 @@ -172,11 +172,17 @@ auto end = buffer; end += sprintf(end, "d5:added%d:", added_len); - memcpy(end, added.begin()->c_str(), added_len); + + if (!added.empty()) + memcpy(end, added.begin()->c_str(), added_len); + end += added_len; end += sprintf(end, "7:dropped%d:", removed_len); - memcpy(end, removed.begin()->c_str(), removed_len); + + if (!removed.empty()) + memcpy(end, removed.begin()->c_str(), removed_len); + end += removed_len; *end++ = 'e'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/protocol/handshake.cc new/libtorrent-0.16.21/src/protocol/handshake.cc --- old/libtorrent-0.16.20/src/protocol/handshake.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/protocol/handshake.cc 2026-08-26 09:17:41.000000000 +0200 @@ -467,7 +467,7 @@ m_encryption.info()->decrypt(m_readBuffer.position(), std::min<uint32_t>(m_readPos, m_readBuffer.remaining())); - } if (m_encryption.is_stream_encrypted()) { + } else if (m_encryption.is_stream_encrypted()) { LT_LOG_EXTRA_DEBUG_SA(m_address, "read_encryption_negotiation: peer offered encrypted stream", 0); if (m_encryption.policy().require_plaintext_stream()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/protocol/peer_connection_base.cc new/libtorrent-0.16.21/src/protocol/peer_connection_base.cc --- old/libtorrent-0.16.20/src/protocol/peer_connection_base.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/protocol/peer_connection_base.cc 2026-08-26 09:17:41.000000000 +0200 @@ -546,7 +546,8 @@ return false; } - uint32_t length = read_stream_throws(m_nullBuffer, std::min(quota, m_request_list.transfer()->piece().length() - m_request_list.transfer()->position())); + uint32_t remaining = m_request_list.transfer()->piece().length() - m_request_list.transfer()->position(); + uint32_t length = read_stream_throws(m_nullBuffer, std::min({quota, remaining, static_cast<uint32_t>(null_buffer_size)})); throttle->node_used(m_peer_chunks.download_throttle(), length); if (is_encrypted()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/data/file_list.cc new/libtorrent-0.16.21/src/torrent/data/file_list.cc --- old/libtorrent-0.16.20/src/torrent/data/file_list.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/data/file_list.cc 2026-08-26 09:17:41.000000000 +0200 @@ -411,6 +411,9 @@ // Update the path during open so that any changes to root dir // and file paths are properly handled. + if (entry->path()->empty()) + throw storage_error("Empty filename is not allowed."); + if (entry->path()->back().empty()) entry->set_frozen_path(std::string()); else @@ -422,8 +425,6 @@ if (entry->size_bytes() > m_max_file_size) throw storage_error("File exceedes the configured max file size."); - if (entry->path()->empty()) - throw storage_error("Empty filename is not allowed."); // Handle directory creation outside of open_file, so we can do // it here if necessary. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/download/choke_queue.cc new/libtorrent-0.16.21/src/torrent/download/choke_queue.cc --- old/libtorrent-0.16.20/src/torrent/download/choke_queue.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/download/choke_queue.cc 2026-08-26 09:17:41.000000000 +0200 @@ -156,6 +156,11 @@ void choke_queue::balance() { + // A group that was never given its slots cannot be balanced; calling through + // an empty std::function would terminate the client. + if (!m_slotCanUnchoke) + return; + LT_LOG_THIS("balancing queue: heuristics:%i currently_unchoked:%" PRIu32 " max_unchoked:%" PRIu32, m_heuristics, m_currently_unchoked, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/download/resource_manager.cc new/libtorrent-0.16.21/src/torrent/download/resource_manager.cc --- old/libtorrent-0.16.20/src/torrent/download/resource_manager.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/download/resource_manager.cc 2026-08-26 09:17:41.000000000 +0200 @@ -3,6 +3,7 @@ #include <algorithm> #include <functional> #include <limits> +#include <memory> #include <numeric> #include "download/download_main.h" @@ -62,13 +63,13 @@ auto group_itr = m_choke_groups.begin(); while (group_itr != m_choke_groups.end()) { - (*group_itr)->set_first(&*entry_itr); + (*group_itr)->set_first(std::to_address(entry_itr)); entry_itr = std::find_if(entry_itr, end(), [group_itr, this](value_type v) { return (std::distance(m_choke_groups.begin(), group_itr)) < v.group(); }); - (*group_itr)->set_last(&*entry_itr); + (*group_itr)->set_last(std::to_address(entry_itr)); group_itr++; } } @@ -79,14 +80,14 @@ auto group_itr = m_choke_groups.begin(); while (group_itr != m_choke_groups.end()) { - if ((*group_itr)->first() != &*entry_itr) + if ((*group_itr)->first() != std::to_address(entry_itr)) throw internal_error("ResourceManager::receive_tick() invalid first iterator."); entry_itr = std::find_if(entry_itr, end(), [group_itr, this](value_type v) { return (std::distance(m_choke_groups.begin(), group_itr)) < v.group(); }); - if ((*group_itr)->last() != &*entry_itr) + if ((*group_itr)->last() != std::to_address(entry_itr)) throw internal_error("ResourceManager::receive_tick() invalid last iterator."); group_itr++; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/download.cc new/libtorrent-0.16.21/src/torrent/download.cc --- old/libtorrent-0.16.20/src/torrent/download.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/download.cc 2026-08-26 09:17:41.000000000 +0200 @@ -339,6 +339,9 @@ if (m_ptr->main()->file_list()->bitfield()->empty()) throw input_error("Download::clear_range(...) Bitfield is empty."); + if (first > last || last > m_ptr->main()->file_list()->bitfield()->size_bits()) + throw input_error("Download::update_range(...) Range is out of bounds."); + if (flags & update_range_recheck) m_ptr->hash_checker()->hashing_ranges().insert(first, last); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/object_stream.cc new/libtorrent-0.16.21/src/torrent/object_stream.cc --- old/libtorrent-0.16.20/src/torrent/object_stream.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/object_stream.cc 2026-08-26 09:17:41.000000000 +0200 @@ -1,7 +1,10 @@ #include "config.h" +#include <algorithm> + #include "torrent/object_stream.h" +#include <charconv> #include <iostream> #include <iterator> #include <limits> @@ -16,23 +19,31 @@ static bool object_read_string(std::istream* input, std::string& str) { - uint32_t size; - *input >> size; + uint32_t remaining; + *input >> remaining; if (input->fail() || input->get() != ':') return false; - try { - str.resize(size); - - } catch (const std::length_error&) { + // Limit to 32 MiB, used in many clients. + if (remaining > 1 << 25) return false; - } - for (auto& c : str) { - if (!input->good()) - break; - c = input->get(); + str.clear(); + + while (remaining != 0) { + // Read in chunks of 64 KiB, it is very unlikely that a bencode stream is really that large so + // we're assuming this fails at some point. + uint32_t read_size = std::min<uint32_t>(remaining, 1 << 16); + + str.resize(str.size() + read_size); + + input->read(str.data() + str.size() - read_size, read_size); + + if (input->gcount() != read_size) + return false; + + remaining -= read_size; } return !input->fail(); @@ -40,45 +51,36 @@ static const char* object_read_bencode_c_value(const char* first, const char* last, int64_t& value) { - if (first == last) - return first; - - bool neg = false; + auto errc = std::from_chars(first, last, value, 10); - if (*first == '-') { - // Don't allow '-0', or '-' followed by non-numeral. - if ((first + 1) == last || *(first + 1) <= '0' || *(first + 1) > '9') - return first; - - neg = true; - first++; - } + if (errc.ec != std::errc() || errc.ptr == first) + throw torrent::bencode_error("Invalid bencode data: invalid integer."); - value = 0; + if (errc.ptr >= last || *errc.ptr != 'e') + throw torrent::bencode_error("Invalid bencode data: missing 'e' terminator."); - while (first != last && *first >= '0' && *first <= '9') - value = value * 10 + (*first++ - '0'); + if (value != 0 && *first == '0') + throw torrent::bencode_error("Invalid bencode data: leading zeros are not allowed."); - if (neg) - value = -value; + if (value == 0 && *first == '-') + throw torrent::bencode_error("Invalid bencode data: negative zero is not allowed."); - return first; + return errc.ptr + 1; } raw_string object_read_bencode_c_string(const char* first, const char* last) { - // Set the most-significant bit so that if there are no numbers in - // the input it will fail the length check, while "0" will shift the - // bit out. - unsigned int length = 0x1U << (std::numeric_limits<unsigned int>::digits - 1); + uint32_t length{}; - while (first != last && *first >= '0' && *first <= '9') - length = length * 10 + (*first++ - '0'); + auto errc = std::from_chars(first, last, length, 10); - if (length + 1 > static_cast<unsigned int>(std::distance(first, last)) || length + 1 == 0 || *first++ != ':') - throw torrent::bencode_error("Invalid bencode data."); + if (errc.ec != std::errc() || errc.ptr == first || errc.ptr == last || *errc.ptr != ':') + throw torrent::bencode_error("Invalid bencode data: string length is invalid."); - return raw_string(first, length); + if (std::distance(errc.ptr + 1, last) < static_cast<std::ptrdiff_t>(length)) + throw torrent::bencode_error("Invalid bencode data: string length exceeds available data."); + + return raw_string(errc.ptr + 1, length); } // Could consider making this non-recursive, but they seldomly are @@ -185,12 +187,7 @@ switch (*first) { case 'i': *object = Object::create_value(); - first = object_read_bencode_c_value(first + 1, last, object->as_value()); - - if (first == last || *first++ != 'e') - break; - - return first; + return object_read_bencode_c_value(first + 1, last, object->as_value()); case 'l': if (++depth >= 1024) @@ -434,19 +431,23 @@ if (src == 0) return object_write_bencode_c_char(output, '0'); + uint64_t value; + if (src < 0) { object_write_bencode_c_char(output, '-'); - src = -src; + value = -static_cast<uint64_t>(src); + } else { + value = static_cast<uint64_t>(src); } char buffer[20]; char* first = buffer + 20; // We don't need locale support, so just do this directly. - while (src != 0) { - *--first = '0' + src % 10; + while (value != 0) { + *--first = '0' + value % 10; - src /= 10; + value /= 10; } object_write_bencode_c_string(output, first, 20 - std::distance(buffer, first)); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/path.cc new/libtorrent-0.16.21/src/torrent/path.cc --- old/libtorrent-0.16.20/src/torrent/path.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/path.cc 2026-08-26 09:17:41.000000000 +0200 @@ -23,6 +23,19 @@ } } +bool +Path::compare_less(const Path* left, const Path* right) { + return std::lexicographical_compare(left->begin(), left->end(), right->begin(), right->end(), + [](const auto& l, const auto& r) { return l.str() < r.str(); }); +} + +bool +Path::is_prefix(const Path* prefix, const Path* path) { + return prefix->size() <= path->size() && + std::equal(prefix->begin(), prefix->end(), path->begin(), + [](const auto& l, const auto& r) { return l.str() == r.str(); }); +} + std::string Path::as_string() const { if (empty()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/path.h new/libtorrent-0.16.21/src/torrent/path.h --- old/libtorrent-0.16.20/src/torrent/path.h 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/path.h 2026-08-26 09:17:41.000000000 +0200 @@ -36,6 +36,9 @@ base_type* base() { return this; } const base_type* base() const { return this; } + + static bool compare_less(const Path* left, const Path* right); + static bool is_prefix(const Path* prefix, const Path* path); }; inline void Path::push_back(const std::string& path) { insert_path(end(), path); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/runtime/network_config.cc new/libtorrent-0.16.21/src/torrent/runtime/network_config.cc --- old/libtorrent-0.16.20/src/torrent/runtime/network_config.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/runtime/network_config.cc 2026-08-26 09:17:41.000000000 +0200 @@ -264,6 +264,53 @@ return sa_addr_str(m_local_inet6_address.get()); } +uint16_t +NetworkConfig::local_inet_port() const { + auto guard = lock_guard(); + return m_local_inet_port; +} + +uint16_t +NetworkConfig::local_inet6_port() const { + auto guard = lock_guard(); + return m_local_inet6_port; +} + +uint16_t +NetworkConfig::local_port_for_family(int family) const { + auto guard = lock_guard(); + + switch (family) { + case AF_INET: return m_local_inet_port; + case AF_INET6: return m_local_inet6_port; + default: + throw input_error("NetworkConfig::local_port_for_family() called with invalid address family"); + } +} + +uint16_t +NetworkConfig::local_port_best_match() const { + auto guard = lock_guard(); + + if (m_local_inet_port == 0) + return m_local_inet6_port; + + if (m_local_inet6_port == 0) + return m_local_inet_port; + + if (m_prefer_ipv6) { + if (m_block_ipv6 && !m_block_ipv4) + return m_local_inet_port; + + return m_local_inet6_port; + } + + if (m_block_ipv4 && !m_block_ipv6) + return m_local_inet6_port; + + return m_local_inet_port; +} + void NetworkConfig::set_bind_address(const sockaddr* sa) { auto guard = lock_guard(); @@ -346,6 +393,25 @@ } void +NetworkConfig::set_local_inet_port(uint16_t port) { + auto guard = lock_guard(); + m_local_inet_port = port; +} + +void +NetworkConfig::set_local_inet6_port(uint16_t port) { + auto guard = lock_guard(); + m_local_inet6_port = port; +} + +void +NetworkConfig::set_local_port(uint16_t port) { + auto guard = lock_guard(); + m_local_inet_port = port; + m_local_inet6_port = port; +} + +void NetworkConfig::set_encryption_modes(encryption_mode handshake, encryption_mode stream) { if (handshake == ENCRYPTION_MODE_DENY && stream == ENCRYPTION_MODE_REQUIRE) throw internal_error("Invalid encryption modes: handshake_deny/stream_require"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/runtime/network_config.h new/libtorrent-0.16.21/src/torrent/runtime/network_config.h --- old/libtorrent-0.16.20/src/torrent/runtime/network_config.h 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/runtime/network_config.h 2026-08-26 09:17:41.000000000 +0200 @@ -82,6 +82,13 @@ c_sa_shared_ptr local_inet6_address_or_null() const; std::string local_inet6_address_str() const; + // Ports reported to trackers, parallel to the local addresses above. A port of 0 means unset, + // in which case trackers fall back to reporting runtime::listen_port(). + uint16_t local_inet_port() const; + uint16_t local_inet6_port() const; + uint16_t local_port_for_family(int family) const; + uint16_t local_port_best_match() const; + void set_bind_address(const sockaddr* sa); void set_bind_address_str(const std::string& addr); void set_bind_inet_address(const sockaddr* sa); @@ -95,6 +102,10 @@ void set_local_inet6_address(const sockaddr* sa); void set_local_inet6_address_str(const std::string& addr); + void set_local_inet_port(uint16_t port); + void set_local_inet6_port(uint16_t port); + void set_local_port(uint16_t port); + int listen_backlog() const; void set_listen_backlog(int backlog); @@ -170,6 +181,9 @@ c_sa_shared_ptr m_local_inet_address; c_sa_shared_ptr m_local_inet6_address; + uint16_t m_local_inet_port{0}; + uint16_t m_local_inet6_port{0}; + int m_listen_backlog{SOMAXCONN}; uint16_t m_override_dht_port{0}; uint32_t m_send_buffer_size{0}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/runtime/network_manager.cc new/libtorrent-0.16.21/src/torrent/runtime/network_manager.cc --- old/libtorrent-0.16.20/src/torrent/runtime/network_manager.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/runtime/network_manager.cc 2026-08-26 09:17:41.000000000 +0200 @@ -264,7 +264,7 @@ listen_open_unsafe(m_listen_port, m_listen_port); } catch (const base_error& e) { - LT_LOG_NOTICE("Could not restart listen socket: %" PRIu16 " : %s", e.what()); + LT_LOG_NOTICE("Could not restart listen socket: %" PRIu16 " : %s", m_listen_port, e.what()); return; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/runtime/socket_manager.cc new/libtorrent-0.16.21/src/torrent/runtime/socket_manager.cc --- old/libtorrent-0.16.20/src/torrent/runtime/socket_manager.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/runtime/socket_manager.cc 2026-08-26 09:17:41.000000000 +0200 @@ -20,6 +20,25 @@ constexpr uint32_t allocation_headroom = 8; uint32_t +calculate_alloc_limit(torrent::runtime::socket_manager_category_t category) { + if (category == torrent::runtime::category_http) + return torrent::runtime::SocketManager::http_max_alloc; + + if (category == torrent::runtime::category_files) + return torrent::runtime::SocketManager::files_max_alloc; + + return torrent::runtime::SocketManager::category_max_alloc; +} + +uint32_t +calculate_alloc_minimum(torrent::runtime::socket_manager_category_t category) { + if (category == torrent::runtime::category_files) + return torrent::runtime::SocketManager::files_min_alloc; + + return 0; +} + +uint32_t calculate_min_generic(uint32_t open_max) { if (open_max >= 16384) return 12288; @@ -127,6 +146,16 @@ } uint32_t +SocketManager::category_alloc_limit(category_t category) { + return calculate_alloc_limit(category); +} + +uint32_t +SocketManager::category_alloc_minimum(category_t category) { + return calculate_alloc_minimum(category); +} + +uint32_t SocketManager::generic_min_allocation() { return calculate_min_generic(m_max_size); } @@ -208,6 +237,14 @@ allocation = std::max(allocation, m_category_min_alloc[static_cast<uint32_t>(category)].load()); allocation = std::min(allocation, m_category_max_alloc[static_cast<uint32_t>(category)].load()); + if (allocation > calculate_alloc_limit(category)) + throw input_error("adjust_allocation: allocation exceeds the category limit : " + + std::to_string(allocation) + " > " + std::to_string(calculate_alloc_limit(category))); + + if (allocation < calculate_alloc_minimum(category)) + throw input_error("adjust_allocation: allocation below the category minimum : " + + std::to_string(allocation) + " < " + std::to_string(calculate_alloc_minimum(category))); + total_allocated += allocation; new_max_size[static_cast<uint32_t>(category)] = allocation; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/runtime/socket_manager.h new/libtorrent-0.16.21/src/torrent/runtime/socket_manager.h --- old/libtorrent-0.16.20/src/torrent/runtime/socket_manager.h 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/runtime/socket_manager.h 2026-08-26 09:17:41.000000000 +0200 @@ -53,6 +53,9 @@ public: static constexpr uint32_t category_count = 5; static constexpr uint32_t category_max_alloc = 1000000; + static constexpr uint32_t http_max_alloc = 4096; + static constexpr uint32_t files_max_alloc = 1 << 16; + static constexpr uint32_t files_min_alloc = 4; static constexpr int flag_inactive = (1 << 0); using category_t = socket_manager_category_t; @@ -66,6 +69,9 @@ uint32_t category_min_allocation(category_t category); uint32_t category_max_allocation(category_t category); + uint32_t category_alloc_limit(category_t category); + uint32_t category_alloc_minimum(category_t category); + uint32_t generic_min_allocation(); uint32_t reserved_allocation(); uint32_t available_allocation(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/system/poll_epoll.cc new/libtorrent-0.16.21/src/torrent/system/poll_epoll.cc --- old/libtorrent-0.16.20/src/torrent/system/poll_epoll.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/system/poll_epoll.cc 2026-08-26 09:17:41.000000000 +0200 @@ -13,6 +13,7 @@ #include "torrent/net/fd.h" #include "torrent/system/event.h" #include "torrent/system/thread.h" +#include "torrent/utils/chrono.h" #include "torrent/utils/log.h" #define LT_LOG(log_fmt, ...) \ @@ -195,6 +196,8 @@ Poll::do_poll(std::chrono::microseconds timeout) { int status = poll(timeout); + this_thread::thread()->set_cached_time(torrent::utils::time_since_epoch()); + if (status == -1) { if (errno != EINTR) throw internal_error("Poll::work() " + std::string(std::strerror(errno))); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/torrent/system/poll_kqueue.cc new/libtorrent-0.16.21/src/torrent/system/poll_kqueue.cc --- old/libtorrent-0.16.20/src/torrent/system/poll_kqueue.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/torrent/system/poll_kqueue.cc 2026-08-26 09:17:41.000000000 +0200 @@ -15,6 +15,7 @@ #include "torrent/net/fd.h" #include "torrent/system/event.h" #include "torrent/system/thread.h" +#include "torrent/utils/chrono.h" // TODO: Change to use unordered_map, and at regular intervals trim the size? @@ -196,6 +197,8 @@ Poll::do_poll(std::chrono::microseconds timeout) { int status = poll(timeout); + this_thread::thread()->set_cached_time(torrent::utils::time_since_epoch()); + if (status == -1) { if (errno != EINTR) throw internal_error("Poll::do_poll() error: " + std::string(std::strerror(errno))); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/tracker/tracker_http.cc new/libtorrent-0.16.21/src/tracker/tracker_http.cc --- old/libtorrent-0.16.20/src/tracker/tracker_http.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/tracker/tracker_http.cc 2026-08-26 09:17:41.000000000 +0200 @@ -313,7 +313,9 @@ if (m_params.numwant >= 0 && state != tracker::TrackerState::EVENT_STOPPED) s << "&numwant=" << m_params.numwant; - s << "&port=" << runtime::listen_port() + auto local_port = runtime::network_config()->local_port_for_family(family); + + s << "&port=" << (local_port != 0 ? local_port : runtime::listen_port()) << "&uploaded=" << m_params.uploaded_adjusted << "&downloaded=" << m_params.completed_adjusted << "&left=" << m_params.download_left; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/tracker/tracker_udp.cc new/libtorrent-0.16.21/src/tracker/tracker_udp.cc --- old/libtorrent-0.16.20/src/tracker/tracker_udp.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/tracker/tracker_udp.cc 2026-08-26 09:17:41.000000000 +0200 @@ -298,7 +298,9 @@ buffer.write_32(info().key); buffer.write_32(m_params.numwant); - buffer.write_16(runtime::listen_port()); + + auto local_port = runtime::network_config()->local_port_for_family(family); + buffer.write_16(local_port != 0 ? local_port : runtime::listen_port()); if (buffer.size_end() != 98) throw internal_error("TrackerUdp::prepare_announce() unexpected buffer size."); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/tracker/udp_router.cc new/libtorrent-0.16.21/src/tracker/udp_router.cc --- old/libtorrent-0.16.20/src/tracker/udp_router.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/tracker/udp_router.cc 2026-08-26 09:17:41.000000000 +0200 @@ -553,7 +553,7 @@ } if (bytes_read == 0) - throw internal_error("UdpRouter::event_read() read datagram of length 0"); + continue; m_buffer.set_end(bytes_read); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/src/utils/diffie_hellman.cc new/libtorrent-0.16.21/src/utils/diffie_hellman.cc --- old/libtorrent-0.16.20/src/utils/diffie_hellman.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/src/utils/diffie_hellman.cc 2026-08-26 09:17:41.000000000 +0200 @@ -56,12 +56,25 @@ DiffieHellman::compute_secret(const unsigned char *pubkey, unsigned int length) { BIGNUM* k = BN_bin2bn(pubkey, length, nullptr); - m_secret = std::make_unique<char[]>(DH_size(dh_get(m_dh))); - m_size = DH_compute_key(reinterpret_cast<unsigned char*>(m_secret.get()), k, dh_get(m_dh)); - + int secret_size = DH_size(dh_get(m_dh)); + + m_secret = std::make_unique<char[]>(secret_size); + + int computed_size = DH_compute_key(reinterpret_cast<unsigned char*>(m_secret.get()), k, dh_get(m_dh)); + BN_free(k); - return m_size != -1; + if (computed_size == -1) + return false; + + if (computed_size < secret_size) { + std::memmove(m_secret.get() + secret_size - computed_size, m_secret.get(), computed_size); + std::memset(m_secret.get(), 0, secret_size - computed_size); + } + + m_size = secret_size; + + return true; } void diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/test/torrent/object_stream_test.cc new/libtorrent-0.16.21/test/torrent/object_stream_test.cc --- old/libtorrent-0.16.20/test/torrent/object_stream_test.cc 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/test/torrent/object_stream_test.cc 2026-08-26 09:17:42.000000000 +0200 @@ -195,3 +195,82 @@ obj.as_map()["d"] = torrent::Object(); CPPUNIT_ASSERT(object_write_bencode(obj, "d1:ai1e1:b4:test1:cl3:fooee")); } + +namespace { + +bool +read_string_accepted(const char* input) { + try { + uint64_t string_length = std::strlen(input); + + torrent::Object tmp; + auto last = torrent::object_read_bencode_c(input, input + string_length, &tmp); + + return tmp.is_string() && last == input + string_length; + + } catch (const torrent::bencode_error&) { + return false; + } +} + +} // namespace anonymous + +void +ObjectStreamTest::test_read_string_length() { + CPPUNIT_ASSERT(read_string_accepted("0:")); + CPPUNIT_ASSERT(read_string_accepted("1:a")); + CPPUNIT_ASSERT(read_string_accepted("4:abcd")); + CPPUNIT_ASSERT(read_string_accepted("10:abcdefghij")); + + CPPUNIT_ASSERT(!read_string_accepted("4294967296:abcd")); + CPPUNIT_ASSERT(!read_string_accepted("4294967300:abcd")); + CPPUNIT_ASSERT(!read_string_accepted("18446744073709551616:abcd")); + CPPUNIT_ASSERT(!read_string_accepted(":abcd")); + + CPPUNIT_ASSERT(!read_string_accepted("4:abc")); + CPPUNIT_ASSERT(!read_string_accepted("4:abcde")); + CPPUNIT_ASSERT(!read_string_accepted("-4:abcd")); + + CPPUNIT_ASSERT(!read_string_accepted("0x4:abcd")); + CPPUNIT_ASSERT(!read_string_accepted("0X4:abcd")); + CPPUNIT_ASSERT(!read_string_accepted("a:abcdefghij")); +} + +static bool +read_value_ok(const char* input, int64_t expected) { + try { + torrent::Object tmp; + const char* last = input + std::strlen(input); + + return torrent::object_read_bencode_c(input, last, &tmp) == last && + tmp.is_value() && tmp.as_value() == expected; + + } catch (const torrent::bencode_error&) { + return false; + } +} + +static bool +read_value_rejected(const char* input) { + try { + torrent::Object tmp; + torrent::object_read_bencode_c(input, input + std::strlen(input), &tmp); + return false; + + } catch (const torrent::bencode_error&) { + return true; + } +} + +void +ObjectStreamTest::test_read_value_bounds() { + CPPUNIT_ASSERT(read_value_ok("i0e", 0)); + CPPUNIT_ASSERT(read_value_ok("i-1e", -1)); + CPPUNIT_ASSERT(read_value_ok("i9223372036854775807e", std::numeric_limits<int64_t>::max())); + CPPUNIT_ASSERT(read_value_ok("i-9223372036854775808e", std::numeric_limits<int64_t>::min())); + + CPPUNIT_ASSERT(read_value_rejected("i9223372036854775808e")); + CPPUNIT_ASSERT(read_value_rejected("i-9223372036854775809e")); + CPPUNIT_ASSERT(read_value_rejected("i18446744073709551615e")); + CPPUNIT_ASSERT(read_value_rejected("i99999999999999999999e")); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libtorrent-0.16.20/test/torrent/object_stream_test.h new/libtorrent-0.16.21/test/torrent/object_stream_test.h --- old/libtorrent-0.16.20/test/torrent/object_stream_test.h 2026-08-06 10:14:08.000000000 +0200 +++ new/libtorrent-0.16.21/test/torrent/object_stream_test.h 2026-08-26 09:17:42.000000000 +0200 @@ -9,6 +9,8 @@ CPPUNIT_TEST(testOutputMask); CPPUNIT_TEST(testBuffer); CPPUNIT_TEST(testReadBencodeC); + CPPUNIT_TEST(test_read_string_length); + CPPUNIT_TEST(test_read_value_bounds); CPPUNIT_TEST(test_read_skip); CPPUNIT_TEST(test_read_skip_invalid); @@ -22,6 +24,8 @@ void testBuffer(); void testReadBencodeC(); + void test_read_string_length(); + void test_read_value_bounds(); void test_read_skip(); void test_read_skip_invalid();
