Feat: Convert URLs in torrent comments into clickable links - #3138
Closed
jakobbg wants to merge 132 commits into
Closed
Feat: Convert URLs in torrent comments into clickable links#3138jakobbg wants to merge 132 commits into
jakobbg wants to merge 132 commits into
Conversation
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.22.9 to 7.29.6. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.29.6/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-version: 7.29.6 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com>
#3066 fixed the ratio plugin on rtorrent 0.16.x by always sending (target_string, value) to group.NAME.ratio.{min,max,upload}.set, which is what the strict 0.16 signature requires. On older rtorrent the same set commands are the opposite — strictly a bare value, and the (target, value) shape is rejected with "Wrong object type". Encapsulate the call-shape adaptation in rTorrentSettings::getRatioGroupCommand so callers (ratio plugin flush(), but also any future consumer) do not have to know which signature applies. The helper: * always uses the canonical group.* prefix. group2.* was a transient alias name: stubbed and non-functional on 0.15.x and removed in 0.16. The old "if iVersion >= 0x904 and cmd in ratioCmds use group2." branch fell into both broken cases and produces fewer successful calls than the unconditional group.* path on every rtorrent version verified. * prepends an empty-string target to *.set commands only when iVersion >= 0x1000, so 0.15.x callers continue to receive the single-value shape they require and 0.16+ callers receive the (target, value) shape they require. ratio.php is reverted to passing the bare value to the helper for the .set commands; the helper does the version-aware wrapping. The unused $ratioCmds whitelist is dropped — only the group2.* branch consulted it, and that branch is gone. Reported by @ml-1 in #3065 after upgrading to v5.3.3 with rtorrent 0.15.7.
"Remove and delete data" removed the torrent but left the files on disk on rtorrent builds that do not register file.append. The removewithdata handler set d.custom5 and relied on the erasedata event.download.erased handler to record the file list via file.append. On rtorrent 0.16.x, file.append is only registered when method.use_deprecated is enabled at command-registration time at startup; the rc file's method.use_deprecated.set runs too late and only the -D launch flag turns it on in time. With a stock launch the method is absent, so the erase event records nothing and the data is never queued for deletion. d.erase is independent of that event, so the torrent still disappears and the data is silently orphaned. Record the file list in PHP instead, using only canonical commands (d.base_path, d.is_multi_file, f.multicall/f.frozen_path), and write the erasedata .list file directly. This works on every rtorrent version. Because rXMLRPCRequest flattens all returned values into ->val, query one torrent per request so the file list can be split back out per torrent. Clear d.custom5 so the legacy event stays inactive on builds that still provide file.append (the list written here is authoritative), avoiding a double delete. The existing erasedata garbage collector removes the listed files on its next scheduled run.
The download-erased event recorded the delete list via file.append (and a fin.sh helper), which is unavailable on rtorrent builds that do not enable method.use_deprecated. removewithdata now records that list over RPC, so the event is dead weight on every version. Keep only the scheduled garbage collector that applies the lists; drop the event registration/teardown and the now-unused fin.sh helper.
Update for magnet bulk add
This file was removed in commit 54165cb, but the stale reference means erasedata still looks for it at launch, and fails to launch if its not detected. Since this is no longer used anywhere, its safe to remove.
…ting Since raw XMLRPC from the client is now forwarded as untrusted (subject to rtorrent's command whitelist), the throttle plugin's per-torrent apply broke: it built d.stop / d.set_throttle_name / d.start commands in the browser and sent them as raw RPC. Making that work again would mean whitelisting stop/start/throttle-name for all untrusted callers. Instead, route the action through the plugin's own action.php: the client posts only the throttle value and a hash list, and the new rThrottle::apply() validates each hash (40 hex chars) and issues the commands over the trusted connection
Fixes a bug where the 'Save To' context menu item triggers the 'TypeError: can't access property "trim", d.savepath is undefined' when used on an uninitiated torrent. We solve this by falling back on base_path, which is set even for uninitialised torrents.
A user-run diagnostic that verifies the host meets ruTorrent's requirements and flags common install/config mistakes. It is never called at runtime. It runs from the command line only (`php env_check.php`, exits 0/1) and refuses to run under any web SAPI: PHP_SAPI is fixed by how PHP was invoked and cannot be influenced by an HTTP request, so it returns 403 and leaks nothing over the web even if left in a web-served directory (the built-in `php -S` server reports "cli-server", not "cli", and is refused too). It's self-contained -- the only ruTorrent file it reads is conf/config.php -- so it still runs when a missing prerequisite has otherwise broken ruTorrent. Checks: * PHP >= 7.4 (arrow functions are used in the codebase) and required extensions (json, pcre) + fsockopen for SCGI. * Recommended extensions (simplexml, curl, mbstring, zlib) and external programs (php, curl, gzip). * Configuration: $scgi_host/$scgi_port coherence, $XMLRPCMountPoint present, $topDirectory exists/readable, $log_file and $tempDirectory writable, share/ writable. * rtorrent: connects over SCGI and reports the version, classified against the supported set -- 0.9.8 (the tracker baseline) and the 0.16.x series are supported; anything else is flagged "may or may not work". The pure decision logic (version support policy, PHP minimum, SCGI/config validation) lives in a Requirements class that the live probing sits on top of, and is covered by tests/php/RequirementsTest.php. Motivated by #2996: users on unsupported PHP/rtorrent currently get a silent break with no indication of the cause.
…epair
UTF::utf8ize previously attempted to "repair" as many mangled strings
as possible, but I've observed a number of examples over the years where
it choked and broke rutorrent.
I would argue that taking arbitrary strings of bytes, and attempting
to convert them with perfect accuracy into valid UTF8 (when those strings
come to us mangled, corrupt and always with unknown encodings) isn't possible.
Here's a handful of examples of of byte sequences that choke the UTF8 repair code:
```php
require('php/utility/json.php');
var_dump(JSON::safeEncode("\xC0\x80"));
var_dump(JSON::safeEncode("\xED\xA0\x80"));
var_dump(JSON::safeEncode("\xED\xBF\xBF"));
var_dump(JSON::safeEncode("\xF5\x80\x80\x80"));
var_dump(JSON::safeEncode("\xF7\xBF\xBF\xBF"));
```
The problem is that I think this list is nowhere near comprehensive, and
even if we fix all of these, there will be many more corrupt sequences in
the wild we can't predict.
Instead of trying to repair all this corrupt data, let's just use the xFFFD
replacement character. Users who add files with corrupt strings will see the
replacement character (adding some awareness), but that becomes a content
problem and not a "ruTorrent is broken" problem.
Update JSON::safeEncode to sanitize, rather than attempting convert/repair
rTask::run() launches a task by asking rtorrent to run it (execute.nothrow.bg). That is fine for a task started from a web request, while rtorrent is idle. But a task started from a script that rtorrent invoked through an event handler's blocking `execute` -- e.g. the unpack plugin's update.php, registered on event.download.finished -- calls back into rtorrent while its single main thread is still blocked inside that same handler. The nested call cannot be serviced, so it hangs until the RPC socket times out; the client is frozen for that whole window and the half-created task is then discarded, so nothing runs. This is why automatic unpacking silently does nothing (and the UI stalls on completion) while manual unpacking, a plain web request, works. Detect that context via the CLI SAPI -- an event-handler script runs under php-cli, a web request does not -- and in that case launch the task directly (FLG_RUN_AS_WEB) rather than re-entering rtorrent. The script already runs as rtorrent's user, so the forked task keeps the same ownership. Fixes #2976.
_task: run event-triggered tasks directly instead of via rtorrent
Add php/methods-0.16.16.php with aliases for commands removed or deprecated in rTorrent 0.16.16: - Proxy addresses: network.http.proxy_address → network.proxy.http, network.proxy_address → network.proxy.global - Socket queries: network.open_sockets → system.sockets.size, network.max_open_sockets → system.sockets.max_size - d.multicall2 → d.multicall - HTTP cap: network.http.max_total_connections → system.sockets.http.min_alloc - Open files: network.max_open_files.set → system.sockets.files.min_alloc.set Update settings.php to load the new file at iVersion >= 0x1010. Update js/content.js with matching client-side aliases.
…6.x)
setHandlers() registers event.download.inserted handlers that capture the
matched ratio group / throttle channel into a custom field and then apply it
with:
branch=$not=$d.get_custom=x-extratioN,,<...>,<apply>=$d.get_custom=x-extratioN
$not=$d.get_custom=... is evaluated before branch runs, so branch gets the
value (e.g. "rat_3") as its condition. On 0.16.x `not` no longer treats a
non-empty non-numeric string as true, so the expression faults and branch
never runs the apply command: the custom field is set but the torrent is
never added to the ratio view / given the throttle channel. The rule
silently does nothing on newly added torrents, while manual assignment (a
different code path) still works.
Use the command itself as the branch condition instead of a pre-substituted
value: branch=d.get_custom=x-extratioN,<apply>=$d.get_custom=x-extratioN.
branch executes d.custom and treats the non-empty result as true, running the
apply command when a rule matched and skipping it otherwise. Also removes the
now-unused d.views.has scaffolding.
Verified on rtorrent 0.16.17: before, a matching torrent received the custom
field but stayed out of the ratio view; after, it is added to the view on
insert.
Fixes #3076.
extratio: fix ratio/throttle rules not applied on torrent insert (0.16.x)
The 5.x restyle increased row spacing compared with 4.x -- extra per-cell vertical padding/margin in the torrent list, and roomier sidebar rows -- which several users found too spacious (#2995). Add an opt-in "Compact list rows" toggle under Settings > General > User Interface. When enabled it sets a `compact` class on <body> and: - zeroes the torrent list cells' vertical padding/margin, and - tightens the sidebar (panel-label rows and category-panel headings, reached through the light DOM / ::part()). Status icons are left at their native size, so nothing is clipped. Off by default. Mirrors the existing alternate_color option (default in webui.js, apply-on-load, on-change handler, options-window row) and adds the Compact_rows string translated across all bundled languages.
The unpause action sent only d.resume, which restarts data transfer but leaves d.state=0 from pause's d.stop. ruTorrent flags any open torrent with d.state=0 as paused, so an unpaused torrent stays shown as "Pausing" forever while data flows, and rtorrent treats it as user-stopped on its next restart. Send d.start instead, matching the direct XML-RPC mount's unpause stub (rTorrentStub.prototype.unpause in js/rtorrent.js). Repro on a stock install with httprpc: click the toolbar Pause button twice on a downloading torrent - transfers resume but the status stays "Pausing" until the torrent is started and paused again.
… testing-round fixes Navigation (per review): every back/cancel/OK control used history.go(-1), which walks the browser's global history and can land outside the app or full-page-reload a stale URL. Controls now call the app's own navigation: a goBack() that knows each page's parent, and a navbar home button (house icon, replacing the back arrow) that always returns to the torrent list. Hardware/browser back still works through the existing hashchange listener. All bare buttons got type="button". Behavior fixes found while testing the round: - Restore the list scroll position after the hash update (assigning an empty fragment scrolls to top), and reset it when the sort or filter actually changes, since the old position is meaningless then - Make the filter page transactional: taps still apply live, OK commits, and leaving any other way rolls the changes back; show the matched count / total size on the filter page itself - Neutralize sticky :hover on touch devices, which left tapped buttons looking permanently pressed (Bootstrap's outline-button hover is a filled state) - Return to the list after a successful torrent add (errors keep the form for correction) - Scope toast dismiss timers to their own element - a previous toast's timers could hide the current one early - and position toasts below the top edge so iOS doesn't extend their color into the status bar - Route noty() notifications into mobile toasts, and toast deleted torrents (detected from the list diff; the desktop gets this from the history plugin, which is disabled on mobile) - Truncate file percentages like the desktop does (theConverter.round floors) instead of rounding - Shrink the filter tab's tap target to its content, so taps on the empty tab-bar space no longer open the filter page - Show the full content path (base_path) as Save As on the General tab, like the desktop details; the move form still prefills the directory - Live-refresh the settings page speed-limit selects when the limits change elsewhere (e.g. from the desktop UI) - Treat OK without changes on the save-path page as cancel; the datadir worker stops and closes the torrent even when there is nothing to move
After a successful add, the list scrolls to the new torrent's row once it appears (magnet and URL adds can take a few polls to materialize; the intent expires after 30s) and fades an accent-colored highlight on it. Detection rides the existing per-cycle row diff, picking the topmost added row in display order when several arrive at once, and is skipped when the user has navigated away or the active filter hides the row. The highlight is an inset box-shadow on the cell: it renders above the table striping, and row-level classes are reset by every row update.
…cation) The bundled JShrink minifier's saveRegex() treated the first unescaped '/' as the end of a regex literal, but a '/' inside a character class ([...]) is a literal slash. So a pattern like /[^/]/ was cut mid-regex and the rest of the file mangled, producing an "unterminated regular expression literal" SyntaxError in the browser. getplugins.php minifies the entire concatenated plugin bundle in a single pass, so one such regex in any plugin breaks the whole minified bundle and leaves the UI unable to load (empty torrent list). Several bundled plugins use class-internal slashes, e.g. /themes\/([^/]+)\// and the [^:/?#] announce-URL parser. JShrink v1.8.1 tracks the character class and only ends the regex on a '/' outside a class. It is namespaced (JShrink\Minifier), so getplugins.php now references \JShrink\Minifier::minify(); the class autoloader already strips namespaces, so nothing else needs to change. Adds tests/php/MinifierTest.php locking in the character-class behaviour and guarding plain regexes and division operators from being mis-parsed.
…er-class Update vendored JShrink to v1.8.1 (fixes regex character-class minification)
The PHP tests under tests/ were never executed in CI -- only the Jest (JS) suite ran. Wire the PHP suite in and make it fail the build on failures: - tests.yml: add a `php` job (setup-php 8.1) that runs tests/php-test.sh. - php-test.sh: exit non-zero when any test file reports a failure. Two signals are honoured -- a non-zero process exit (the TestLib self-running suites end with exit($failures)) and failure output (the TestCase runner only prints Passed/Failed). Previously the script never gated, so failures were invisible to CI. - PermissionTest::testPermission hardcoded uid 1000, so it only passed when the tests happened to run as uid 1000 -- never true in CI (uid 1001) or on most machines. Probe with the running process's own uid/gids instead.
CI: run the PHP test suite (and make it gate)
httprpc: restore d.state on unpause so torrents leave the paused state
A search icon right of the sort icon toggles a search bar fixed below the tab bar; typing filters the list live with the same semantics as the desktop quick search (case-insensitive substring on the name, * as a wildcard, 220ms debounce; see TextSearch in js/panel.js). The search narrows whatever the status/label/tracker filters matched, so the filter chip and the filter page totals reflect it, and the chip shows the active term in quotes. Closing the bar (its X button or the search icon) clears the search so the list can't stay invisibly narrowed. iOS details: the bar is tucked 1px under the measured tab-bar height so fractional heights can't leave a see-through gap, and the keyboard is dismissed when a scroll gesture starts, since position:fixed elements drift off-screen while it's open (touchmove, not scroll: Safari's scroll-into-view on focus would close it as it opens).
The markup was fetched without a cache-buster, so after an update the browser could keep serving a stale cached mobile.html while the new init.js loads through the plugin loader's ?v= token - new code running against old markup with missing element ids. Use the core's cacheBust() for the fetch, so the markup stays cached between releases and refreshes together with the code on a version bump.
Add the mobile plugin as a bundled plugin
Bumps js/webui.js to 5.3.10 and every ?v= asset token (539 -> 5310) in index.html and lang/langs.js, so cached CSS/JS/lang refresh on upgrade.
Bump version to 5.3.10 and update cache-bust params
processTorrents() read the desktop categoryList "plabel" panel
unconditionally to build the label filter list. That access threw a
TypeError in two real situations, aborting the function before any row
was rendered and leaving the torrent list blank (the error was swallowed
by the update request's empty error handler):
- the panel is not always populated when the mobile view first paints;
- the "no label" bucket ("-_-_-nlb-_-_-") only exists when at least one
torrent is unlabelled, so it is missing when every torrent has a
label -- a common configuration.
Guard both reads: fall back to a zero "no label" count and skip the
label loop when the panel is absent. When the panel is present the
behaviour is unchanged.
- Fix the empty torrent list on the direct XML-RPC mount (reported on the bundling PR): the tracker fetch sent ?action=getalltrackers with no hashes. httprpc treats that as "all torrents", but the direct mount's stub builds one t.multicall per given hash and silently sends nothing for an empty list - its callback never fires, and the list render lives in that callback. Name every hash explicitly, like the desktop's getAllTrackers, and short-circuit the zero-torrent case (the render also removes deleted torrents' rows, so it must still run) - Insert torrents that appear between full renders (e.g. added from another session) at their sorted position instead of appending them to the end of the list - Toast torrents added from elsewhere (another session, rss, automation), like the deletion toasts; a local add keeps its upload receipt and scroll-to-torrent instead. Skip the first render cycle, when every torrent counts as new - Dismiss the on-screen keyboard on scroll for all text fields, not just the quick search - Commit a new-label edit only via its check button or the return key; tapping or scrolling away now cancels instead of saving
plugin.request drops timeouts and errors by design (the next poll retries), and an exception thrown in a response handler died invisibly inside the ajax machinery. Each of the recent blank-torrent-list bugs was hidden by exactly this silence. Log all three cases to the console with the request that owned them - diagnostic breadcrumbs only, no user-facing noise.
Per review: the list fetch bypassed plugin.request (so a throwing processTorrents stayed invisible there), and the addTorrents hook isn't a request at all. Route the fetch through plugin.request and give the hook its own try/catch, so every path into processTorrents leaves a named console breadcrumb instead of a silent blank list.
plugin.theme picks the color scheme, with a live try-before-you-buy selector on the settings page like the accent one; 'system' tracks the device's preference live. Everything themes off Bootstrap's data-bs-theme, with the hardcoded light colors in mobile.css moved to the corresponding CSS variables. The near-black accent would vanish on the dark theme's near-black background, so there it resolves to its counterpart, near-white (it's relabeled "Contrast" to match), with the progress-bar stripes, progress labels and selected-filter text flipping dark to stay legible on the near-white fills. Progress labels also gain a bolder weight across all themes.
mobile: log swallowed request failures to the console
mobile: don't blank the torrent list when the label panel is incomplete
Naming every hash regressed large lists on transports that carry the query string in the request line: at ~950 torrents the URL outgrows nginx's default buffers and is 414-rejected before PHP could collapse it (stock httprpc was unaffected — its stub override collapses >50 hashes to the hashless form and POSTs a body either way). The PHP transports all treat a hashless getalltrackers as "all torrents", which is exactly what this fetch wants, so send that form. Enumerate the hashes only when a probe shows the active transport is the base XML-RPC stub — the one case that silently drops the hashless form, and the bug the enumeration was added for. There the hashes travel as POSTed XML with the stub's existing fragmentation, so no list size can overflow anything.
mobile: fix the empty torrent list on the direct XML-RPC mount, plus a round of fixes
mobile: add a theme setting: light, dark, or follow the system
Bump version to 5.3.11
jakobbg
marked this pull request as draft
July 28, 2026 10:38
Contributor
Author
|
Closing PR, will redo against develop. Sorry for this. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR updates the torrent description view to automatically identify plaintext URLs (
http,https, andftp) within the torrent comments and convert them into clickable HTML hyperlinks.Changes
getClickableComment()to handle URL regex replacement.htmlspecialchars()before parsing links to prevent XSS vulnerabilities.target="_blank"andrel="noopener noreferrer") so links safely open in a new tab without navigating away from the application.Testing Instructions
Trump: http://torrent.site/torrent.php?id=12345).