diff --git a/list_fetch.py b/list_fetch.py index f3c9506..511107b 100644 --- a/list_fetch.py +++ b/list_fetch.py @@ -217,6 +217,20 @@ def list_extract_dir(bot): return candidate +def _fetch_file_size_budget(): + """MAX_FETCH_FILE_SIZE, resolved the way dcc_fetch.py's own admission + check already reads it: 0 means no limit (#302), not a real zero-byte + ceiling. Read here rather than left as the raw setting because this + module uses the value twice - once as the zip-bomb sum cap in + _validate_zip_members(), once as the running extraction budget in + process_fetched_list_zip() - and a `budget` that starts at a literal 0 + would fail the very first byte written, the same bug either call site + would have on its own (#937 was the sibling of this one, on + MAX_FETCH_LIST_FILE_SIZE).""" + raw = int(getattr(config, "MAX_FETCH_FILE_SIZE", 200 * 1024 * 1024)) + return raw if raw > 0 else float("inf") + + def _validate_zip_members(infolist, extract_dir): """Check EVERY member before anything is extracted. Returns a short rejection reason string, or None if the whole archive is clear to @@ -229,7 +243,7 @@ def _validate_zip_members(infolist, extract_dir): f"{MAX_LIST_ZIP_ENTRIES} a real master-list archive should " f"ever need (zip-bomb-shaped guard)") - max_total = int(getattr(config, "MAX_FETCH_FILE_SIZE", 200 * 1024 * 1024)) + max_total = _fetch_file_size_budget() total_uncompressed = 0 for info in infolist: if info.is_dir(): @@ -683,8 +697,7 @@ def _extract_and_locate_list_file(zip_path, extract_dir): shutil.rmtree(platform_compat.long_path(extract_dir), ignore_errors=True) return None, reason - max_total = int(getattr(config, "MAX_FETCH_FILE_SIZE", 200 * 1024 * 1024)) - budget = max_total + budget = _fetch_file_size_budget() for info in infolist: if info.is_dir(): continue diff --git a/tests/test_list_fetch.py b/tests/test_list_fetch.py index a7ec6d9..ab07118 100644 --- a/tests/test_list_fetch.py +++ b/tests/test_list_fetch.py @@ -207,8 +207,20 @@ def test_a_declared_total_size_over_the_cap_is_rejected_before_extracting(self): self.assertFalse(ok) self.assertIn("exceeds", reason) - self.assertNotIn("bigbot", config.fetched_bot_lists) - self.assertFalse(os.path.exists(list_fetch.list_extract_dir("bigbot"))) + + def test_zero_means_no_cap_on_the_declared_total_either(self): + """#937's sibling: MAX_FETCH_FILE_SIZE = 0 is "no limit" here too, not + a zero-byte zip-bomb ceiling that rejects any real list - and the same + value is the running extraction budget, so a fix that only touched the + sum check and left `budget` starting at a literal 0 would still fail + on the very first byte written.""" + self.set_config(MAX_FETCH_FILE_SIZE=0) + big_txt = _list_txt() + ("!OtherBot Filler.flac ::INFO:: 1.0MB\n" * 50) + _write_zip(self.zip_path, [("OtherBot-2026-08-27.txt", big_txt)]) + + ok, reason = list_fetch.process_fetched_list_zip("bigbot", self.zip_path) + + self.assertTrue(ok, reason) def test_an_oversized_zip_on_disk_is_rejected_before_it_is_opened(self): """#162 finding #10, belt-to-braces half: MAX_LIST_ZIP_ENTRIES and the