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. Before is on the left; after is on the right. Unified view fits narrow screens more easily.
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()
|