detect_content_type() peeked at m_buffer[m_body] to infer JSON vs XML
when no CONTENT_TYPE header was provided. When the TCP header segment
arrives without any body bytes, m_body equals m_position and the peek
reads the null terminator padding byte — not the actual '{' or '[' —
causing JSON requests to be incorrectly classified as XML and fail.
Fix:
- Remove the body peek from detect_content_type(); defer it to after
the full body is confirmed present in event_read().
- Add a m_content_type_set flag to distinguish header-provided type
from auto-detected type.
SCgiTask objects are pre-allocated in a pool (scgi.cc) and reused across
SCGI connections. SCgiTask::open() did not reset m_trusted, so when a
task that had handled an untrusted connection (m_trusted=false) was
reused for a new connection, m_trusted stayed false unless the new
connection explicitly sent UNTRUSTED_CONNECTION=1.
The header parser only set m_trusted=false on value 1 and was a no-op
on value 0 (the comment said "default is trusted, so do nothing") —
which is wrong for a reused task that is no longer in default state.
This caused intermittent rejection of trusted commands (e.g. ruTorrent
calling execute.capture for UID detection) with "Command X is not allowed
for untrusted connections", producing cascading plugin failures and
"ruTorrent cannot determine the UID of rTorrent user" in the web UI.
Fix:
- SCgiTask::open() resets m_trusted=true to default.
- parse_headers explicitly sets m_trusted=true on UNTRUSTED_CONNECTION=0,
so the value sent on the wire is authoritative regardless of pool
reuse semantics.
Verified on gb4 with rtorrent 0.16.11 + this fix: 30/30 trusted calls
succeed, 30/30 untrusted correctly blocked, 30/30 trusted-after-untrusted
batch all succeed (previously 70%+ would fail in the same scenario).
1. network.rpc.use_xmlrpc and network.rpc.use_jsonrpc: change from
CMD2_VAR_BOOL_U (getter+setter both safe) to CMD2_VAR_BOOL_U_GET
(getter safe, setter trusted-only). Untrusted callers could
previously disable RPC transports entirely.
2. Remove broad catch(std::exception&) and catch(...) from xmlrpc_c.cc
that masked real defects and altered fault semantics.
3. Revert SCGI callback catch-all to re-throw instead of swallowing
exceptions with a generic error response.
Root cause: network.rpc.use_xmlrpc and network.rpc.use_jsonrpc were not
marked as untrusted-safe, but RpcManager::process() calls them before
dispatching to the protocol handler. When an untrusted request arrived,
call_command() threw untrusted_error for these gatekeepers, which escaped
the callback_interrupt_pollling callback and crashed rtorrent.
Fix: Mark network.rpc.use_xmlrpc/jsonrpc as safe (CMD2_VAR_BOOL_U).
Also harden exception safety:
- SCGI callback catch-all now sends a generic error response instead of
re-throwing, since the callback infrastructure may not support
exception propagation.
- xmlrpc_c.cc now has catch(std::exception&) and catch(...) safety nets
after the specific exception handlers.
Replace the v2 blacklist approach with a per-command flag system.
Commands must opt in to being available for untrusted connections
via flag_untrusted_safe (0x400), checked in call_command() which
catches all execution paths including nested commands.
Infrastructure changes:
- Add flag_untrusted_safe to CommandMap
- Add untrusted_error exception type for proper error codes
- Enforce trust check in both call_command() overloads
- Add catch blocks in xmlrpc_c, xmlrpc_tinyxml2, and jsonrpc handlers
- Port SCGI trust state management from v2 (thread_local, header parsing)
- Add _U macro variants in command_helpers.h for safe command registration
- Add CMD2_VAR_*_U and CMD2_VAR_*_U_GET variants for variables
- Close pipe fds on fork failure in ExecFile::execute
- Add exception-safe fclose in cmd_file_append via try/catch
- Add overflow guards before K/M/G bit shifts in parse_whole_value
- Fix %u format for int* in sscanf (change to %d)
- Fix typo "atter"→"after" in error message