mirror of
https://github.com/guillaumemeyer/watermarks-remover.git
synced 2026-08-22 13:11:57 +02:00
fix: truncated ISOBMFF containers still run the C2PA byte-scan fallback (#176)
* fix: truncated ISOBMFF containers still run the C2PA byte-scan fallback inspect_isobmff returned early when no boxes parse, skipping the whole-file C2PA byte scan that every sibling inspector (png, jpeg, gif, tiff) reaches even after a truncation: a first box whose size field overruns the file — the truncated AVIF/HEIC/MP4 download shape — reported has_c2pa=False with only a not-a-valid finding while literal b"c2pa" sat in the file (#167). Also reached for MP4/MOV/M4A through av_meta._inspect_mp4. Run the byte scan before reporting the parse failure: a truncated container with markers byte-scans to has_c2pa=True (finding plus the parse-failure note, so the truncation isn't hidden); a walkable container keeps its stronger box-level findings; a markerless truncated container keeps the plain not-a-valid shape. * fix: satisfy ruff in isobmff truncated byte-scan tests - drop the unused # noqa: E402 (already per-file-ignored for tests/**) - bind the unused has_ai unpacked value to _ (RUF059) --------- Co-authored-by: yzxcj797 <yzxcj797@users.noreply.github.com> Co-authored-by: Guillaume Meyer (The Opinionated Man) <1385518+guillaumemeyer@users.noreply.github.com> Co-authored-by: guillaumemeyer <guillaumemeyer@users.noreply.github.com>
This commit is contained in:
co-authored by
yzxcj797
Guillaume Meyer
guillaumemeyer
parent
1cf93d0d60
commit
0f41bd3994
@@ -512,7 +512,17 @@ def inspect_isobmff(data: bytes, fmt: str = "avif") -> tuple[bool, bool, list[st
|
||||
|
||||
boxes = _parse_isobmff_boxes(data)
|
||||
if not boxes:
|
||||
return False, False, [f"not a valid {fmt.upper()} (no ISOBMFF boxes found)"]
|
||||
# Box parsing failed (e.g. the first box's size overruns a truncated
|
||||
# download) — that is exactly when the whole-file byte scan below is
|
||||
# most useful, and every sibling inspector (png/jpeg/gif/tiff) still
|
||||
# runs its equivalent after a truncation. Run the fallback, then
|
||||
# report the parse failure alongside whatever it found (#167).
|
||||
whole = _contains_any(data, C2PA_MARKERS)
|
||||
if whole:
|
||||
findings.append(f"byte-scan C2PA markers: {', '.join(whole[:6])}")
|
||||
has_c2pa = True
|
||||
findings.append(f"not a valid {fmt.upper()} (no ISOBMFF boxes found)")
|
||||
return has_c2pa, has_ai or has_c2pa, findings
|
||||
|
||||
for fourcc, payload, _, _ in boxes:
|
||||
name = fourcc.decode("latin-1", errors="replace")
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Truncated ISOBMFF containers still run the whole-file C2PA byte scan (#167).
|
||||
|
||||
A first box whose size overruns the file (a truncated AVIF/HEIC/MP4 download)
|
||||
used to return early with a not-a-valid finding, skipping the byte-scan
|
||||
fallback every sibling inspector reaches — literal b"c2pa" could be present
|
||||
and the report still said clean.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPTS = ROOT / "service" / "scripts"
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
|
||||
import image_meta
|
||||
|
||||
|
||||
def _avif(marker: bytes, first_box_overruns: bool) -> bytes:
|
||||
ftyp = b"\x00\x00\x00\x14ftypavif" + b"\x00\x00\x00\x00" # 20-byte ftyp
|
||||
body = marker + b"\x00" * 16
|
||||
if first_box_overruns:
|
||||
# First box (ftyp) declares a size larger than the whole file.
|
||||
size = len(ftyp) + len(body) + 64
|
||||
head = struct.pack(">I", size) + b"ftypavif" + b"\x00" * 8
|
||||
return head + body
|
||||
return struct.pack(">I", len(ftyp)) + b"ftypavif" + b"\x00" * 8 + body
|
||||
|
||||
|
||||
def test_truncated_avif_still_bytescans_for_c2pa():
|
||||
data = _avif(b"c2pa contentcredentials", first_box_overruns=True)
|
||||
assert b"c2pa" in data
|
||||
|
||||
has_c2pa, _, findings = image_meta.inspect_isobmff(data, fmt="avif")
|
||||
# The byte-scan fallback fires instead of the early clean-looking return.
|
||||
assert has_c2pa is True, findings
|
||||
assert any("byte-scan C2PA markers" in f for f in findings), findings
|
||||
# The parse failure is still reported.
|
||||
assert any("no ISOBMFF boxes found" in f for f in findings), findings
|
||||
|
||||
|
||||
def test_intact_avif_box_parse_path_unchanged():
|
||||
data = _avif(b"c2pa contentcredentials", first_box_overruns=False)
|
||||
has_c2pa, _, findings = image_meta.inspect_isobmff(data, fmt="avif")
|
||||
assert has_c2pa is True
|
||||
# A walkable container finds the C2PA box directly (stronger evidence than
|
||||
# the byte scan); the parse-failure note must not appear.
|
||||
assert any("top-level box" in f and "c2pa" in f.lower() for f in findings), findings
|
||||
assert not any("no ISOBMFF boxes found" in f for f in findings)
|
||||
|
||||
|
||||
def test_truncated_markerless_avif_reports_parse_failure():
|
||||
data = _avif(b"nothing interesting", first_box_overruns=True)
|
||||
has_c2pa, _, findings = image_meta.inspect_isobmff(data, fmt="avif")
|
||||
assert has_c2pa is False
|
||||
assert findings == ["not a valid AVIF (no ISOBMFF boxes found)"]
|
||||
Reference in New Issue
Block a user