From 12b7cdd77d853fb5429c8a4f85ee330c66f45c3c Mon Sep 17 00:00:00 2001 From: muffin Date: Tue, 24 Mar 2026 18:25:43 +0000 Subject: [PATCH] fix: enable unsafe cookie jar for IP-based qBittorrent hosts aiohttp rejects session cookies by default when the host is an IP literal. Detect whether QBITTORRENT_HOST is an IP address at startup and, if so, create the ClientSession with CookieJar(unsafe=True). Hostname-based connections are unaffected. Made-with: Cursor --- qsticky.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/qsticky.py b/qsticky.py index 0f9ef3a..75d04d8 100644 --- a/qsticky.py +++ b/qsticky.py @@ -3,6 +3,7 @@ import logging import aiohttp import asyncio +import ipaddress import signal import ssl from typing import Optional, Dict, Any @@ -98,6 +99,9 @@ def __init__(self): self.last_login_failed = False self.first_run = True self.last_known_port = None + self.use_unsafe_qbit_cookie_jar = self._is_ip_address( + self.settings.qbittorrent_host + ) def _setup_logger(self) -> logging.Logger: logger = logging.getLogger("qsticky") @@ -110,6 +114,19 @@ def _setup_logger(self) -> logging.Logger: logger.addHandler(handler) return logger + def _is_ip_address(self, host: str) -> bool: + try: + ipaddress.ip_address(host) + return True + except ValueError: + return False + + def _get_qbit_cookie_jar(self) -> Optional[aiohttp.CookieJar]: + if not self.use_unsafe_qbit_cookie_jar: + return None + + return aiohttp.CookieJar(unsafe=True) + async def get_current_qbit_port(self) -> Optional[int]: self.logger.debug("Retrieving current qBittorrent port") try: @@ -151,7 +168,11 @@ async def _init_session(self) -> None: self.logger.debug("SSL verification enabled") connector = aiohttp.TCPConnector(ssl=ssl_context) - self.session = aiohttp.ClientSession(timeout=timeout, connector=connector) + self.session = aiohttp.ClientSession( + timeout=timeout, + connector=connector, + cookie_jar=self._get_qbit_cookie_jar() + ) self.logger.debug("Session initialized with timeouts") async def _login(self) -> bool: @@ -293,7 +314,11 @@ async def handle_port_change(self) -> None: ssl_context.verify_mode = ssl.CERT_NONE connector = aiohttp.TCPConnector(ssl=ssl_context) - async with aiohttp.ClientSession(timeout=ClientTimeout(total=30), connector=connector) as session: + async with aiohttp.ClientSession( + timeout=ClientTimeout(total=30), + connector=connector, + cookie_jar=self._get_qbit_cookie_jar() + ) as session: self.session = session try: new_port = await self._get_forwarded_port() @@ -473,4 +498,4 @@ async def main() -> None: await manager.cleanup() if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main())