Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package rtorrent for openSUSE:Factory 
checked in at 2026-04-09 17:20:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rtorrent (Old)
 and      /work/SRC/openSUSE:Factory/.rtorrent.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rtorrent"

Thu Apr  9 17:20:32 2026 rev:28 rq:1345576 version:0.16.9

Changes:
--------
--- /work/SRC/openSUSE:Factory/rtorrent/rtorrent.changes        2026-03-16 
14:21:23.411405589 +0100
+++ /work/SRC/openSUSE:Factory/.rtorrent.new.21863/rtorrent.changes     
2026-04-09 17:20:45.933664272 +0200
@@ -1,0 +2,8 @@
+Thu Apr  9 13:57:46 UTC 2026 - Jan Engelhardt <[email protected]>
+
+- Update to release 0.16.9
+  * Improved DNS cache handling and various bug fixes
+  * Trusted/Untrusted XMLRPC Connection Security Model
+  * SCGI Systemd Socket Activation Support
+
+-------------------------------------------------------------------

Old:
----
  rtorrent-0.16.8.tar.gz

New:
----
  rtorrent-0.16.9.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ rtorrent.spec ++++++
--- /var/tmp/diff_new_pack.nmvy9M/_old  2026-04-09 17:20:47.481728386 +0200
+++ /var/tmp/diff_new_pack.nmvy9M/_new  2026-04-09 17:20:47.481728386 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           rtorrent
-Version:        0.16.8
+Version:        0.16.9
 Release:        0
 Summary:        Console-based BitTorrent client
 License:        SUSE-GPL-2.0+-with-openssl-exception

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.nmvy9M/_old  2026-04-09 17:20:47.513729711 +0200
+++ /var/tmp/diff_new_pack.nmvy9M/_new  2026-04-09 17:20:47.517729877 +0200
@@ -1,5 +1,5 @@
-mtime: 1773653033
-commit: 327ebbbfdc243b23a33c29682bee8a4bb249dfd98cf37832edc02d065bce5495
+mtime: 1775743098
+commit: 1a5485ec9c6a9a88e829354c9404787bd95698e5e76215c5e1c7aac77b333ebb
 url: https://src.opensuse.org/jengelh/rtorrent
 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-04-09 15:58:29.000000000 +0200
@@ -0,0 +1 @@
+.osc

++++++ rtorrent-0.16.8.tar.gz -> rtorrent-0.16.9.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/Makefile.in 
new/rtorrent-0.16.9/Makefile.in
--- old/rtorrent-0.16.8/Makefile.in     2026-03-15 18:07:04.000000000 +0100
+++ new/rtorrent-0.16.9/Makefile.in     2026-04-06 16:10:12.000000000 +0200
@@ -341,6 +341,8 @@
 SET_MAKE = @SET_MAKE@
 SHELL = @SHELL@
 STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
 VERSION = @VERSION@
 abs_builddir = @abs_builddir@
 abs_srcdir = @abs_srcdir@
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/config.h.in 
new/rtorrent-0.16.9/config.h.in
--- old/rtorrent-0.16.8/config.h.in     2026-03-15 18:07:03.000000000 +0100
+++ new/rtorrent-0.16.9/config.h.in     2026-04-06 16:10:11.000000000 +0200
@@ -87,6 +87,9 @@
 /* Define to 1 if you have the <string.h> header file. */
 #undef HAVE_STRING_H
 
+/* Support for systemd socket activation. */
+#undef HAVE_SYSTEMD
+
 /* Define to 1 if you have the <sys/stat.h> header file. */
 #undef HAVE_SYS_STAT_H
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/configure 
new/rtorrent-0.16.9/configure
--- old/rtorrent-0.16.8/configure       2026-03-15 18:07:03.000000000 +0100
+++ new/rtorrent-0.16.9/configure       2026-04-06 16:10:11.000000000 +0200
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.72 for rtorrent 0.16.8.
+# Generated by GNU Autoconf 2.72 for rtorrent 0.16.9.
 #
 # Report bugs to <[email protected]>.
 #
@@ -614,8 +614,8 @@
 # Identity of this package.
 PACKAGE_NAME='rtorrent'
 PACKAGE_TARNAME='rtorrent'
-PACKAGE_VERSION='0.16.8'
-PACKAGE_STRING='rtorrent 0.16.8'
+PACKAGE_VERSION='0.16.9'
+PACKAGE_STRING='rtorrent 0.16.9'
 PACKAGE_BUGREPORT='[email protected]'
 PACKAGE_URL=''
 
@@ -656,6 +656,8 @@
 am__EXEEXT_TRUE
 LTLIBOBJS
 LIBOBJS
+SYSTEMD_LIBS
+SYSTEMD_CFLAGS
 LUA_INCLUDE
 LUA_LIB
 pkgluaexecdir
@@ -834,6 +836,7 @@
 with_xmlrpc_c
 with_lua
 with_xmlrpc_tinyxml2
+with_systemd
 enable_year2038
 '
       ac_precious_vars='build_alias
@@ -861,7 +864,9 @@
 DEPENDENCIES_LIBS
 LUA
 LUA_LIB
-LUA_INCLUDE'
+LUA_INCLUDE
+SYSTEMD_CFLAGS
+SYSTEMD_LIBS'
 
 
 # Initialize some variables set by options.
@@ -1410,7 +1415,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 rtorrent 0.16.8 to adapt to many kinds of systems.
+'configure' configures rtorrent 0.16.9 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1481,7 +1486,7 @@
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of rtorrent 0.16.8:";;
+     short | recursive ) echo "Configuration of rtorrent 0.16.9:";;
    esac
   cat <<\_ACEOF
 
@@ -1526,6 +1531,8 @@
   --with-xmlrpc-c=PATH    enable XMLRPC-C support
   --with-lua              enable LUA support
   --with-xmlrpc-tinyxml2  enable XMLRPC support via tinyxml2
+  --with-systemd          enable systemd socket activation support
+                          [[default=no]]
 
 Some influential environment variables:
   CC          C compiler command
@@ -1560,6 +1567,10 @@
   LUA         The Lua interpreter, e.g. /usr/bin/lua5.1
   LUA_LIB     The Lua library, e.g. -llua5.1
   LUA_INCLUDE The Lua includes, e.g. -I/usr/include/lua5.1
+  SYSTEMD_CFLAGS
+              C compiler flags for SYSTEMD, overriding pkg-config
+  SYSTEMD_LIBS
+              linker flags for SYSTEMD, overriding pkg-config
 
 Use these variables to override the choices made by 'configure' or to help
 it to find libraries and programs with nonstandard names/locations.
@@ -1628,7 +1639,7 @@
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-rtorrent configure 0.16.8
+rtorrent configure 0.16.9
 generated by GNU Autoconf 2.72
 
 Copyright (C) 2023 Free Software Foundation, Inc.
@@ -2257,7 +2268,7 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by rtorrent $as_me 0.16.8, which was
+It was created by rtorrent $as_me 0.16.9, which was
 generated by GNU Autoconf 2.72.  Invocation command line was
 
   $ $0$ac_configure_args_raw
@@ -3950,7 +3961,7 @@
 
 # Define the identity of the package.
  PACKAGE='rtorrent'
- VERSION='0.16.8'
+ VERSION='0.16.9'
 
 
 printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -21553,19 +21564,19 @@
 fi
 
 pkg_failed=no
-{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent >= 
0.16.8" >&5
-printf %s "checking for libtorrent >= 0.16.8... " >&6; }
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent >= 
0.16.9" >&5
+printf %s "checking for libtorrent >= 0.16.9... " >&6; }
 
 if test -n "$DEPENDENCIES_CFLAGS"; then
     pkg_cv_DEPENDENCIES_CFLAGS="$DEPENDENCIES_CFLAGS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists 
--print-errors \"libtorrent >= 0.16.8\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "libtorrent >= 0.16.8") 2>&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists 
--print-errors \"libtorrent >= 0.16.9\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libtorrent >= 0.16.9") 2>&5
   ac_status=$?
   printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_DEPENDENCIES_CFLAGS=`$PKG_CONFIG --cflags "libtorrent >= 0.16.8" 
2>/dev/null`
+  pkg_cv_DEPENDENCIES_CFLAGS=`$PKG_CONFIG --cflags "libtorrent >= 0.16.9" 
2>/dev/null`
                      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -21577,12 +21588,12 @@
     pkg_cv_DEPENDENCIES_LIBS="$DEPENDENCIES_LIBS"
  elif test -n "$PKG_CONFIG"; then
     if test -n "$PKG_CONFIG" && \
-    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists 
--print-errors \"libtorrent >= 0.16.8\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "libtorrent >= 0.16.8") 2>&5
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists 
--print-errors \"libtorrent >= 0.16.9\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libtorrent >= 0.16.9") 2>&5
   ac_status=$?
   printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
   test $ac_status = 0; }; then
-  pkg_cv_DEPENDENCIES_LIBS=`$PKG_CONFIG --libs "libtorrent >= 0.16.8" 
2>/dev/null`
+  pkg_cv_DEPENDENCIES_LIBS=`$PKG_CONFIG --libs "libtorrent >= 0.16.9" 
2>/dev/null`
                      test "x$?" != "x0" && pkg_failed=yes
 else
   pkg_failed=yes
@@ -21603,14 +21614,14 @@
         _pkg_short_errors_supported=no
 fi
         if test $_pkg_short_errors_supported = yes; then
-                DEPENDENCIES_PKG_ERRORS=`$PKG_CONFIG --short-errors 
--print-errors --cflags --libs "libtorrent >= 0.16.8" 2>&1`
+                DEPENDENCIES_PKG_ERRORS=`$PKG_CONFIG --short-errors 
--print-errors --cflags --libs "libtorrent >= 0.16.9" 2>&1`
         else
-                DEPENDENCIES_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags 
--libs "libtorrent >= 0.16.8" 2>&1`
+                DEPENDENCIES_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags 
--libs "libtorrent >= 0.16.9" 2>&1`
         fi
         # Put the nasty error message in config.log where it belongs
         echo "$DEPENDENCIES_PKG_ERRORS" >&5
 
-        as_fn_error $? "Package requirements (libtorrent >= 0.16.8) were not 
met:
+        as_fn_error $? "Package requirements (libtorrent >= 0.16.9) were not 
met:
 
 $DEPENDENCIES_PKG_ERRORS
 
@@ -22610,6 +22621,95 @@
 
 
 
+
+# Check whether --with-systemd was given.
+if test ${with_systemd+y}
+then :
+  withval=$with_systemd;
+      if test "$withval" = "yes"; then
+
+pkg_failed=no
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libsystemd" >&5
+printf %s "checking for libsystemd... " >&6; }
+
+if test -n "$SYSTEMD_CFLAGS"; then
+    pkg_cv_SYSTEMD_CFLAGS="$SYSTEMD_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists 
--print-errors \"libsystemd\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libsystemd") 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_SYSTEMD_CFLAGS=`$PKG_CONFIG --cflags "libsystemd" 2>/dev/null`
+                     test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$SYSTEMD_LIBS"; then
+    pkg_cv_SYSTEMD_LIBS="$SYSTEMD_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists 
--print-errors \"libsystemd\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libsystemd") 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_SYSTEMD_LIBS=`$PKG_CONFIG --libs "libsystemd" 2>/dev/null`
+                     test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+                SYSTEMD_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors 
--cflags --libs "libsystemd" 2>&1`
+        else
+                SYSTEMD_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs 
"libsystemd" 2>&1`
+        fi
+        # Put the nasty error message in config.log where it belongs
+        echo "$SYSTEMD_PKG_ERRORS" >&5
+
+        as_fn_error $? "libsystemd not found. Install libsystemd-dev (or the 
equivalent for your distribution)." "$LINENO" 5
+elif test $pkg_failed = untried; then
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+        as_fn_error $? "libsystemd not found. Install libsystemd-dev (or the 
equivalent for your distribution)." "$LINENO" 5
+else
+        SYSTEMD_CFLAGS=$pkg_cv_SYSTEMD_CFLAGS
+        SYSTEMD_LIBS=$pkg_cv_SYSTEMD_LIBS
+        { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }
+
+            CXXFLAGS="$CXXFLAGS $SYSTEMD_CFLAGS"
+            LIBS="$LIBS $SYSTEMD_LIBS"
+
+printf "%s\n" "#define HAVE_SYSTEMD 1" >>confdefs.h
+
+
+fi
+      fi
+
+fi
+
+
+
 if test ${with_xmlrpc_c+y} && test ${with_xmlrpc_tinyxml2+y}; then
   as_fn_error $? "--with-xmlrpc-c and --with-xmlrpc-tinyxml2 cannot be used 
together. Please choose only one" "$LINENO" 5
 fi
@@ -23202,7 +23302,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by rtorrent $as_me 0.16.8, which was
+This file was extended by rtorrent $as_me 0.16.9, which was
 generated by GNU Autoconf 2.72.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -23270,7 +23370,7 @@
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config='$ac_cs_config_escaped'
 ac_cs_version="\\
-rtorrent config.status 0.16.8
+rtorrent config.status 0.16.9
 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/rtorrent-0.16.8/configure.ac 
new/rtorrent-0.16.9/configure.ac
--- old/rtorrent-0.16.8/configure.ac    2026-03-15 17:58:09.000000000 +0100
+++ new/rtorrent-0.16.9/configure.ac    2026-04-06 16:00:46.000000000 +0200
@@ -1,6 +1,6 @@
 m4_pattern_allow([PKG_CHECK_EXISTS])
 
-AC_INIT([rtorrent],[0.16.8],[[email protected]])
+AC_INIT([rtorrent],[0.16.9],[[email protected]])
 
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_MACRO_DIRS([scripts])
@@ -48,7 +48,7 @@
 fi
 
 PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
-PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.8])
+PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.9])
 
 AC_LANG_PUSH(C++)
 TORRENT_WITH_XMLRPC_C
@@ -56,6 +56,7 @@
 
 TORRENT_WITH_LUA
 TORRENT_WITH_TINYXML2
+TORRENT_WITH_SYSTEMD
 
 if test ${with_xmlrpc_c+y} && test ${with_xmlrpc_tinyxml2+y}; then
   AC_MSG_ERROR([--with-xmlrpc-c and --with-xmlrpc-tinyxml2 cannot be used 
together. Please choose only one])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/doc/Makefile.in 
new/rtorrent-0.16.9/doc/Makefile.in
--- old/rtorrent-0.16.8/doc/Makefile.in 2026-03-15 18:07:04.000000000 +0100
+++ new/rtorrent-0.16.9/doc/Makefile.in 2026-04-06 16:10:12.000000000 +0200
@@ -227,6 +227,8 @@
 SET_MAKE = @SET_MAKE@
 SHELL = @SHELL@
 STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
 VERSION = @VERSION@
 abs_builddir = @abs_builddir@
 abs_srcdir = @abs_srcdir@
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/scripts/checks.m4 
new/rtorrent-0.16.9/scripts/checks.m4
--- old/rtorrent-0.16.8/scripts/checks.m4       2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/scripts/checks.m4       2026-04-06 16:00:46.000000000 
+0200
@@ -358,3 +358,20 @@
     ]
   )
 ])
+
+
+AC_DEFUN([TORRENT_WITH_SYSTEMD], [
+  AC_ARG_WITH(systemd,
+    AS_HELP_STRING([--with-systemd],[enable systemd socket activation support 
[[default=no]]]),
+    [
+      if test "$withval" = "yes"; then
+        PKG_CHECK_MODULES([SYSTEMD], [libsystemd],
+          [
+            CXXFLAGS="$CXXFLAGS $SYSTEMD_CFLAGS"
+            LIBS="$LIBS $SYSTEMD_LIBS"
+            AC_DEFINE(HAVE_SYSTEMD, 1, [Support for systemd socket 
activation.])
+          ],
+          [AC_MSG_ERROR([libsystemd not found. Install libsystemd-dev (or the 
equivalent for your distribution).])])
+      fi
+    ])
+])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/Makefile.in 
new/rtorrent-0.16.9/src/Makefile.in
--- old/rtorrent-0.16.8/src/Makefile.in 2026-03-15 18:07:04.000000000 +0100
+++ new/rtorrent-0.16.9/src/Makefile.in 2026-04-06 16:10:12.000000000 +0200
@@ -430,6 +430,8 @@
 SET_MAKE = @SET_MAKE@
 SHELL = @SHELL@
 STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
 VERSION = @VERSION@
 abs_builddir = @abs_builddir@
 abs_srcdir = @abs_srcdir@
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_download.cc 
new/rtorrent-0.16.9/src/command_download.cc
--- old/rtorrent-0.16.8/src/command_download.cc 2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_download.cc 2026-04-06 16:00:46.000000000 
+0200
@@ -884,4 +884,91 @@
   CMD2_DL_LIST    ("t.multicall", std::bind(&t_multicall, 
std::placeholders::_1, std::placeholders::_2));
 
   CMD2_ANY_LIST   ("p.call_target", std::bind(&p_call_target, 
std::placeholders::_2));
+
+  rpc::rpc.mark_safe("add_peer");
+  rpc::rpc.mark_safe("d.hash");
+  rpc::rpc.mark_safe("d.local_id");
+  rpc::rpc.mark_safe("d.local_id_html");
+  rpc::rpc.mark_safe("d.bitfield");
+  rpc::rpc.mark_safe("d.base_path");
+  rpc::rpc.mark_safe("d.base_filename");
+  rpc::rpc.mark_safe("d.name");
+  rpc::rpc.mark_safe("d.directory");
+  rpc::rpc.mark_safe("d.directory_base");
+  rpc::rpc.mark_safe("d.creation_date");
+  rpc::rpc.mark_safe("d.load_date");
+  rpc::rpc.mark_safe("d.up.rate");
+  rpc::rpc.mark_safe("d.up.total");
+  rpc::rpc.mark_safe("d.down.rate");
+  rpc::rpc.mark_safe("d.down.total");
+  rpc::rpc.mark_safe("d.skip.rate");
+  rpc::rpc.mark_safe("d.skip.total");
+  rpc::rpc.mark_safe("d.is_open");
+  rpc::rpc.mark_safe("d.is_active");
+  rpc::rpc.mark_safe("d.is_hash_checked");
+  rpc::rpc.mark_safe("d.is_hash_checking");
+  rpc::rpc.mark_safe("d.is_multi_file");
+  rpc::rpc.mark_safe("d.is_private");
+  rpc::rpc.mark_safe("d.is_pex_active");
+  rpc::rpc.mark_safe("d.is_partially_done");
+  rpc::rpc.mark_safe("d.is_not_partially_done");
+  rpc::rpc.mark_safe("d.is_meta");
+  rpc::rpc.mark_safe("d.peer_exchange");
+  rpc::rpc.mark_safe("d.resume");
+  rpc::rpc.mark_safe("d.pause");
+  rpc::rpc.mark_safe("d.open");
+  rpc::rpc.mark_safe("d.close");
+  rpc::rpc.mark_safe("d.close.directly");
+  rpc::rpc.mark_safe("d.erase");
+  rpc::rpc.mark_safe("d.check_hash");
+  rpc::rpc.mark_safe("d.save_resume");
+  rpc::rpc.mark_safe("d.save_full_session");
+  rpc::rpc.mark_safe("d.update_priorities");
+  rpc::rpc.mark_safe("d.custom");
+  rpc::rpc.mark_safe("d.custom1");
+  rpc::rpc.mark_safe("d.custom2");
+  rpc::rpc.mark_safe("d.custom3");
+  rpc::rpc.mark_safe("d.custom4");
+  rpc::rpc.mark_safe("d.custom5");
+  rpc::rpc.mark_safe("d.size_bytes");
+  rpc::rpc.mark_safe("d.size_chunks");
+  rpc::rpc.mark_safe("d.size_pex");
+  rpc::rpc.mark_safe("d.completed_bytes");
+  rpc::rpc.mark_safe("d.bytes_done");
+  rpc::rpc.mark_safe("d.peers_accounted");
+  rpc::rpc.mark_safe("d.chunks_hashed");
+  rpc::rpc.mark_safe("d.tracker_size");
+  rpc::rpc.mark_safe("d.completed_chunks");
+  rpc::rpc.mark_safe("d.left_bytes");
+  rpc::rpc.mark_safe("d.chunk_size");
+  rpc::rpc.mark_safe("d.priority");
+  rpc::rpc.mark_safe("d.priority_str");
+  rpc::rpc.mark_safe("d.state");
+  rpc::rpc.mark_safe("d.state_changed");
+  rpc::rpc.mark_safe("d.state_counter");
+  rpc::rpc.mark_safe("d.connection_current");
+  rpc::rpc.mark_safe("d.connection_leech");
+  rpc::rpc.mark_safe("d.connection_seed");
+  rpc::rpc.mark_safe("d.throttle_name");
+  rpc::rpc.mark_safe("d.uploads_max");
+  rpc::rpc.mark_safe("d.downloads_max");
+  rpc::rpc.mark_safe("d.peers_min");
+  rpc::rpc.mark_safe("d.peers_max");
+  rpc::rpc.mark_safe("d.peers_connected");
+  rpc::rpc.mark_safe("d.peers_not_connected");
+  rpc::rpc.mark_safe("d.peers_complete");
+  rpc::rpc.mark_safe("d.tracker_numwant");
+  rpc::rpc.mark_safe("d.tracker_focus");
+  rpc::rpc.mark_safe("d.message");
+  rpc::rpc.mark_safe("d.hashing");
+  rpc::rpc.mark_safe("d.hashing_failed");
+  rpc::rpc.mark_safe("d.free_diskspace");
+  rpc::rpc.mark_safe("d.views");
+  rpc::rpc.mark_safe("d.views.remove");
+  rpc::rpc.mark_safe("d.views.push_back_unique");
+  rpc::rpc.mark_safe("d.ratio");
+  rpc::rpc.mark_safe("f.multicall");
+  rpc::rpc.mark_safe("p.multicall");
+  rpc::rpc.mark_safe("p.call_target");
+  rpc::rpc.mark_safe("t.multicall");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_dynamic.cc 
new/rtorrent-0.16.9/src/command_dynamic.cc
--- old/rtorrent-0.16.8/src/command_dynamic.cc  2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_dynamic.cc  2026-04-06 16:00:46.000000000 
+0200
@@ -455,4 +455,27 @@
   CMD2_ANY         ("strings.tracker_event",             
std::bind(&torrent::option_list_strings, torrent::OPTION_TRACKER_EVENT));
   CMD2_ANY         ("strings.tracker_mode",              
std::bind(&torrent::option_list_strings, torrent::OPTION_TRACKER_MODE));
   // clang-format on
+
+#ifdef HAVE_XMLRPC_TINYXML2
+  rpc::rpc.mark_safe("system.listMethods");
+#endif
+
+  rpc::rpc.mark_safe("method.use_deprecated");
+  rpc::rpc.mark_safe("method.const");
+  rpc::rpc.mark_safe("method.has_key");
+  rpc::rpc.mark_safe("method.list_keys");
+  rpc::rpc.mark_safe("method.get");
+  rpc::rpc.mark_safe("method.rlookup");
+  rpc::rpc.mark_safe("catch");
+
+  rpc::rpc.mark_safe("strings.choke_heuristics");
+  rpc::rpc.mark_safe("strings.choke_heuristics.upload");
+  rpc::rpc.mark_safe("strings.choke_heuristics.download");
+  rpc::rpc.mark_safe("strings.connection_type");
+  rpc::rpc.mark_safe("strings.encryption");
+  rpc::rpc.mark_safe("strings.ip_filter");
+  rpc::rpc.mark_safe("strings.ip_tos");
+  rpc::rpc.mark_safe("strings.log_group");
+  rpc::rpc.mark_safe("strings.tracker_event");
+  rpc::rpc.mark_safe("strings.tracker_mode");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_events.cc 
new/rtorrent-0.16.9/src/command_events.cc
--- old/rtorrent-0.16.8/src/command_events.cc   2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_events.cc   2026-04-06 16:00:46.000000000 
+0200
@@ -344,4 +344,15 @@
   CMD2_ANY_LIST    ("d.multicall.filtered", std::bind(&d_multicall_filtered, 
std::placeholders::_2));
 
   CMD2_ANY_LIST    ("directory.watch.added", std::bind(&directory_watch_added, 
std::placeholders::_2));
+
+  rpc::rpc.mark_safe("start_tied");
+  rpc::rpc.mark_safe("stop_untied");
+  rpc::rpc.mark_safe("close_untied");
+  rpc::rpc.mark_safe("remove_untied");
+
+  rpc::rpc.mark_safe("close_low_diskspace");
+  rpc::rpc.mark_safe("close_low_diskspace.normal");
+  rpc::rpc.mark_safe("download_list");
+  rpc::rpc.mark_safe("d.multicall2");
+  rpc::rpc.mark_safe("d.multicall.filtered");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_file.cc 
new/rtorrent-0.16.9/src/command_file.cc
--- old/rtorrent-0.16.8/src/command_file.cc     2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_file.cc     2026-04-06 16:00:46.000000000 
+0200
@@ -103,4 +103,32 @@
 
   CMD2_FILEITR("fi.filename_last",      std::bind(&apply_fi_filename_last, 
std::placeholders::_1));
   CMD2_FILEITR("fi.is_file",            
std::bind(&torrent::FileListIterator::is_file, std::placeholders::_1));
+
+  rpc::rpc.mark_safe("f.path");
+  rpc::rpc.mark_safe("f.path_components");
+  rpc::rpc.mark_safe("f.path_depth");
+  rpc::rpc.mark_safe("f.frozen_path");
+  rpc::rpc.mark_safe("f.offset");
+  rpc::rpc.mark_safe("f.size_bytes");
+  rpc::rpc.mark_safe("f.size_chunks");
+  rpc::rpc.mark_safe("f.completed_chunks");
+  rpc::rpc.mark_safe("f.range_first");
+  rpc::rpc.mark_safe("f.range_second");
+  rpc::rpc.mark_safe("f.priority");
+  rpc::rpc.mark_safe("f.priority.set");
+  rpc::rpc.mark_safe("f.is_created");
+  rpc::rpc.mark_safe("f.is_open");
+  rpc::rpc.mark_safe("f.is_create_queued");
+  rpc::rpc.mark_safe("f.is_resize_queued");
+  rpc::rpc.mark_safe("f.prioritize_first");
+  rpc::rpc.mark_safe("f.prioritize_first.enable");
+  rpc::rpc.mark_safe("f.prioritize_first.disable");
+  rpc::rpc.mark_safe("f.prioritize_last");
+  rpc::rpc.mark_safe("f.prioritize_last.enable");
+  rpc::rpc.mark_safe("f.prioritize_last.disable");
+  rpc::rpc.mark_safe("f.last_touched");
+  rpc::rpc.mark_safe("f.match_depth_prev");
+  rpc::rpc.mark_safe("f.match_depth_next");
+  rpc::rpc.mark_safe("fi.filename_last");
+  rpc::rpc.mark_safe("fi.is_file");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_groups.cc 
new/rtorrent-0.16.9/src/command_groups.cc
--- old/rtorrent-0.16.8/src/command_groups.cc   2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_groups.cc   2026-04-06 16:00:46.000000000 
+0200
@@ -385,4 +385,24 @@
   CMD2_ANY         ("choke_group.down.heuristics",     
std::bind(&torrent::option_as_string, torrent::OPTION_CHOKE_HEURISTICS,
                                                                  
std::bind(&torrent::choke_queue::heuristics, 
CHOKE_GROUP(&torrent::choke_group::down_queue))));
   CMD2_ANY_LIST    ("choke_group.down.heuristics.set", 
std::bind(&apply_cg_heuristics_set, std::placeholders::_2, false));
+
+  rpc::rpc.mark_safe("choke_group.list");
+  rpc::rpc.mark_safe("choke_group.size");
+  rpc::rpc.mark_safe("choke_group.index_of");
+  rpc::rpc.mark_safe("choke_group.general.size");
+  rpc::rpc.mark_safe("choke_group.tracker.mode");
+  rpc::rpc.mark_safe("choke_group.up.rate");
+  rpc::rpc.mark_safe("choke_group.down.rate");
+  rpc::rpc.mark_safe("choke_group.up.max");
+  rpc::rpc.mark_safe("choke_group.up.max.unlimited");
+  rpc::rpc.mark_safe("choke_group.up.total");
+  rpc::rpc.mark_safe("choke_group.up.queued");
+  rpc::rpc.mark_safe("choke_group.up.unchoked");
+  rpc::rpc.mark_safe("choke_group.up.heuristics");
+  rpc::rpc.mark_safe("choke_group.down.max");
+  rpc::rpc.mark_safe("choke_group.down.max.unlimited");
+  rpc::rpc.mark_safe("choke_group.down.total");
+  rpc::rpc.mark_safe("choke_group.down.queued");
+  rpc::rpc.mark_safe("choke_group.down.unchoked");
+  rpc::rpc.mark_safe("choke_group.down.heuristics");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_local.cc 
new/rtorrent-0.16.9/src/command_local.cc
--- old/rtorrent-0.16.8/src/command_local.cc    2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_local.cc    2026-04-06 16:00:46.000000000 
+0200
@@ -120,6 +120,15 @@
   rpc::commands.call("method.insert", rpc::create_object_list("group." + name 
+ ".ratio.max", "value", (int64_t)300));
   rpc::commands.call("method.insert", rpc::create_object_list("group." + name 
+ ".ratio.upload", "value", (int64_t)20 << 20));
 
+  rpc::rpc.mark_safe("group." + name + ".view");
+  rpc::rpc.mark_safe("group." + name + ".view.set");
+  rpc::rpc.mark_safe("group." + name + ".ratio.min");
+  rpc::rpc.mark_safe("group." + name + ".ratio.min.set");
+  rpc::rpc.mark_safe("group." + name + ".ratio.max");
+  rpc::rpc.mark_safe("group." + name + ".ratio.max.set");
+  rpc::rpc.mark_safe("group." + name + ".ratio.upload");
+  rpc::rpc.mark_safe("group." + name + ".ratio.upload.set");
+
   if (rpc::call_command_value("method.use_intermediate") == 3) {
     // Cleaned up in 0.16.1:
 
@@ -173,9 +182,13 @@
   if (output == nullptr)
     throw torrent::input_error("Could not append to file '" + 
args.front().as_string() + "': " + std::strerror(errno));
 
-  file_print_list(++args.begin(), args.end(), output, file_print_delim_space);
-
-  fprintf(output, "\n");
+  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();
 }
@@ -319,4 +332,25 @@
   CMD2_ANY_P("argument.3", std::bind(&rpc::command_base::argument_ref, 3));
 
   CMD2_ANY_LIST  ("group.insert", std::bind(&group_insert, 
std::placeholders::_2));
+
+  rpc::rpc.mark_safe("system.api_version");
+  rpc::rpc.mark_safe("system.client_version");
+  rpc::rpc.mark_safe("system.library_version");
+  rpc::rpc.mark_safe("system.file.max_size");
+  rpc::rpc.mark_safe("system.file.split_size");
+  rpc::rpc.mark_safe("system.file.split_suffix");
+
+  rpc::rpc.mark_safe("directory.default");
+  rpc::rpc.mark_safe("session.path");
+  rpc::rpc.mark_safe("session.use_lock");
+  rpc::rpc.mark_safe("session.on_completion");
+
+  rpc::rpc.mark_safe("pieces.sync.always_safe");
+  rpc::rpc.mark_safe("pieces.sync.timeout");
+  rpc::rpc.mark_safe("pieces.sync.timeout_safe");
+  rpc::rpc.mark_safe("pieces.preload.type");
+  rpc::rpc.mark_safe("pieces.preload.min_size");
+  rpc::rpc.mark_safe("pieces.preload.min_rate");
+  rpc::rpc.mark_safe("pieces.memory.max");
+  rpc::rpc.mark_safe("pieces.hash.on_completion");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_network.cc 
new/rtorrent-0.16.9/src/command_network.cc
--- old/rtorrent-0.16.8/src/command_network.cc  2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_network.cc  2026-04-06 16:00:46.000000000 
+0200
@@ -3,6 +3,10 @@
 #include <functional>
 #include <cstdio>
 #include <unistd.h>
+#ifdef HAVE_SYSTEMD
+#include <sys/socket.h>
+#include <systemd/sd-daemon.h>
+#endif
 #include <rak/address_info.h>
 #include <torrent/torrent.h>
 #include <torrent/rate.h>
@@ -144,6 +148,60 @@
 }
 
 torrent::Object
+apply_scgi_systemd() {
+#ifdef HAVE_SYSTEMD
+  if (scgi_thread::scgi() != nullptr)
+    throw torrent::input_error("SCGI already enabled.");
+
+  int n = sd_listen_fds(0);
+  if (n < 1)
+    throw torrent::input_error("No systemd socket(s) provided (sd_listen_fds 
returned " +
+                               std::to_string(n) + ").");
+
+  // Iterate over all provided fds. Use the first listening stream socket;
+  // close the rest. The systemd docs say unused fds should be closed.
+  int selected_fd = -1;
+  for (int i = 0; i < n; i++) {
+    int fd = SD_LISTEN_FDS_START + i;
+
+    if (selected_fd != -1) {
+      ::close(fd);
+      continue;
+    }
+
+    auto err = sd_is_socket(fd, AF_UNSPEC, SOCK_STREAM, 1);
+
+    if (err < 0) {
+      // Safe to ignore errors here - we just skip it and move on.
+      ::close(fd);
+      continue;
+    }
+
+    if (err == 0) {
+      // Not the socket we're looking for.
+      ::close(fd);
+      continue;
+    }
+
+    selected_fd = fd;
+  }
+
+  if (selected_fd == -1)
+    throw torrent::input_error("No listening stream socket found among 
systemd-provided fds.");
+
+  initialize_rpc_handlers();
+
+  rpc::SCgi* scgi = new rpc::SCgi;
+  scgi->open_fd(selected_fd);
+
+  scgi_thread::set_scgi(scgi);
+  return torrent::Object();
+#else
+  throw torrent::input_error("Systemd SCGI endpoint is not supported.");
+#endif
+}
+
+torrent::Object
 apply_xmlrpc_dialect(const std::string& arg) {
   int value;
 
@@ -243,6 +301,7 @@
   CMD2_ANY_STRING  ("network.scgi.open_port",        std::bind(&apply_scgi, 
std::placeholders::_2, 1));
   CMD2_ANY_STRING  ("network.scgi.open_local",       std::bind(&apply_scgi, 
std::placeholders::_2, 2));
   CMD2_VAR_BOOL    ("network.scgi.dont_route",       false);
+  CMD2_ANY         ("network.scgi.open_systemd",     [](auto, auto) { return 
apply_scgi_systemd(); });
 
   CMD2_ANY_STRING  ("network.xmlrpc.dialect.set",    [](const auto&, const 
auto& arg) { return apply_xmlrpc_dialect(arg); })
   CMD2_ANY         ("network.xmlrpc.size_limit",     [](const auto&, const 
auto&)     { return rpc::rpc.size_limit(); });
@@ -261,4 +320,36 @@
   CMD2_ANY_VALUE_V ("network.block.outgoing.set",    [nw_config](auto, auto& 
value) { return nw_config->set_block_outgoing(value); });
   CMD2_ANY         ("network.prefer.ipv6",           [nw_config](auto, auto)   
     { return nw_config->is_prefer_ipv6(); });
   CMD2_ANY_VALUE_V ("network.prefer.ipv6.set",       [nw_config](auto, auto& 
value) { return nw_config->set_prefer_ipv6(value); });
+
+  rpc::rpc.mark_safe("network.port_open");
+  rpc::rpc.mark_safe("network.port_random");
+  rpc::rpc.mark_safe("network.port_range");
+  rpc::rpc.mark_safe("network.listen.port");
+  rpc::rpc.mark_safe("network.listen.backlog");
+
+  rpc::rpc.mark_safe("network.http.current_open");
+  rpc::rpc.mark_safe("network.http.max_cache_connections");
+  rpc::rpc.mark_safe("network.http.max_host_connections");
+  rpc::rpc.mark_safe("network.http.max_total_connections");
+
+  rpc::rpc.mark_safe("network.open_files");
+  rpc::rpc.mark_safe("network.max_open_files");
+  rpc::rpc.mark_safe("network.max_open_sockets");
+  rpc::rpc.mark_safe("network.total_handshakes");
+
+  rpc::rpc.mark_safe("network.send_buffer.size");
+  rpc::rpc.mark_safe("network.receive_buffer.size");
+  rpc::rpc.mark_safe("network.bind_address");
+  rpc::rpc.mark_safe("network.local_address");
+  rpc::rpc.mark_safe("network.xmlrpc.size_limit");
+  rpc::rpc.mark_safe("network.open_sockets");
+  rpc::rpc.mark_safe("network.http.cacert");
+  rpc::rpc.mark_safe("network.http.capath");
+  rpc::rpc.mark_safe("network.http.proxy_address");
+  rpc::rpc.mark_safe("network.proxy_address");
+  rpc::rpc.mark_safe("network.scgi.dont_route");
+  rpc::rpc.mark_safe("protocol.pex");
+
+  rpc::rpc.mark_safe("network.rpc.use_xmlrpc");
+  rpc::rpc.mark_safe("network.rpc.use_jsonrpc");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_peer.cc 
new/rtorrent-0.16.9/src/command_peer.cc
--- old/rtorrent-0.16.8/src/command_peer.cc     2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_peer.cc     2026-04-06 16:00:46.000000000 
+0200
@@ -60,6 +60,8 @@
 
 torrent::Object
 retrieve_p_completed_percent(torrent::Peer* peer) {
+  if (peer->bitfield()->size_bits() == 0)
+    return int64_t(0);
   return (100 * peer->bitfield()->size_set()) / peer->bitfield()->size_bits();
 }
 
@@ -98,4 +100,30 @@
 
   CMD2_PEER_V("p.disconnect",         std::bind(&torrent::Peer::disconnect, 
std::placeholders::_1, 0));
   CMD2_PEER_V("p.disconnect_delayed", std::bind(&torrent::Peer::disconnect, 
std::placeholders::_1, torrent::ConnectionList::disconnect_delayed));
+
+  rpc::rpc.mark_safe("p.address");
+  rpc::rpc.mark_safe("p.port");
+  rpc::rpc.mark_safe("p.client_version");
+  rpc::rpc.mark_safe("p.options_str");
+  rpc::rpc.mark_safe("p.id");
+  rpc::rpc.mark_safe("p.id_html");
+  rpc::rpc.mark_safe("p.up_rate");
+  rpc::rpc.mark_safe("p.up_total");
+  rpc::rpc.mark_safe("p.down_rate");
+  rpc::rpc.mark_safe("p.down_total");
+  rpc::rpc.mark_safe("p.peer_rate");
+  rpc::rpc.mark_safe("p.peer_total");
+  rpc::rpc.mark_safe("p.is_encrypted");
+  rpc::rpc.mark_safe("p.is_incoming");
+  rpc::rpc.mark_safe("p.is_obfuscated");
+  rpc::rpc.mark_safe("p.is_snubbed");
+  rpc::rpc.mark_safe("p.is_unwanted");
+  rpc::rpc.mark_safe("p.is_preferred");
+  rpc::rpc.mark_safe("p.snubbed");
+  rpc::rpc.mark_safe("p.snubbed.set");
+  rpc::rpc.mark_safe("p.banned");
+  rpc::rpc.mark_safe("p.banned.set");
+  rpc::rpc.mark_safe("p.completed_percent");
+  rpc::rpc.mark_safe("p.disconnect");
+  rpc::rpc.mark_safe("p.disconnect_delayed");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_throttle.cc 
new/rtorrent-0.16.9/src/command_throttle.cc
--- old/rtorrent-0.16.8/src/command_throttle.cc 2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_throttle.cc 2026-04-06 16:00:46.000000000 
+0200
@@ -199,4 +199,60 @@
   CMD2_ANY_STRING  ("throttle.up.rate",   std::bind(&retrieve_throttle_info, 
std::placeholders::_2, throttle_info_up | throttle_info_rate));
   CMD2_ANY_STRING  ("throttle.down.max",  std::bind(&retrieve_throttle_info, 
std::placeholders::_2, throttle_info_down | throttle_info_max));
   CMD2_ANY_STRING  ("throttle.down.rate", std::bind(&retrieve_throttle_info, 
std::placeholders::_2, throttle_info_down | throttle_info_rate));
+
+  rpc::rpc.mark_safe("throttle.unchoked_uploads");
+  rpc::rpc.mark_safe("throttle.max_unchoked_uploads");
+  rpc::rpc.mark_safe("throttle.unchoked_downloads");
+  rpc::rpc.mark_safe("throttle.max_unchoked_downloads");
+
+  rpc::rpc.mark_safe("throttle.min_peers.normal");
+  rpc::rpc.mark_safe("throttle.min_peers.normal.set");
+  rpc::rpc.mark_safe("throttle.max_peers.normal");
+  rpc::rpc.mark_safe("throttle.max_peers.normal.set");
+  rpc::rpc.mark_safe("throttle.min_peers.seed");
+  rpc::rpc.mark_safe("throttle.min_peers.seed.set");
+  rpc::rpc.mark_safe("throttle.max_peers.seed");
+  rpc::rpc.mark_safe("throttle.max_peers.seed.set");
+
+  rpc::rpc.mark_safe("throttle.min_uploads");
+  rpc::rpc.mark_safe("throttle.min_uploads.set");
+  rpc::rpc.mark_safe("throttle.max_uploads");
+  rpc::rpc.mark_safe("throttle.max_uploads.set");
+  rpc::rpc.mark_safe("throttle.min_downloads");
+  rpc::rpc.mark_safe("throttle.min_downloads.set");
+  rpc::rpc.mark_safe("throttle.max_downloads");
+  rpc::rpc.mark_safe("throttle.max_downloads.set");
+
+  rpc::rpc.mark_safe("throttle.max_uploads.div");
+  rpc::rpc.mark_safe("throttle.max_uploads.div.set");
+  rpc::rpc.mark_safe("throttle.max_uploads.div._val");
+  rpc::rpc.mark_safe("throttle.max_uploads.div._val.set");
+  rpc::rpc.mark_safe("throttle.max_uploads.global");
+  rpc::rpc.mark_safe("throttle.max_uploads.global.set");
+  rpc::rpc.mark_safe("throttle.max_uploads.global._val");
+  rpc::rpc.mark_safe("throttle.max_uploads.global._val.set");
+  rpc::rpc.mark_safe("throttle.max_downloads.div");
+  rpc::rpc.mark_safe("throttle.max_downloads.div.set");
+  rpc::rpc.mark_safe("throttle.max_downloads.div._val");
+  rpc::rpc.mark_safe("throttle.max_downloads.div._val.set");
+  rpc::rpc.mark_safe("throttle.max_downloads.global");
+  rpc::rpc.mark_safe("throttle.max_downloads.global.set");
+  rpc::rpc.mark_safe("throttle.max_downloads.global._val");
+  rpc::rpc.mark_safe("throttle.max_downloads.global._val.set");
+
+  rpc::rpc.mark_safe("throttle.global_up.rate");
+  rpc::rpc.mark_safe("throttle.global_up.total");
+  rpc::rpc.mark_safe("throttle.global_up.max_rate");
+  rpc::rpc.mark_safe("throttle.global_up.max_rate.set");
+  rpc::rpc.mark_safe("throttle.global_up.max_rate.set_kb");
+  rpc::rpc.mark_safe("throttle.global_down.rate");
+  rpc::rpc.mark_safe("throttle.global_down.total");
+  rpc::rpc.mark_safe("throttle.global_down.max_rate");
+  rpc::rpc.mark_safe("throttle.global_down.max_rate.set");
+  rpc::rpc.mark_safe("throttle.global_down.max_rate.set_kb");
+
+  rpc::rpc.mark_safe("throttle.up.max");
+  rpc::rpc.mark_safe("throttle.up.rate");
+  rpc::rpc.mark_safe("throttle.down.max");
+  rpc::rpc.mark_safe("throttle.down.rate");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_tracker.cc 
new/rtorrent-0.16.9/src/command_tracker.cc
--- old/rtorrent-0.16.8/src/command_tracker.cc  2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_tracker.cc  2026-04-06 16:00:46.000000000 
+0200
@@ -151,4 +151,35 @@
 
   CMD2_ANY_STRING     ("dht.add_node",          std::bind(&apply_dht_add_node, 
std::placeholders::_2));
   CMD2_ANY            ("dht.statistics",        
std::bind(&core::DhtManager::dht_statistics, dht_manager));
+
+  rpc::rpc.mark_safe("t.url");
+  rpc::rpc.mark_safe("t.group");
+  rpc::rpc.mark_safe("t.id");
+  rpc::rpc.mark_safe("t.type");
+  rpc::rpc.mark_safe("t.is_usable");
+  rpc::rpc.mark_safe("t.is_busy");
+  rpc::rpc.mark_safe("t.is_enabled");
+  rpc::rpc.mark_safe("t.is_enabled.set");
+  rpc::rpc.mark_safe("t.is_extra_tracker");
+  rpc::rpc.mark_safe("t.is_open");
+  rpc::rpc.mark_safe("t.normal_interval");
+  rpc::rpc.mark_safe("t.scrape_time_last");
+  rpc::rpc.mark_safe("t.scrape_counter");
+  rpc::rpc.mark_safe("t.success_time_last");
+  rpc::rpc.mark_safe("t.success_counter");
+  rpc::rpc.mark_safe("t.failed_time_last");
+  rpc::rpc.mark_safe("t.failed_counter");
+  rpc::rpc.mark_safe("t.activity_time_last");
+  rpc::rpc.mark_safe("t.activity_time_next");
+  rpc::rpc.mark_safe("t.scrape_complete");
+  rpc::rpc.mark_safe("t.scrape_incomplete");
+  rpc::rpc.mark_safe("t.scrape_downloaded");
+
+  rpc::rpc.mark_safe("dht.mode.set");
+  rpc::rpc.mark_safe("dht.port");
+  rpc::rpc.mark_safe("dht.override_port");
+  rpc::rpc.mark_safe("dht.add_node");
+  rpc::rpc.mark_safe("dht.statistics");
+  rpc::rpc.mark_safe("trackers.numwant");
+  rpc::rpc.mark_safe("trackers.use_udp");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/command_ui.cc 
new/rtorrent-0.16.9/src/command_ui.cc
--- old/rtorrent-0.16.8/src/command_ui.cc       2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/command_ui.cc       2026-04-06 16:00:46.000000000 
+0200
@@ -888,4 +888,28 @@
       return control->object_storage()->get_str(display::color_vars[color_id]);
     });
   }
+
+  rpc::rpc.mark_safe("view.set_visible");
+  rpc::rpc.mark_safe("view.set_not_visible");
+
+  rpc::rpc.mark_safe("cat");
+  rpc::rpc.mark_safe("if");
+  rpc::rpc.mark_safe("branch");
+  rpc::rpc.mark_safe("and");
+  rpc::rpc.mark_safe("or");
+  rpc::rpc.mark_safe("not");
+  rpc::rpc.mark_safe("value");
+  rpc::rpc.mark_safe("compare");
+  rpc::rpc.mark_safe("elapsed.less");
+  rpc::rpc.mark_safe("elapsed.greater");
+
+  rpc::rpc.mark_safe("convert.gm_time");
+  rpc::rpc.mark_safe("convert.gm_date");
+  rpc::rpc.mark_safe("convert.time");
+  rpc::rpc.mark_safe("convert.date");
+  rpc::rpc.mark_safe("convert.elapsed_time");
+  rpc::rpc.mark_safe("convert.kb");
+  rpc::rpc.mark_safe("convert.mb");
+  rpc::rpc.mark_safe("convert.xb");
+  rpc::rpc.mark_safe("convert.throttle");
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/core/download.cc 
new/rtorrent-0.16.9/src/core/download.cc
--- old/rtorrent-0.16.8/src/core/download.cc    2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/core/download.cc    2026-04-06 16:00:46.000000000 
+0200
@@ -137,7 +137,7 @@
     rpc::call_command("d.state.set", (int64_t)0, rpc::make_target(this));
     control->core()->download_list()->close_directly(this);
 
-    throw torrent::input_error("Cannot change the directory of an open 
download atter the files have been moved.");
+    throw torrent::input_error("Cannot change the directory of an open 
download after the files have been moved.");
   }
 
   control->core()->download_list()->close_directly(this);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/display/frame.cc 
new/rtorrent-0.16.9/src/display/frame.cc
--- old/rtorrent-0.16.8/src/display/frame.cc    2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/display/frame.cc    2026-04-06 16:00:46.000000000 
+0200
@@ -361,7 +361,7 @@
     (*itr)->balance(x, y, m_width, std::min((*itr)->m_height, height));
 
     y += (*itr)->m_height;
-    height -= (*itr)->m_height;
+    height -= std::min(height, (*itr)->m_height);
   }
 }
 
@@ -436,7 +436,7 @@
     (*itr)->balance(x, y, std::min((*itr)->m_width, width), m_height);
 
     x += (*itr)->m_width;
-    width -= (*itr)->m_width;
+    width -= std::min(width, (*itr)->m_width);
   }
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/display/utils.cc 
new/rtorrent-0.16.9/src/display/utils.cc
--- old/rtorrent-0.16.8/src/display/utils.cc    2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/display/utils.cc    2026-04-06 16:00:46.000000000 
+0200
@@ -150,7 +150,7 @@
 
   if (d->is_hash_checking()) {
     first = print_buffer(first, last, "Checking hash [%2i%%]",
-                         (d->download()->chunks_hashed() * 100) / 
d->download()->file_list()->size_chunks());
+                         d->download()->file_list()->size_chunks() != 0 ? 
(d->download()->chunks_hashed() * 100) / 
d->download()->file_list()->size_chunks() : 0);
 
   } else if (d->tracker_controller().has_active_trackers_not_scrape()) {
     auto tracker = d->tracker_controller().find_if([](const auto& t) {
@@ -208,7 +208,7 @@
   if (d->is_done())
     first = print_buffer(first, last, " 100%% ");
   else if (d->is_open())
-    first = print_buffer(first, last, "  %2u%% 
",(d->download()->file_list()->completed_chunks() * 100) / 
d->download()->file_list()->size_chunks());
+    first = print_buffer(first, last, "  %2u%% ", 
d->download()->file_list()->size_chunks() != 0 ? 
(d->download()->file_list()->completed_chunks() * 100) / 
d->download()->file_list()->size_chunks() : 0);
   else
     first = print_buffer(first, last, "      ");
 
@@ -260,7 +260,7 @@
     //return print_buffer(first, last, "[--%%]");
     return print_buffer(first, last, "     ");
   else
-    return print_buffer(first, last, "[%2u%%]", 
(d->download()->file_list()->completed_chunks() * 100) / 
d->download()->file_list()->size_chunks());
+    return print_buffer(first, last, "[%2u%%]", 
d->download()->file_list()->size_chunks() != 0 ? 
(d->download()->file_list()->completed_chunks() * 100) / 
d->download()->file_list()->size_chunks() : 0);
 }
 
 char*
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/display/window_http_queue.cc 
new/rtorrent-0.16.9/src/display/window_http_queue.cc
--- old/rtorrent-0.16.8/src/display/window_http_queue.cc        2026-03-15 
17:58:09.000000000 +0100
+++ new/rtorrent-0.16.9/src/display/window_http_queue.cc        2026-04-06 
16:00:46.000000000 +0200
@@ -19,7 +19,7 @@
   set_active(false);
 
   m_conn_insert = 
m_queue->signal_insert().insert(m_queue->signal_insert().end(), [this](auto h) 
{ receive_insert(h); });
-  m_conn_erase  = 
m_queue->signal_erase().insert(m_queue->signal_insert().end(), [this](auto h) { 
receive_erase(h); });
+  m_conn_erase  = 
m_queue->signal_erase().insert(m_queue->signal_erase().end(), [this](auto h) { 
receive_erase(h); });
 
   m_task_deactivate.slot() = [this] {
       if (!m_container.empty())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/display/window_input.cc 
new/rtorrent-0.16.9/src/display/window_input.cc
--- old/rtorrent-0.16.8/src/display/window_input.cc     2026-03-15 
17:58:09.000000000 +0100
+++ new/rtorrent-0.16.9/src/display/window_input.cc     2026-04-06 
16:00:46.000000000 +0200
@@ -12,7 +12,7 @@
   m_canvas->erase();
   m_canvas->print(0, 0, "%s> %s", m_title.c_str(), m_input != NULL ? 
m_input->c_str() : "<NULL>");
 
-  if (m_focus)
+  if (m_focus && m_input != NULL)
     m_canvas->set_attr(m_input->get_pos() + 2 + m_title.size(), 0, 1, 
A_REVERSE, COLOR_PAIR(0));
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/main.cc 
new/rtorrent-0.16.9/src/main.cc
--- old/rtorrent-0.16.8/src/main.cc     2026-03-15 17:58:09.000000000 +0100
+++ new/rtorrent-0.16.9/src/main.cc     2026-04-06 16:00:46.000000000 +0200
@@ -419,6 +419,7 @@
     // Deprecate:
 
     CMD2_VAR_STRING("dht.throttle.name",   "deprecated");
+    rpc::rpc.mark_safe("dht.throttle.name");
 
     CMD2_REDIRECT("network.http.max_open",     
"network.http.max_total_connections");
     CMD2_REDIRECT("network.http.max_open.set", 
"network.http.max_total_connections.set");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/option_parser.cc 
new/rtorrent-0.16.9/src/option_parser.cc
--- old/rtorrent-0.16.8/src/option_parser.cc    2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/option_parser.cc    2026-04-06 16:00:46.000000000 
+0200
@@ -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 <[email protected]>
-
-
 #include "config.h"
 
 #include <algorithm>
@@ -137,8 +103,11 @@
 OptionParser::call_int_pair(slot_int_pair slot, const std::string& arg) {
   int a, b;
 
-  if (std::sscanf(arg.c_str(), "%u-%u", &a, &b) != 2)
+  if (std::sscanf(arg.c_str(), "%d-%d", &a, &b) != 2)
     throw std::runtime_error("Invalid argument, \"" + arg + "\" should be 
\"a-b\"");
 
+  if (a < 0 || b < 0)
+    throw std::runtime_error("Invalid argument, \"" + arg + "\" should be 
positive numbers");
+  
   slot(a, b);
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/command_map.cc 
new/rtorrent-0.16.9/src/rpc/command_map.cc
--- old/rtorrent-0.16.8/src/rpc/command_map.cc  2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/rpc/command_map.cc  2026-04-06 16:00:46.000000000 
+0200
@@ -98,11 +98,17 @@
   if (itr == base_type::end())
     throw torrent::input_error("Command \"" + std::string(key) + "\" does not 
exist.");
 
+  if (!rpc.is_trusted() && !(itr->second.m_flags & flag_untrusted_safe))
+    throw untrusted_error("Command \"" + std::string(key) + "\" is not allowed 
for untrusted connections.");
+
   return itr->second.m_anySlot(&itr->second.m_variable, target, arg);
 }
 
 const CommandMap::mapped_type
 CommandMap::call_command(iterator itr, const mapped_type& arg, const 
target_type& target) {
+  if (!rpc.is_trusted() && !(itr->second.m_flags & flag_untrusted_safe))
+    throw untrusted_error("Command \"" + itr->first + "\" is not allowed for 
untrusted connections.");
+
   return itr->second.m_anySlot(&itr->second.m_variable, target, arg);
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/command_map.h 
new/rtorrent-0.16.9/src/rpc/command_map.h
--- old/rtorrent-0.16.8/src/rpc/command_map.h   2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/rpc/command_map.h   2026-04-06 16:00:46.000000000 
+0200
@@ -56,6 +56,8 @@
   static const int flag_file_target    = 0x100;
   static const int flag_tracker_target = 0x200;
 
+  static const int flag_untrusted_safe = 0x400;
+
   CommandMap() = default;
 
   bool                has(const std::string& key) const { return 
base_type::find(key) != base_type::end(); }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/exec_file.cc 
new/rtorrent-0.16.9/src/rpc/exec_file.cc
--- old/rtorrent-0.16.8/src/rpc/exec_file.cc    2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/rpc/exec_file.cc    2026-04-06 16:00:46.000000000 
+0200
@@ -41,8 +41,13 @@
 
   pid_t childPid = fork();
 
-  if (childPid == -1)
+  if (childPid == -1) {
+    if (flags & flag_capture) {
+      ::close(pipeFd[0]);
+      ::close(pipeFd[1]);
+    }
     throw torrent::input_error("ExecFile::execute(...) Fork failed.");
+  }
 
   if (childPid == 0) {
     if (flags & flag_background) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/jsonrpc.cc 
new/rtorrent-0.16.9/src/rpc/jsonrpc.cc
--- old/rtorrent-0.16.8/src/rpc/jsonrpc.cc      2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/rpc/jsonrpc.cc      2026-04-06 16:00:46.000000000 
+0200
@@ -133,9 +133,12 @@
 
   params_object_list.erase(params_object_list.begin());
 
-  const auto& result = rpc::commands.call_command(itr, params_object, target);
-
-  return object_to_json(result);
+  try {
+    const auto& result = rpc::commands.call_command(itr, params_object, 
target);
+    return object_to_json(result);
+  } catch (untrusted_error& e) {
+    throw rpc_error(JSONRPC_METHOD_NOT_FOUND_ERROR, e.what());
+  }
 }
 
 json
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/parse.cc 
new/rtorrent-0.16.9/src/rpc/parse.cc
--- old/rtorrent-0.16.8/src/rpc/parse.cc        2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/rpc/parse.cc        2026-04-06 16:00:46.000000000 
+0200
@@ -120,11 +120,17 @@
   case 'b':
   case 'B': ++last; break;
   case 'k':
-  case 'K': *value = *value << 10; ++last; break;
+  case 'K':
+    if (*value > (int64_t)0x1FFFFFFFFFFFFF) return src; // overflow guard
+    *value = *value << 10; ++last; break;
   case 'm':
-  case 'M': *value = *value << 20; ++last; break;
+  case 'M':
+    if (*value > (int64_t)0x7FFFFFFFFFF) return src; // overflow guard
+    *value = *value << 20; ++last; break;
   case 'g':
-  case 'G': *value = *value << 30; ++last; break;
+  case 'G':
+    if (*value > (int64_t)0x1FFFFFFFF) return src; // overflow guard
+    *value = *value << 30; ++last; break;
 //   case ' ':
 //   case '\0': *value = *value * unit; break;
 //   default: throw torrent::input_error("Could not parse value.");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/rpc_manager.cc 
new/rtorrent-0.16.9/src/rpc/rpc_manager.cc
--- old/rtorrent-0.16.8/src/rpc/rpc_manager.cc  2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/rpc/rpc_manager.cc  2026-04-06 16:00:46.000000000 
+0200
@@ -13,6 +13,19 @@
 RpcManager rpc;
 ExecFile   execFile;
 
+// Trusted/untrusted XMLRPC connection model.
+//
+// The trust state is set per-request by the SCGI layer based on the
+// UNTRUSTED_CONNECTION header. Commands without flag_untrusted_safe
+// are blocked for untrusted connections. The check is in
+// CommandMap::call_command(), which catches all command execution
+// including nested calls through argument expansion.
+
+bool
+RpcManager::is_trusted() const {
+  return m_trusted;
+}
+
 void
 RpcManager::object_to_target(const torrent::Object& obj, int call_flags, 
rpc::target_type* target, std::function<void()>* deleter) {
   if (!obj.is_string())
@@ -124,6 +137,21 @@
   }
 }
 
+bool
+RpcManager::process_untrusted(RPCType type, const char* in_buffer, uint32_t 
length, slot_response_callback callback) {
+  bool previous = m_trusted;
+  m_trusted = false;
+
+  try {
+    bool result = process(type, in_buffer, length, callback);
+    m_trusted = previous;
+    return result;
+  } catch (...) {
+    m_trusted = previous;
+    throw;
+  }
+}
+
 void
 RpcManager::initialize_handlers() {
   if (m_handlers_initialized)
@@ -175,4 +203,14 @@
   m_jsonrpc.insert_command(name, parm, doc);
 }
 
+void
+RpcManager::mark_safe(const std::string& key) {
+  auto itr = commands.find(key);
+
+  if (itr == commands.end())
+    return;
+
+  itr->second.m_flags |= CommandMap::flag_untrusted_safe;
+}
+
 } // namespace rpc
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/rpc_manager.h 
new/rtorrent-0.16.9/src/rpc/rpc_manager.h
--- old/rtorrent-0.16.8/src/rpc/rpc_manager.h   2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/rpc/rpc_manager.h   2026-04-06 16:00:46.000000000 
+0200
@@ -4,6 +4,7 @@
 #include <cstdint>
 #include <functional>
 #include <torrent/common.h>
+#include <torrent/exceptions.h>
 
 #include "rpc/command.h"
 #include "rpc/command_map.h"
@@ -34,6 +35,12 @@
   std::string m_msg;
 };
 
+class untrusted_error : public torrent::input_error {
+public:
+  using torrent::input_error::input_error;
+  virtual ~untrusted_error() throw() = default;
+};
+
 class RpcManager {
 public:
   using slot_download          = std::function<core::Download*(const char*)>;
@@ -63,17 +70,26 @@
   void           set_type_enabled(RPCType type, bool enabled);
 
   bool           process(RPCType type, const char* in_buffer, uint32_t length, 
slot_response_callback callback);
+  bool           process_untrusted(RPCType type, const char* in_buffer, 
uint32_t length, slot_response_callback callback);
 
   void           insert_command(const char* name, const char* parm, const 
char* doc);
+  void           mark_safe(const std::string& key);
 
   slot_download& slot_find_download() { return m_slot_find_download; }
   slot_file&     slot_find_file()     { return m_slot_find_file; }
   slot_tracker&  slot_find_tracker()  { return m_slot_find_tracker; }
   slot_peer&     slot_find_peer()     { return m_slot_find_peer; }
 
+  // Trusted/untrusted XMLRPC connection model.
+  // When an SCGI request includes the UNTRUSTED_CONNECTION header,
+  // commands without flag_untrusted_safe are blocked.
+  bool           is_trusted() const;
+
   static void    object_to_target(const torrent::Object& obj, int callFlags, 
rpc::target_type* target, std::function<void()>* deleter);
 
 private:
+  bool          m_trusted{true};
+
   XmlRpc        m_xmlrpc;
   JsonRpc       m_jsonrpc;
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/scgi.cc 
new/rtorrent-0.16.9/src/rpc/scgi.cc
--- old/rtorrent-0.16.8/src/rpc/scgi.cc 2026-03-15 17:58:09.000000000 +0100
+++ new/rtorrent-0.16.9/src/rpc/scgi.cc 2026-04-06 16:00:46.000000000 +0200
@@ -2,6 +2,7 @@
 
 #include <algorithm>
 #include <cassert>
+#include <fcntl.h>
 #include <unistd.h>
 #include <sys/un.h>
 #include <torrent/connection_manager.h>
@@ -81,6 +82,19 @@
 }
 
 void
+SCgi::open_fd(int fd) {
+  torrent::runtime::socket_manager()->open_event_or_throw(this, [&]() {
+      if (!torrent::fd_set_nonblock(fd))
+        throw torrent::resource_error("Could not set non-blocking on systemd 
fd: " +
+                                      std::string(std::strerror(errno)));
+      set_file_descriptor(fd);
+      // fd is already bound and listening; no bind()/listen() needed.
+    });
+
+  torrent::connection_manager()->inc_socket_count();
+}
+
+void
 SCgi::open(sockaddr* sa, unsigned int length) {
   try {
     if (::bind(file_descriptor(), sa, length) == -1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/scgi.h 
new/rtorrent-0.16.9/src/rpc/scgi.h
--- old/rtorrent-0.16.8/src/rpc/scgi.h  2026-03-15 17:58:09.000000000 +0100
+++ new/rtorrent-0.16.9/src/rpc/scgi.h  2026-04-06 16:00:46.000000000 +0200
@@ -21,6 +21,7 @@
 
   void                open_port(sockaddr* sa, unsigned int length, bool 
dont_route);
   void                open_named(const std::string& filename);
+  void                open_fd(int fd);
 
   void                activate();
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/scgi_task.cc 
new/rtorrent-0.16.9/src/rpc/scgi_task.cc
--- old/rtorrent-0.16.8/src/rpc/scgi_task.cc    2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/rpc/scgi_task.cc    2026-04-06 16:00:46.000000000 
+0200
@@ -111,6 +111,9 @@
     size_t      content_length = 0;
     const char* header_end     = current + header_size;
 
+    // Assume trusted until we find the UNTRUSTED_CONNECTION header.
+    m_trusted = true;
+
     // Parse out the null-terminated header keys and values, with
     // checks to ensure it doesn't scan beyond the limits of the
     // header
@@ -141,6 +144,8 @@
           goto event_read_failed;
       } else if (strcmp(key, "CONTENT_TYPE") == 0) {
         content_type = value;
+      } else if (strcmp(key, "UNTRUSTED_CONNECTION") == 0 && strcmp(value, 
"1") == 0) {
+        m_trusted = false;
       }
     }
 
@@ -200,7 +205,7 @@
 
 void
 SCgiTask::event_write() {
-  int bytes = ::send(m_fileDesc, m_position, m_buffer_size, MSG_NOSIGNAL);
+  int bytes = ::send(m_fileDesc, m_position, m_buffer_size, 0);
 
   if (bytes == -1) {
     if (!(errno == EAGAIN || errno == EINTR))
@@ -270,6 +275,7 @@
   // TODO: Rewrite RpcManager.process to pass the result buffer instead of 
having to copy it.
 
   auto scgi_thread = torrent::utils::Thread::self();
+  bool trusted = m_trusted;
 
   auto result_callback = [this, scgi_thread](const char* b, uint32_t l) {
       receive_write(b, l);
@@ -285,30 +291,30 @@
 
   auto lock = std::lock_guard<std::mutex>(m_result_mutex);
 
+  RpcManager::RPCType rpc_type;
+
   switch (content_type()) {
   case rpc::SCgiTask::ContentType::JSON:
-    torrent::main_thread::thread()->callback_interrupt_polling(this, [buffer, 
length, result_callback]() {
-        rpc.process(RpcManager::RPCType::JSON, buffer, length,
-                    [result_callback](const char* b, uint32_t l) {
-                      result_callback(b, l);
-                      return true;
-                    });
-      });
+    rpc_type = RpcManager::RPCType::JSON;
     break;
-
   case rpc::SCgiTask::ContentType::XML:
-    torrent::main_thread::thread()->callback_interrupt_polling(this, [buffer, 
length, result_callback]() {
-        rpc.process(RpcManager::RPCType::XML, buffer, length,
-                    [result_callback](const char* b, uint32_t l) {
-                      result_callback(b, l);
-                      return true;
-                    });
-      });
+    rpc_type = RpcManager::RPCType::XML;
     break;
-
   default:
     throw torrent::internal_error("SCgiTask::receive_call(...) received bad 
input.");
   }
+
+  torrent::main_thread::thread()->callback_interrupt_polling(this, [buffer, 
length, result_callback, trusted, rpc_type]() {
+      auto callback = [result_callback](const char* b, uint32_t l) {
+          result_callback(b, l);
+          return true;
+        };
+
+      if (trusted)
+        rpc.process(rpc_type, buffer, length, callback);
+      else
+        rpc.process_untrusted(rpc_type, buffer, length, callback);
+    });
 }
 
 void
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/scgi_task.h 
new/rtorrent-0.16.9/src/rpc/scgi_task.h
--- old/rtorrent-0.16.8/src/rpc/scgi_task.h     2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/rpc/scgi_task.h     2026-04-06 16:00:46.000000000 
+0200
@@ -53,6 +53,7 @@
   unsigned int        m_buffer_size{0};
 
   ContentType         m_content_type{ XML };
+  bool                m_trusted{true};
 };
 
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/xmlrpc_c.cc 
new/rtorrent-0.16.9/src/rpc/xmlrpc_c.cc
--- old/rtorrent-0.16.8/src/rpc/xmlrpc_c.cc     2026-03-15 17:58:09.000000000 
+0100
+++ new/rtorrent-0.16.9/src/rpc/xmlrpc_c.cc     2026-04-06 16:00:46.000000000 
+0200
@@ -385,6 +385,10 @@
 
     return object_to_xmlrpc(env, rpc::commands.call_command(itr, object, 
target));
 
+  } catch (untrusted_error& e) {
+    xmlrpc_env_set_fault(env, XMLRPC_REQUEST_REFUSED_ERROR, e.what());
+    return NULL;
+
   } catch (xmlrpc_error_c& e) {
     xmlrpc_env_set_fault(env, e.type(), e.what());
     return NULL;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/rpc/xmlrpc_tinyxml2.cc 
new/rtorrent-0.16.9/src/rpc/xmlrpc_tinyxml2.cc
--- old/rtorrent-0.16.8/src/rpc/xmlrpc_tinyxml2.cc      2026-03-15 
17:58:09.000000000 +0100
+++ new/rtorrent-0.16.9/src/rpc/xmlrpc_tinyxml2.cc      2026-04-06 
16:00:46.000000000 +0200
@@ -31,7 +31,7 @@
 // const int XMLRPC_NETWORK_ERROR                = -504;
 // const int XMLRPC_TIMEOUT_ERROR                = -505;
 const int XMLRPC_NO_SUCH_METHOD_ERROR         = -506;
-// const int XMLRPC_REQUEST_REFUSED_ERROR        = -507;
+const int XMLRPC_REQUEST_REFUSED_ERROR        = -507;
 // const int XMLRPC_INTROSPECTION_DISABLED_ERROR = -508;
 const int XMLRPC_LIMIT_EXCEEDED_ERROR         = -509;
 // const int XMLRPC_INVALID_UTF8_ERROR           = -510;
@@ -238,7 +238,11 @@
     throw rpc_error(XMLRPC_TYPE_ERROR, "invalid parameters: too few");
   }
 
-  return rpc::commands.call_command(cmd_itr, params_raw, target);
+  try {
+    return rpc::commands.call_command(cmd_itr, params_raw, target);
+  } catch (untrusted_error& e) {
+    throw rpc_error(XMLRPC_REQUEST_REFUSED_ERROR, e.what());
+  }
 }
 
 void
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/session/session_manager.cc 
new/rtorrent-0.16.9/src/session/session_manager.cc
--- old/rtorrent-0.16.8/src/session/session_manager.cc  2026-03-15 
17:58:09.000000000 +0100
+++ new/rtorrent-0.16.9/src/session/session_manager.cc  2026-04-06 
16:00:46.000000000 +0200
@@ -112,8 +112,10 @@
     if (!m_active)
       throw torrent::internal_error("SessionManager::save_download() called 
while not active.");
 
-    if (replace_save_request_unsafe(save_request))
-      throw torrent::internal_error("SessionManager::save_full_download() 
replacing existing save request, not supported?");
+    if (replace_save_request_unsafe(save_request)) {
+      LT_LOG("updated pending save request with full save data : download:%p", 
download);
+      return;
+    }
 
     m_save_requests.push_back(std::move(save_request));
     m_save_request_counter = m_save_requests.size();
@@ -426,11 +428,22 @@
   if (itr->path != save_request.path)
     throw 
torrent::internal_error("SessionManager::replace_save_request_unsafe() path 
mismatch on replace: " + itr->path + " != " + save_request.path);
 
-  if (save_request.torrent_stream != nullptr)
-    throw 
torrent::internal_error("SessionManager::replace_save_request_unsafe() cannot 
replace full save requests.");
-
-  itr->rtorrent_stream   = std::move(save_request.rtorrent_stream);
-  itr->libtorrent_stream = std::move(save_request.libtorrent_stream);
+  // If the existing request is a full save (created during torrent 
initialization),
+  // keep its torrent_stream but update the resume streams with newer data.
+  // Otherwise, replace all streams.
+  if (itr->torrent_stream != nullptr) {
+    // Keep existing full-save torrent stream, only update resume streams
+    if (save_request.rtorrent_stream != nullptr)
+      itr->rtorrent_stream = std::move(save_request.rtorrent_stream);
+    
+    if (save_request.libtorrent_stream != nullptr)
+      itr->libtorrent_stream = std::move(save_request.libtorrent_stream);
+  } else {
+    // No full save pending, replace everything
+    itr->torrent_stream    = std::move(save_request.torrent_stream);
+    itr->rtorrent_stream   = std::move(save_request.rtorrent_stream);
+    itr->libtorrent_stream = std::move(save_request.libtorrent_stream);
+  }
 
   return true;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/ui/element_download_list.cc 
new/rtorrent-0.16.9/src/ui/element_download_list.cc
--- old/rtorrent-0.16.8/src/ui/element_download_list.cc 2026-03-15 
17:58:09.000000000 +0100
+++ new/rtorrent-0.16.9/src/ui/element_download_list.cc 2026-04-06 
16:00:46.000000000 +0200
@@ -167,6 +167,8 @@
 
 void
 ElementDownloadList::receive_end() {
+  if (m_view->size_visible() == 0)
+    return;
   m_view->set_focus(m_view->end_visible() - 1);
   m_view->set_last_changed();
 }
@@ -221,10 +223,10 @@
 
   std::string old_name = view() ? view()->name() : "";
   if (!old_name.empty())
-    rpc::commands.call_catch("event.view.hide", rpc::make_target(), name, 
"View hide event action failed: ");
+    rpc::commands.call_catch("event.view.hide", rpc::make_target(), old_name, 
"View hide event action failed: ");
   set_view(*itr);
-  if (!old_name.empty())
-    rpc::commands.call_catch("event.view.show", rpc::make_target(), old_name, 
"View show event action failed: ");
+  if (!name.empty())
+    rpc::commands.call_catch("event.view.show", rpc::make_target(), name, 
"View show event action failed: ");
 }
 
 void
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/src/ui/root.cc 
new/rtorrent-0.16.9/src/ui/root.cc
--- old/rtorrent-0.16.8/src/ui/root.cc  2026-03-15 17:58:09.000000000 +0100
+++ new/rtorrent-0.16.9/src/ui/root.cc  2026-04-06 16:00:46.000000000 +0200
@@ -496,7 +496,7 @@
   } else if (style == "emacs") {
     m_keymap = emacs_keymap;
   } else {
-    throw torrent::input_error("Root::set_keymap_style() -> ui.keymap.style is 
configured with unknown keymap style: " + m_keymap_style);
+    throw torrent::input_error("Root::set_keymap_style() -> ui.keymap.style is 
configured with unknown keymap style: " + style);
   }
 
   m_keymap_style = style;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rtorrent-0.16.8/test/Makefile.in 
new/rtorrent-0.16.9/test/Makefile.in
--- old/rtorrent-0.16.8/test/Makefile.in        2026-03-15 18:07:04.000000000 
+0100
+++ new/rtorrent-0.16.9/test/Makefile.in        2026-04-06 16:10:12.000000000 
+0200
@@ -561,6 +561,8 @@
 SET_MAKE = @SET_MAKE@
 SHELL = @SHELL@
 STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
 VERSION = @VERSION@
 abs_builddir = @abs_builddir@
 abs_srcdir = @abs_srcdir@

Reply via email to