fix(archive): magic-byte archive detection so mis-named archives extract — #713 part 1
Patreon attachment downloads land with sanitized URL-blob filenames
(01_https___www.patreon.com_media-u_v3_<id>) whose Path.suffix is junk, never
.zip — so the extension-only is_archive() filed them as opaque PostAttachments
and never extracted them ("No images attached to this post").
Add archive_extractor.detect_archive_format() — extension first, then magic-byte
sniff (zipfile.is_zipfile + RAR/7z signatures). is_archive(), extract_archive(),
and safe_probe._inspect_archive() (the bomb-guard) all route through it, so a
mis-named/extension-less archive is now detected, bomb-guarded, integrity-tested,
AND extracted regardless of filename. Stops new ones; part 2 re-extracts the
already-imported backlog.
Tests: mis-named zip detected + extracted; non-archive dotted name not
misdetected; _inspect_archive on a mis-named zip; signature updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,24 @@ def test_cbz_alias(tmp_path):
|
||||
assert [n for n, _ in members] == ["p1.jpg"]
|
||||
|
||||
|
||||
def test_misnamed_zip_detected_and_extracted(tmp_path):
|
||||
"""A real zip whose filename has no usable extension (Patreon attachment
|
||||
URL-blob name) is detected by magic bytes and its members extracted —
|
||||
previously it was filed as an opaque attachment and never opened."""
|
||||
z = tmp_path / "01_https___www.patreon.com_media-u_v3_131083093"
|
||||
_zip(z, {"a.jpg": b"img", "b.png": b"img2"})
|
||||
assert is_archive(z)
|
||||
with extract_archive(z) as members:
|
||||
assert sorted(n for n, _ in members) == ["a.jpg", "b.png"]
|
||||
|
||||
|
||||
def test_non_archive_with_dotted_name_is_not_archive(tmp_path):
|
||||
"""A non-archive file with a dotted/extension-less name isn't misdetected."""
|
||||
f = tmp_path / "01_https___www.patreon.com_media-u_v3_999"
|
||||
f.write_bytes(b"\x89PNG\r\n\x1a\n not really but not a zip")
|
||||
assert not is_archive(f)
|
||||
|
||||
|
||||
def test_corrupt_archive_yields_nothing(tmp_path):
|
||||
bad = tmp_path / "broken.zip"
|
||||
bad.write_bytes(b"not a zip at all")
|
||||
|
||||
@@ -40,11 +40,21 @@ def test_probe_archive_corrupt_zip_clean_rejection(tmp_path):
|
||||
def test_inspect_archive_reports_size_and_clean_integrity(tmp_path):
|
||||
z = tmp_path / "sized.zip"
|
||||
_zip(z, {"a.txt": b"x" * 100, "b.txt": b"y" * 50})
|
||||
total, bad = safe_probe._inspect_archive(z, ".zip")
|
||||
total, bad = safe_probe._inspect_archive(z)
|
||||
assert total == 150
|
||||
assert bad is None
|
||||
|
||||
|
||||
def test_inspect_archive_detects_misnamed_zip(tmp_path):
|
||||
"""A zip with a mangled / extension-less name (Patreon URL-blob attachment)
|
||||
is still bomb-guarded + integrity-tested via magic-byte detection."""
|
||||
z = tmp_path / "01_https___www.patreon.com_media-u_v3_131083093"
|
||||
_zip(z, {"a.txt": b"x" * 100})
|
||||
total, bad = safe_probe._inspect_archive(z)
|
||||
assert total == 100
|
||||
assert bad is None
|
||||
|
||||
|
||||
def test_run_probe_bomb_guard(tmp_path, monkeypatch):
|
||||
"""In-process call to _run_probe so the monkeypatched cap takes effect.
|
||||
The public probe_archive runs in a subprocess which re-imports the
|
||||
|
||||
Reference in New Issue
Block a user