feat: read the hash out of a checksum tool's line

This commit is contained in:
Abdessamad Derraz committed 2026-11-05 12:09:55 +01:00
1 parent ea2e0b10c4
commit 9c17943996
3 files changed
+50 -2

No files matched your search

+2 -1
View File
@@ -4517,8 +4517,9 @@ def generate_files_page() -> str:
"",
'<form class="rb-file-lookup" action="./" method="get">',
'<label for="rb-hash">SHA1, MD5, CRC32 or SHA256</label> ',
# No pattern: the line md5sum or certutil prints is taken whole, and
# the page reads the hash out of it.
'<input id="rb-hash" name="hash" size="66" autocomplete="off" spellcheck="false" '
'pattern="\\s*(0x)?([0-9a-fA-F]{8}|[0-9a-fA-F]{32}|[0-9a-fA-F]{40}|[0-9a-fA-F]{64})\\s*" '
'required> ',
'<button type="submit">Find</button>',
"</form>",
+33
View File
@@ -265,5 +265,38 @@ class CopyButtonInTheHeader(unittest.TestCase):
self.assertIn(".expressive-code .frame.has-title .copy { inset-block-start:", source)
class AChecksumLineIsAHash(unittest.TestCase):
"""The lookup and the search took a bare hash only: the line md5sum,
shasum --tag, certutil or Get-FileHash prints was refused whole."""
def test_the_hash_is_read_out_of_the_line(self):
source = _read(SITE_TS)
code = (source[source.index("const HASH_LENGTHS"):source.index("\n", source.index("const HASH_LENGTHS"))]
+ source[source.index("const normalizeHash"):source.index("// Tables")])
md5, sha1 = "490f666e1afb15b7362b406ed1cea246", "b05def971d8ec59f346f2d9ac21fb742e3eb6917"
spaced = " ".join(sha1[i:i + 2] for i in range(0, 40, 2))
cases = [
f"{md5} scph5501.bin",
f"SHA1 (scph5501.bin) = {sha1}",
f"SHA1 hash of scph5501.bin:\r\n{spaced}\r\nCertUtil: -hashfile command completed successfully.",
f"Algorithm Hash Path\n--------- ---- ----\nSHA1 {sha1.upper()} C:\\bios\\scph5501.bin",
f" 0x{md5.upper()} ",
"8d8cb7e4",
"deadbeef.bin",
f"{md5} {sha1}",
]
out = _node(code + f"console.log(JSON.stringify({json.dumps(cases)}.map((c) => "
"[normalizeHash(c), isHash(normalizeHash(c))])));")
self.assertEqual(json.loads(out), [
[md5, True], [sha1, True], [sha1, True], [sha1, True], [md5, True],
["8d8cb7e4", True], ["deadbeef.bin", False], [md5 + sha1, False],
])
def test_the_form_takes_the_line(self):
source = _read(REPO_ROOT / "scripts" / "generate_site.py")
start = source.index('<input id="rb-hash"')
self.assertNotIn("pattern=", source[start:source.index("required>", start)])
if __name__ == "__main__":
unittest.main()
+15 -1
View File
@@ -24,7 +24,21 @@ type Item = {
};
const siteRoot = () => new URL(import.meta.env.BASE_URL.replace(/\/?$/, '/'), location.origin);
const normalizeHash = (raw: string) => raw.trim().toLowerCase().replace(/^0x/, '').replace(/\s+/g, '');
// A pasted hash, or the line a checksum tool prints around it: md5sum
// (`<hash> <file>`), BSD and shasum --tag (`SHA1 (<file>) = <hash>`),
// certutil (the bytes spaced on a line of their own), Get-FileHash's table.
// The one word of an MD5, SHA1 or SHA256's length is taken: an 8-character
// word in a file name searched for is no CRC32. None or two, and the text is
// kept as typed, which isHash refuses.
const normalizeHash = (raw: string) => {
const text = raw.trim().toLowerCase();
const bare = text.replace(/^0x/, '').replace(/\s+/g, '');
if (/^[0-9a-f]*$/.test(bare)) return bare;
const words = text.split('\n').flatMap((line) =>
/^[0-9a-f ]+$/.test(line.trim()) ? [line.replace(/\s+/g, '')] : (line.match(/\b[0-9a-f]+\b/g) ?? []));
const found = [...new Set(words.filter((word) => word.length > 8 && word.length in HASH_LENGTHS))];
return found.length === 1 ? found[0] : bare;
};
const isHash = (value: string) => /^[0-9a-f]+$/.test(value) && value.length in HASH_LENGTHS;
// Tables