Fix: qBittorrent 5.2.0+ login probe fails (HTTP 204 not handled)
qBittorrent 5.2.0 changed /api/v2/auth/login to return HTTP 204 (No Content) on success instead of HTTP 200 with body 'Ok.'. The adapter required the body to equal 'Ok.', so every login on 5.2.0+ failed with 'HTTP 204 body=' — the connection probe and all torrent actions were broken. Treat login as successful on the SID auth cookie and/or a success body: 'Ok.' (<=5.1) or an empty HTTP 204 (>=5.2.0). Still reject bad creds, which qBittorrent reports as HTTP 200 + 'Fails.' (not a 4xx). Tests: 204-empty -> success, SID-cookie+empty-body -> success, 'Fails.' (even with a stale cookie) -> failure.
This commit is contained in:
parent
807ac39570
commit
45f91fd318
2 changed files with 58 additions and 1 deletions
|
|
@ -117,7 +117,19 @@ class QBittorrentAdapter:
|
|||
headers={'Referer': self._url},
|
||||
timeout=self.DEFAULT_TIMEOUT,
|
||||
)
|
||||
if not resp.ok or resp.text.strip() != 'Ok.':
|
||||
body = resp.text.strip()
|
||||
has_sid = bool(sess.cookies.get('SID'))
|
||||
# qBittorrent reports BAD credentials as HTTP 200 + body "Fails."
|
||||
# (it does NOT use a 4xx). SUCCESS is the SID auth cookie and/or a
|
||||
# success body: "Ok." on <= 5.1, or an empty HTTP 204 on 5.2.0+,
|
||||
# which changed /api/v2/auth/login to return 204 No Content.
|
||||
# The old check required body == "Ok." and so rejected 5.2.0+.
|
||||
login_ok = (
|
||||
resp.ok
|
||||
and body.lower() != 'fails.'
|
||||
and (has_sid or resp.status_code == 204 or body in ('', 'Ok.'))
|
||||
)
|
||||
if not login_ok:
|
||||
logger.error("qBittorrent login failed: HTTP %s body=%r", resp.status_code, resp.text[:200])
|
||||
return None
|
||||
self._session = sess
|
||||
|
|
|
|||
|
|
@ -150,6 +150,51 @@ def test_qbit_login_failure_returns_none() -> None:
|
|||
assert sess is None
|
||||
|
||||
|
||||
def test_qbit_login_accepts_204_no_content() -> None:
|
||||
"""qBittorrent 5.2.0+ returns HTTP 204 with an empty body on a successful
|
||||
login (was HTTP 200 + 'Ok.'). The adapter must treat that as success even
|
||||
when no SID cookie is visible to us."""
|
||||
adapter = _qbit_with_config()
|
||||
fake_session = MagicMock()
|
||||
fake_session.cookies.get.return_value = None # no SID surfaced
|
||||
resp = _mock_response(204, text='')
|
||||
resp.text = ''
|
||||
fake_session.post.return_value = resp
|
||||
with patch('core.torrent_clients.qbittorrent.http_requests.Session',
|
||||
return_value=fake_session):
|
||||
sess = adapter._ensure_session_sync()
|
||||
assert sess is not None
|
||||
|
||||
|
||||
def test_qbit_login_accepts_sid_cookie_with_empty_body() -> None:
|
||||
"""A SID auth cookie is the authoritative success signal regardless of body."""
|
||||
adapter = _qbit_with_config()
|
||||
fake_session = MagicMock()
|
||||
fake_session.cookies.get.return_value = 'SID-abc123'
|
||||
resp = _mock_response(200, text='')
|
||||
resp.text = ''
|
||||
fake_session.post.return_value = resp
|
||||
with patch('core.torrent_clients.qbittorrent.http_requests.Session',
|
||||
return_value=fake_session):
|
||||
sess = adapter._ensure_session_sync()
|
||||
assert sess is not None
|
||||
|
||||
|
||||
def test_qbit_login_rejects_fails_even_with_stale_cookie() -> None:
|
||||
"""Bad creds: qBittorrent returns HTTP 200 'Fails.' (not a 4xx). Must fail
|
||||
even if a stale SID cookie lingers on the session."""
|
||||
adapter = _qbit_with_config()
|
||||
fake_session = MagicMock()
|
||||
fake_session.cookies.get.return_value = 'SID-stale'
|
||||
resp = _mock_response(200, text='Fails.')
|
||||
resp.text = 'Fails.'
|
||||
fake_session.post.return_value = resp
|
||||
with patch('core.torrent_clients.qbittorrent.http_requests.Session',
|
||||
return_value=fake_session):
|
||||
sess = adapter._ensure_session_sync()
|
||||
assert sess is None
|
||||
|
||||
|
||||
def test_qbit_parse_status_normalises_native_fields() -> None:
|
||||
adapter = _qbit_with_config()
|
||||
status = adapter._parse_status({
|
||||
|
|
|
|||
Loading…
Reference in a new issue