fix: strip trailing slashes from the download directories

get_custom_dirs() builds the folder dropdown by removing the base path
as a prefix from every subdirectory it finds. The base directory's own
path does not carry a trailing slash, so with DOWNLOAD_DIR=/downloads/
the base failed to match itself and fell through to the leading-slash
trim, leaking 'downloads' into the dropdown as a bogus folder option.
Selecting it would have downloaded into /downloads/downloads.

Normalised in Config alongside URL_PREFIX, after the '%%' indirection so
AUDIO_DOWNLOAD_DIR is resolved first. '/' and '///' still resolve to '/'
rather than the empty string.

Found while trying to reproduce #542, which does not reproduce on
current code -- both directory listings populate correctly with a
distinct AUDIO_DOWNLOAD_DIR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Alex Shnitman
2026-08-20 10:23:40 +02:00
parent b74185b2af
commit 346da19108
2 changed files with 39 additions and 0 deletions
+28
View File
@@ -77,6 +77,34 @@ class ConfigTests(unittest.TestCase):
self.assertEqual(c.PUBLIC_HOST_URL, "https://ytdl.example.com/")
self.assertEqual(c.PUBLIC_HOST_AUDIO_URL, "https://audio.example.com/")
def test_download_dirs_lose_trailing_slash(self):
# get_custom_dirs strips the base path as a prefix from each subdirectory,
# and the base directory's own path has no trailing slash -- so a trailing
# slash here leaked the absolute path into the folder dropdown.
with patch.dict(os.environ, _base_env(
DOWNLOAD_DIR="/downloads/",
AUDIO_DOWNLOAD_DIR="/audio/",
TEMP_DIR="/tmp/",
STATE_DIR="/state/",
), clear=False):
c = Config()
self.assertEqual(c.DOWNLOAD_DIR, "/downloads")
self.assertEqual(c.AUDIO_DOWNLOAD_DIR, "/audio")
self.assertEqual(c.TEMP_DIR, "/tmp")
self.assertEqual(c.STATE_DIR, "/state")
def test_root_download_dir_survives_normalisation(self):
with patch.dict(os.environ, _base_env(DOWNLOAD_DIR="/", AUDIO_DOWNLOAD_DIR="///"), clear=False):
c = Config()
self.assertEqual(c.DOWNLOAD_DIR, "/")
self.assertEqual(c.AUDIO_DOWNLOAD_DIR, "/")
def test_download_dirs_without_trailing_slash_unchanged(self):
with patch.dict(os.environ, _base_env(DOWNLOAD_DIR="/downloads", AUDIO_DOWNLOAD_DIR="."), clear=False):
c = Config()
self.assertEqual(c.DOWNLOAD_DIR, "/downloads")
self.assertEqual(c.AUDIO_DOWNLOAD_DIR, ".")
def test_ytdl_options_json_loaded(self):
opts = {"quiet": True, "no_warnings": True}
with patch.dict(