mirror of
https://github.com/alexta69/metube.git
synced 2026-09-21 13:35:01 +00:00
fix: warn when uploaded cookies shadow a configured cookiefile
Reported in #881, where the reporter had to reverse-engineer this from the outside over several days: setting YTDL_OPTIONS={"cookiefile": "/cookies/cookies.txt"} appeared to do nothing whenever a cookies file had also been uploaded through the UI. Uploaded cookies winning is correct and stays as it is. The upload exists so cookies can be refreshed without restarting the container, and letting YTDL_OPTIONS win instead would leave a visible UI button that silently does nothing. The defect is that it happened in silence, and could not be reported afterwards even in principle. set_runtime_override writes into YTDL_OPTIONS directly, so the moment an uploaded file is applied the configured path is gone from the live config: delete_cookies' existing has_manual_cookiefile check compares against COOKIES_PATH and therefore cannot fire once the value has been replaced. The two override points are the only places where both paths are still visible, so that is where the warning has to go. The startup path previously logged only "Cookie file detected"; both it and the upload handler now say plainly which file is being ignored and how to get it back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+26
@@ -1069,6 +1069,30 @@ async def start(request):
|
|||||||
|
|
||||||
COOKIES_PATH = os.path.join(config.STATE_DIR, 'cookies.txt')
|
COOKIES_PATH = os.path.join(config.STATE_DIR, 'cookies.txt')
|
||||||
|
|
||||||
|
|
||||||
|
def warn_if_cookiefile_shadowed():
|
||||||
|
"""Warn before an uploaded cookies file displaces an operator-configured one.
|
||||||
|
|
||||||
|
Uploaded cookies deliberately win: the upload exists so cookies can be
|
||||||
|
refreshed without restarting the container, and letting YTDL_OPTIONS win
|
||||||
|
would leave a visible UI button doing nothing. But set_runtime_override
|
||||||
|
writes straight into YTDL_OPTIONS, so the configured path is gone from the
|
||||||
|
live config the moment an uploaded file is applied — after that, nothing
|
||||||
|
downstream can report the conflict (delete_cookies' has_manual_cookiefile
|
||||||
|
check cannot fire once the value has been replaced). This is the only point
|
||||||
|
where both are still visible, so it is the only place the warning can be
|
||||||
|
issued. Must be called before set_runtime_override. See issue #881, where
|
||||||
|
the silence cost the reporter days of debugging.
|
||||||
|
"""
|
||||||
|
configured = config.YTDL_OPTIONS.get('cookiefile')
|
||||||
|
if isinstance(configured, str) and configured and configured != COOKIES_PATH:
|
||||||
|
log.warning(
|
||||||
|
'Uploaded cookies at %s take precedence over the cookiefile configured in '
|
||||||
|
'YTDL_OPTIONS (%s), which will not be used. Delete the uploaded cookies from '
|
||||||
|
'the UI to go back to the configured file.',
|
||||||
|
COOKIES_PATH, configured)
|
||||||
|
|
||||||
|
|
||||||
@routes.post(config.URL_PREFIX + 'upload-cookies')
|
@routes.post(config.URL_PREFIX + 'upload-cookies')
|
||||||
async def upload_cookies(request):
|
async def upload_cookies(request):
|
||||||
reader = await request.multipart()
|
reader = await request.multipart()
|
||||||
@@ -1098,6 +1122,7 @@ async def upload_cookies(request):
|
|||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
log.warning(f'Could not restrict permissions on cookies file: {exc}')
|
log.warning(f'Could not restrict permissions on cookies file: {exc}')
|
||||||
os.replace(tmp_cookie_path, COOKIES_PATH)
|
os.replace(tmp_cookie_path, COOKIES_PATH)
|
||||||
|
warn_if_cookiefile_shadowed()
|
||||||
config.set_runtime_override('cookiefile', COOKIES_PATH)
|
config.set_runtime_override('cookiefile', COOKIES_PATH)
|
||||||
log.info(f'Cookies file uploaded ({size} bytes)')
|
log.info(f'Cookies file uploaded ({size} bytes)')
|
||||||
return web.Response(text=serializer.encode({'status': 'ok', 'msg': f'Cookies uploaded ({size} bytes)'}))
|
return web.Response(text=serializer.encode({'status': 'ok', 'msg': f'Cookies uploaded ({size} bytes)'}))
|
||||||
@@ -1355,6 +1380,7 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Auto-detect cookie file on startup
|
# Auto-detect cookie file on startup
|
||||||
if os.path.exists(COOKIES_PATH):
|
if os.path.exists(COOKIES_PATH):
|
||||||
|
warn_if_cookiefile_shadowed()
|
||||||
config.set_runtime_override('cookiefile', COOKIES_PATH)
|
config.set_runtime_override('cookiefile', COOKIES_PATH)
|
||||||
log.info(f'Cookie file detected at {COOKIES_PATH}')
|
log.info(f'Cookie file detected at {COOKIES_PATH}')
|
||||||
|
|
||||||
|
|||||||
@@ -313,3 +313,40 @@ class GetCustomDirsTests(unittest.TestCase):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|
||||||
|
class WarnIfCookiefileShadowedTests(unittest.TestCase):
|
||||||
|
"""Issue #881: an uploaded cookies file wins over an operator-configured
|
||||||
|
cookiefile, and used to do so with no way for anyone to notice."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self._saved = main.config.YTDL_OPTIONS
|
||||||
|
main.config.YTDL_OPTIONS = dict(self._saved)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
main.config.YTDL_OPTIONS = self._saved
|
||||||
|
|
||||||
|
def test_warns_when_a_different_cookiefile_is_configured(self):
|
||||||
|
main.config.YTDL_OPTIONS["cookiefile"] = "/cookies/cookies.txt"
|
||||||
|
with self.assertLogs("main", level="WARNING") as cm:
|
||||||
|
main.warn_if_cookiefile_shadowed()
|
||||||
|
joined = "\n".join(cm.output)
|
||||||
|
self.assertIn("/cookies/cookies.txt", joined)
|
||||||
|
self.assertIn(main.COOKIES_PATH, joined)
|
||||||
|
|
||||||
|
def test_silent_when_no_cookiefile_configured(self):
|
||||||
|
main.config.YTDL_OPTIONS.pop("cookiefile", None)
|
||||||
|
with self.assertNoLogs("main", level="WARNING"):
|
||||||
|
main.warn_if_cookiefile_shadowed()
|
||||||
|
|
||||||
|
def test_silent_when_configured_file_is_the_uploaded_one(self):
|
||||||
|
# The steady state after an upload: re-running must not nag.
|
||||||
|
main.config.YTDL_OPTIONS["cookiefile"] = main.COOKIES_PATH
|
||||||
|
with self.assertNoLogs("main", level="WARNING"):
|
||||||
|
main.warn_if_cookiefile_shadowed()
|
||||||
|
|
||||||
|
def test_silent_on_non_string_or_empty_values(self):
|
||||||
|
for value in (None, "", 0, [], {}):
|
||||||
|
main.config.YTDL_OPTIONS["cookiefile"] = value
|
||||||
|
with self.assertNoLogs("main", level="WARNING"):
|
||||||
|
main.warn_if_cookiefile_shadowed()
|
||||||
|
|||||||
Reference in New Issue
Block a user