fix(qbittorrent): recognize SID cookie from raw Set-Cookie header on login
qBittorrent (or its reverse proxy) was returning 204 No Content with an SID
cookie and no body on a successful login, but the client only treated the
response as success if body == "Ok." or the cookie was in the parsed
requests cookie jar (resp.cookies.get("SID")). In the reported case the
Set-Cookie header was present (set-cookie=yes) yet the jar was empty —
requests doesn't always populate the jar from such headers (proxy-set/oddly-
attributed cookies) — so a valid login was reported as "Unexpected response".
The user re-entering correct credentials never helped.
Detect a successful login from EITHER the parsed jar OR the raw Set-Cookie
header (cookie name == SID). qBittorrent only sets SID on a valid login, so
this remains authoritative. The diagnostic now lists the cookie names it saw
for future-proofing.
New regression test reproduces the exact 204 + Set-Cookie SID + empty jar
case. 387/387 backend tests pass; ruff clean.
This commit is contained in:
@@ -74,20 +74,25 @@ class QbittorrentClient:
|
||||
)
|
||||
resp.raise_for_status()
|
||||
body = resp.text.strip()
|
||||
# Some reverse proxies forward the SID cookie but mangle the text body;
|
||||
# accept either success signal. Guard the cookie read behind an empty
|
||||
# body so a mocked response never accidentally reads as success.
|
||||
sid_ok = body == "" and bool(resp.cookies.get("SID"))
|
||||
# qBittorrent signals a successful login with the body "Ok." and/or by
|
||||
# setting a SID session cookie. Some setups (newer qBittorrent, or some
|
||||
# reverse proxies) return 204 No Content with the SID cookie and no
|
||||
# body. ``requests`` can also fail to populate the cookie jar for
|
||||
# cookies with attributes it doesn't parse, so check the raw
|
||||
# Set-Cookie header as well. qBittorrent only sets SID on a valid login.
|
||||
set_cookie_hdr = resp.headers.get("Set-Cookie", "") or ""
|
||||
first_cookie_name = set_cookie_hdr.split("=", 1)[0].strip().upper()
|
||||
sid_ok = bool(resp.cookies.get("SID")) or first_cookie_name == "SID"
|
||||
if body == "Ok." or sid_ok:
|
||||
self._logged_in = True
|
||||
logger.info("qBittorrent login successful for %s", self.base_url)
|
||||
return
|
||||
if body == "Fails.":
|
||||
raise RuntimeError(f"qBittorrent login failed (HTTP {resp.status_code}): invalid username or password")
|
||||
set_cookie = "yes" if resp.headers.get("Set-Cookie") else "no"
|
||||
cookie_names = sorted(resp.cookies.keys()) or (["<unparsed>"] if set_cookie_hdr else [])
|
||||
raise RuntimeError(
|
||||
f"Unexpected response from qBittorrent login endpoint (HTTP {resp.status_code}, "
|
||||
f"body={body!r}, set-cookie={set_cookie}). Expected the text 'Ok.' (or an SID cookie) "
|
||||
f"body={body!r}, cookies={cookie_names}). Expected the text 'Ok.' or an SID cookie "
|
||||
"from /api/v2/auth/login — this usually means base_url does not reach the qBittorrent "
|
||||
"Web API (check the URL, path, and any reverse proxy in front of qBittorrent)."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user