diff --git a/tools/packs/fetch-verified.sh b/tools/packs/fetch-verified.sh index 0b8c03977..41bba2819 100644 --- a/tools/packs/fetch-verified.sh +++ b/tools/packs/fetch-verified.sh @@ -22,6 +22,9 @@ else curl -fsSL --retry 4 --retry-all-errors --connect-timeout 15 --max-time 180 --retry-max-time 240 \ -o "$part" "$url" verify "$part" || { echo "Checksum mismatch for $url" >&2; exit 1; } + if [ -d "$kept" ]; then + rm -rf -- "$kept" + fi mv -f "$part" "$kept" echo "Downloaded and verified pack input: $expected" fi diff --git a/tools/tests/test_pack_downloads.py b/tools/tests/test_pack_downloads.py index 20547c03b..0b5eb6060 100644 --- a/tools/tests/test_pack_downloads.py +++ b/tools/tests/test_pack_downloads.py @@ -51,6 +51,25 @@ class PackDownloadsTest(unittest.TestCase): self.assertEqual(len(self.log.read_text().splitlines()), 2) self.assertEqual((self.cache / self.checksum).read_bytes(), b'verified input') + def test_directory_cache_entry_is_replaced_only_after_verification(self): + entry = self.cache / self.checksum + entry.mkdir() + sentinel = entry / 'old-entry' + sentinel.write_bytes(b'preserve until verified') + self.remote.write_bytes(b'tampered download') + self.assertNotEqual(self.fetch().returncode, 0) + self.assertEqual(sentinel.read_bytes(), b'preserve until verified') + self.assertFalse(self.destination.exists()) + self.remote.write_bytes(b'verified input') + result = self.fetch() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertTrue(entry.is_file()) + self.assertEqual(entry.read_bytes(), b'verified input') + self.assertEqual(self.destination.read_bytes(), b'verified input') + self.assertEqual(list(self.cache.iterdir()), [entry]) + self.assertEqual(self.fetch().returncode, 0) + self.assertEqual(len(self.log.read_text().splitlines()), 2) + def test_changed_pin_cannot_reuse_old_bytes(self): self.assertEqual(self.fetch().returncode, 0) self.remote.write_bytes(b'new approved input')