fix: compare whole md5 in the folder check

check_platform_folder.py, the script the site publishes, still accepted
a cut md5 by prefix. It now compares whole digests as Batocera does
(batocera-systems:1088), like verify since cut_md5_owner.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 16:01:04 +01:00
1 parent 7d99685ce1
commit 35be4cf033
4 files changed
+12 -9

No files matched your search

+2 -2
View File
@@ -27,8 +27,8 @@ def accepts(entry: dict, path: Path, mode: str) -> bool:
member = names.get(entry["zipped_file"].casefold())
return bool(member) and hashlib.new(digest, archive.read(member)).hexdigest() in values
actual = hashlib.new(digest, path.read_bytes()).hexdigest()
if entry.get("zip_digest") != "composite" and any(actual.startswith(v) for v in values):
return True # a truncated md5 (Batocera) compares by its prefix
if entry.get("zip_digest") != "composite" and actual in values:
return True # whole digests, as batocera-systems:1088 compares them
if digest == DIGESTS["md5"] and path.suffix.lower() == ".zip": # Recalbox: members in name order
with zipfile.ZipFile(path) as archive:
names = sorted(name for name in archive.namelist() if not name.endswith("/"))
+2 -1
View File
@@ -658,10 +658,11 @@ class ShortMd5Handling(unittest.TestCase):
scraper = inspect.getsource(batocera_scraper)
self.assertIn("_resolve_truncated_md5(value, md5_index) for value in values", scraper)
resolver = inspect.getsource(common.resolve_local_file)
self.assertIn("if len(md5_candidate) < 32:", resolver)
self.assertIn("cut_md5_owner(cut, by_md5)", resolver)
self.assertIn("_md5_name_ok(path)", resolver)
section = _section(_page("verification-modes.md"), "MD5 shorter than 32 characters (Batocera)")
self.assertIn("_resolve_truncated_md5", section)
self.assertIn("cut_md5_owner", section)
class RegistryKeysDocumented(unittest.TestCase):
+6 -5
View File
@@ -59,10 +59,11 @@ class EachProfileEntryIsHashedOnce(unittest.TestCase):
cats = EntryCategories(_config(["core"], []), profiles)
real = candidates._entry_hashes
with mock.patch.object(candidates, "_entry_hashes", side_effect=real) as counted:
for md5 in ("a" * 32, "a" * 29):
self.assertEqual(cats.category({"name": "a.bin", "md5": md5}, "a.bin", "s"), BIOS)
for _ in range(2):
self.assertEqual(cats.category({"name": "a.bin", "md5": "a" * 32}, "a.bin", "s"), BIOS)
self.assertEqual(counted.call_count, 1)
self.assertTrue(candidates._accepts({"a" * 32}, {"md5": {"a" * 29}}))
# A cut md5 verifies nothing: Batocera compares the whole string.
self.assertFalse(candidates._accepts({"a" * 32}, {"md5": {"a" * 29}}))
self.assertFalse(candidates._accepts({"a" * 32}, {"md5": {"b" * 32}}))
class Category(unittest.TestCase):
@@ -240,8 +241,8 @@ class AProfileIsIndexedOncePerKind(unittest.TestCase):
def test_the_index_answers_as_the_scan_did(self):
cases = [
({"name": "one.bin", "md5": "2" * 32}, BIOS, [{"name": "one.bin", "source_ref": "a.c:3"}]),
# A cut md5 (Batocera's 29 characters) matches by prefix.
({"name": "x", "md5": "1" * 29}, BIOS, [{"name": "one.bin", "source_ref": "a.c:2"}]),
# A cut md5 proves nothing: hashes compare whole.
({"name": "x", "md5": "1" * 29}, BIOS, []),
({"name": "one.bin"}, BIOS, [
{"name": "one.bin", "source_ref": "a.c:2", "by": "name"},
{"name": "one.bin", "source_ref": "a.c:3", "by": "name"},
+2 -1
View File
@@ -545,7 +545,8 @@ class AFolderCheckAgreesWithTheVerifier(unittest.TestCase):
for entry in contract["systems"]["s"]["files"]
]
self.assertEqual(found, expected)
self.assertEqual([v for _d, v in found], ["ok", "wrong", "ok", "wrong", "ok", "missing"])
# plain.bin declares a cut md5: Batocera compares the whole string.
self.assertEqual([v for _d, v in found], ["ok", "wrong", "wrong", "wrong", "ok", "missing"])
def test_batocera_from_the_csv_matches_the_verifier(self):
"""A check driven by platform-files.csv, with zipped_file, gives the