v31.1-roots.1 · Commit 8: [PATCH 08/17] chore(roots): integrate v31 identity and maintenance tooling
ci/test/test_prepare_release.py
Before: /dev/null · After: ci/test/test_prepare_release.py
File metadata
new file mode 100755 index 0000000000000000000000000000000000000000..eefc00275c41480c3e1b45768881746ec3d59b15
Download original patch · Release notes
Diff rows 401–457 of 457. Line numbers refer to the original before/after files.
+ Added− RemovedUnmarked lines are context.
Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.
ci/test/test_prepare_release.py
ADDED
|
@@ -0,0 +1,457 @@ (continued)
|
|
|
401
|
+
f"{framework}/Versions/A/QtGui"],
|
|
402
|
+
)
|
|
403
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
404
|
+
|
|
405
|
+
def test_accepts_repeated_noncyclic_symlink_traversal(self):
|
|
406
|
+
result = self.validate_zip_links(
|
|
407
|
+
{"alias": "dir", "link": "alias/../alias/file"}, ["dir/", "dir/file"],
|
|
408
|
+
)
|
|
409
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
410
|
+
|
|
411
|
+
def test_rejects_unsafe_dangling_and_cyclic_zip_symlinks(self):
|
|
412
|
+
cases = [
|
|
413
|
+
{"link": "/tmp/outside"},
|
|
414
|
+
{"link": "../outside"},
|
|
415
|
+
{"link": "C:/outside"},
|
|
416
|
+
{"link": "..\\outside"},
|
|
417
|
+
{"link": "outside\0"},
|
|
418
|
+
{"link": ""},
|
|
419
|
+
{"link": b"\xff"},
|
|
420
|
+
{"link": "x" * 4097},
|
|
421
|
+
{"link": "missing"},
|
|
422
|
+
{"link": "other", "other": "link"},
|
|
423
|
+
# Resolve alias before ..; lexical normalization alone misses this escape.
|
|
424
|
+
{"alias": ".", "link": "alias/../outside"},
|
|
425
|
+
]
|
|
426
|
+
for links in cases:
|
|
427
|
+
with self.subTest(links=links):
|
|
428
|
+
result = self.validate_zip_links(links, ["bin/bitcoind", "outside"])
|
|
429
|
+
self.assertNotEqual(result.returncode, 0)
|
|
430
|
+
|
|
431
|
+
def test_symlinks_alone_do_not_count_as_release_payload(self):
|
|
432
|
+
result = self.validate_zip_links({"link": "."}, [])
|
|
433
|
+
self.assertNotEqual(result.returncode, 0)
|
|
434
|
+
|
|
435
|
+
def test_rejects_duplicate_and_special_zip_members(self):
|
|
436
|
+
root = "bitcoin-roots-30.3-roots.1"
|
|
437
|
+
with tempfile.TemporaryDirectory() as temporary_dir:
|
|
438
|
+
for kind in ("duplicate", "fifo", "device"):
|
|
439
|
+
with self.subTest(kind=kind):
|
|
440
|
+
archive = Path(temporary_dir) / f"{kind}.zip"
|
|
441
|
+
with zipfile.ZipFile(archive, mode="w") as package:
|
|
442
|
+
package.writestr(f"{root}/bin/bitcoind", b"payload")
|
|
443
|
+
info = zipfile.ZipInfo(f"{root}/bin/bitcoind" if kind == "duplicate" else f"{root}/special")
|
|
444
|
+
info.create_system = 3
|
|
445
|
+
mode = {"duplicate": 0o100755, "fifo": 0o10755, "device": 0o20755}[kind]
|
|
446
|
+
info.external_attr = mode << 16
|
|
447
|
+
package.writestr(info, b"payload")
|
|
448
|
+
result = subprocess.run(
|
|
449
|
+
["python3", ARCHIVE_TOOL, "validate", archive, root],
|
|
450
|
+
capture_output=True, text=True,
|
|
451
|
+
)
|
|
452
|
+
self.assertNotEqual(result.returncode, 0)
|
|
453
|
+
self.assertIn("Duplicate archive member" if kind == "duplicate" else "Unsupported archive member", result.stderr)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
if __name__ == "__main__":
|
|
457
|
+
unittest.main()
|