From 35be4cf033ed6e04aedbbcfe5f3b2d8f9b7e659a Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 6 Nov 2026 16:01:04 +0100 Subject: [PATCH] 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. --- scripts/check_platform_folder.py | 4 ++-- tests/test_doc_pages.py | 3 ++- tests/test_entry_categories.py | 11 ++++++----- tests/test_site_reference.py | 3 ++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/scripts/check_platform_folder.py b/scripts/check_platform_folder.py index 4c5401a44..b45314561 100644 --- a/scripts/check_platform_folder.py +++ b/scripts/check_platform_folder.py @@ -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("/")) diff --git a/tests/test_doc_pages.py b/tests/test_doc_pages.py index cdd6c3ed1..1afcda052 100644 --- a/tests/test_doc_pages.py +++ b/tests/test_doc_pages.py @@ -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): diff --git a/tests/test_entry_categories.py b/tests/test_entry_categories.py index 0f971c5d9..234c6d395 100644 --- a/tests/test_entry_categories.py +++ b/tests/test_entry_categories.py @@ -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"}, diff --git a/tests/test_site_reference.py b/tests/test_site_reference.py index 36f130fde..ace76c9fd 100644 --- a/tests/test_site_reference.py +++ b/tests/test_site_reference.py @@ -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