Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
55 lines
2.5 KiB
Python
55 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Fetch three pinned historical packages and extract byte-identical test copies."""
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import stat
|
|
import tempfile
|
|
import urllib.request
|
|
import zipfile
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
CATALOG = json.loads((ROOT / "tests/legacy-apps.json").read_text())
|
|
BASE = ROOT / "downloads/legacy-apps"
|
|
BASE.mkdir(parents=True, exist_ok=True)
|
|
(BASE / "apps").mkdir(exist_ok=True)
|
|
for app in CATALOG:
|
|
archive = BASE / app["archive"]
|
|
if not archive.exists():
|
|
with urllib.request.urlopen(app["url"], timeout=30) as response:
|
|
data = response.read(10 * 1024 * 1024 + 1)
|
|
if len(data) > 10 * 1024 * 1024:
|
|
raise RuntimeError("Archive too large")
|
|
if hashlib.sha256(data).hexdigest() != app["sha256"]:
|
|
raise RuntimeError("Archive checksum mismatch: " + app["id"])
|
|
archive.write_bytes(data)
|
|
if hashlib.sha256(archive.read_bytes()).hexdigest() != app["sha256"]:
|
|
raise RuntimeError("Archive checksum mismatch: " + app["id"])
|
|
destination = BASE / "apps" / app["id"]
|
|
with zipfile.ZipFile(archive) as package:
|
|
if sum(info.file_size for info in package.infolist()) > 50 * 1024 * 1024:
|
|
raise RuntimeError("Extracted package too large")
|
|
manifest = json.loads(package.read("manifest.webapp"))
|
|
if manifest.get("type", "web") not in ("web", "privileged"):
|
|
raise RuntimeError("Only web and reviewed privileged packages are allowed")
|
|
for info in package.infolist():
|
|
name = Path(info.filename)
|
|
if (
|
|
name.is_absolute()
|
|
or ".." in name.parts
|
|
or "\\" in info.filename
|
|
or stat.S_ISLNK(info.external_attr >> 16)
|
|
):
|
|
raise RuntimeError("Unsafe archive entry: " + info.filename)
|
|
if not destination.exists():
|
|
with tempfile.TemporaryDirectory(dir=BASE / "apps") as temporary:
|
|
stage = Path(temporary) / "package"
|
|
stage.mkdir()
|
|
package.extractall(stage)
|
|
stage.rename(destination)
|
|
for info in package.infolist():
|
|
if not info.is_dir() and (destination / info.filename).read_bytes() != package.read(
|
|
info
|
|
):
|
|
raise RuntimeError("Extracted app was modified: " + app["id"] + "/" + info.filename)
|
|
print(app["id"] + ": archive SHA256 and extracted files verified", flush=True)
|