From 0bb54ac8f81c5f2cc5f8127ce921dcf4f1a3db66 Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Sat, 7 Nov 2026 11:29:27 -0800 Subject: [PATCH] fix(clients): use port 443 for HTTPS Transmission URLs that omit a port (#1673) ## Why Saving a Transmission address as HTTPS and leaving the port blank makes the connection test fail. The client opens port 9091 and reports an SSL wrong-version error, so a daemon published through a reverse proxy on the usual HTTPS port never answers. parse_transmission_url filled in 9091 whenever the URL omitted a port, and that default ignored the scheme. An https address therefore started TLS against 9091, where the peer is not speaking TLS. ## Summary An omitted port now follows the scheme: 443 for https, and 9091 for http, which still covers the empty-URL default. A port written in the URL is left as given. The parser tests cover https://transmission.myserver.org and expect https, that host, port 443, and the path /transmission/rpc. ## Testing The targeted tests for the shelfmark parse_transmission_url change pass locally. The tests passed with the shelfmark parse_transmission_url change applied; a run with it reverted gave no clear result, so no test is shown to catch this bug. Closes #1667 ## Summary by CodeRabbit * **Bug Fixes** * HTTPS Transmission URLs without an explicitly specified port now use port 443. HTTP URLs continue to default to port 9091, and the default RPC path is unchanged. * **Documentation** * Clarified the default ports for each URL scheme and noted that HTTPS instances on port 9091 require `:9091` in the URL. --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> --- docs/environment-variables.md | 4 ++-- shelfmark/download/clients/settings.py | 5 ++++- shelfmark/download/clients/torrent_utils.py | 4 +++- tests/prowlarr/test_torrent_utils.py | 8 ++++++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/environment-variables.md b/docs/environment-variables.md index a6624365..ac20fc52 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -2311,7 +2311,7 @@ How long to keep cached search results before they expire. | `QBITTORRENT_DOWNLOAD_DIR_BOOK` | Server-side directory where book torrents are downloaded (optional, uses qBittorrent default if not specified) | string | _none_ | | `QBITTORRENT_DOWNLOAD_DIR_AUDIOBOOK` | Server-side directory where audiobook torrents are downloaded (optional, uses qBittorrent default if not specified) | string | _none_ | | `QBITTORRENT_TAG` | Tag(s) to assign to qBittorrent downloads. Leave empty for no tags. | string (comma-separated) | _empty list_ | -| `TRANSMISSION_URL` | URL of your Transmission instance (use https:// for TLS) | string | _none_ | +| `TRANSMISSION_URL` | URL of your Transmission instance (use https:// for TLS). Without a port, https uses 443 and http uses 9091, so add :9091 to an https URL served on 9091 | string | _none_ | | `TRANSMISSION_USERNAME` | Transmission RPC username (if authentication enabled) | string | _none_ | | `TRANSMISSION_PASSWORD` | Transmission RPC password | string (secret) | _none_ | | `TRANSMISSION_CATEGORY` | Label to assign to book downloads in Transmission | string | `books` | @@ -2516,7 +2516,7 @@ Tag(s) to assign to qBittorrent downloads. Leave empty for no tags. **Transmission URL** -URL of your Transmission instance (use https:// for TLS) +URL of your Transmission instance (use https:// for TLS). Without a port, https uses 443 and http uses 9091, so add :9091 to an https URL served on 9091 - **Type:** string - **Default:** _none_ diff --git a/shelfmark/download/clients/settings.py b/shelfmark/download/clients/settings.py index 67a62a7a..35b9dfb8 100644 --- a/shelfmark/download/clients/settings.py +++ b/shelfmark/download/clients/settings.py @@ -815,7 +815,10 @@ def prowlarr_clients_settings() -> list[SettingsField]: TextField( key="TRANSMISSION_URL", label="Transmission URL", - description="URL of your Transmission instance (use https:// for TLS)", + description=( + "URL of your Transmission instance (use https:// for TLS). Without a port, " + "https uses 443 and http uses 9091, so add :9091 to an https URL served on 9091" + ), placeholder="http://transmission:9091", show_when={"field": "PROWLARR_TORRENT_CLIENT", "value": "transmission"}, ), diff --git a/shelfmark/download/clients/torrent_utils.py b/shelfmark/download/clients/torrent_utils.py index a1b270d4..d72c1a9a 100644 --- a/shelfmark/download/clients/torrent_utils.py +++ b/shelfmark/download/clients/torrent_utils.py @@ -382,7 +382,9 @@ def parse_transmission_url(url: str) -> tuple[str, str, int, str]: if protocol not in ("http", "https"): protocol = "http" host = parsed.hostname or "localhost" - port = parsed.port or 9091 + # An omitted port is scheme-specific: HTTPS behind a reverse proxy is 443, + # while HTTP (including the empty-URL default) stays on Transmission's RPC port. + port = parsed.port if parsed.port is not None else (443 if protocol == "https" else 9091) path = parsed.path or "/transmission/rpc" # Ensure path ends with /rpc diff --git a/tests/prowlarr/test_torrent_utils.py b/tests/prowlarr/test_torrent_utils.py index 8d9a3be7..985019df 100644 --- a/tests/prowlarr/test_torrent_utils.py +++ b/tests/prowlarr/test_torrent_utils.py @@ -87,6 +87,14 @@ class TestParseTransmissionUrl: assert port == 9091 assert path == "/transmission/rpc" + def test_parse_https_url_without_port_uses_443(self): + """Test an HTTPS URL with no port uses the implicit HTTPS port.""" + protocol, host, port, path = parse_transmission_url("https://transmission.myserver.org") + assert protocol == "https" + assert host == "transmission.myserver.org" + assert port == 443 + assert path == "/transmission/rpc" + def test_parse_url_with_ip_address(self): """Test parsing URL with IP address.""" protocol, host, port, path = parse_transmission_url("http://192.168.1.100:9091")