Files

120 lines
5.4 KiB
Python

#!/usr/bin/env python3
"""Verify, cache and attach the exact CEF runtime required by a release."""
import hashlib
import importlib.util
import json
import os
import pathlib
import re
import shutil
import subprocess
import sys
import tempfile
def verify(root, checksums):
for line in checksums.splitlines():
match = re.fullmatch(r'([a-f0-9]{64}) (.+)', line)
if not match:
raise ValueError('Invalid CEF checksums')
path = pathlib.PurePosixPath(match[2])
if path.is_absolute() or '..' in path.parts or '\\' in match[2] or not (
match[2].startswith('lib/cef/') or match[2] in ('share/licenses/cef.txt', 'share/licenses/cef-credits.html')):
raise ValueError('Invalid CEF payload path')
with (root / path).open('rb') as source:
if hashlib.file_digest(source, 'sha256').hexdigest() != match[1]:
raise ValueError('CEF checksum mismatch: ' + match[2])
if not any(line.endswith(' lib/cef/libcef.so') for line in checksums.splitlines()):
raise ValueError('CEF library checksum is missing')
def prepare(base, store):
base, store = pathlib.Path(base), pathlib.Path(store)
requirement = json.loads((base / 'CEF_RUNTIME.json').read_text())
runtime_id = requirement['id']
if requirement['schemaVersion'] != 1 or not re.fullmatch(r'[a-zA-Z0-9.+-]+', runtime_id) or '..' in runtime_id:
raise ValueError('Invalid CEF runtime identity')
expected = (base / 'CEF_SHA256SUMS').read_text()
parent = store / 'cef'
if parent.is_symlink():
raise ValueError('CEF storage must not be a symlink')
parent.mkdir(mode=0o755, exist_ok=True)
cached = parent / runtime_id
if cached.exists() or cached.is_symlink():
if cached.is_symlink() or not cached.is_dir():
raise ValueError('Invalid CEF cache directory')
verify(cached, expected)
return cached
with tempfile.TemporaryDirectory(prefix='.install-', dir=parent) as work:
work = pathlib.Path(work)
payload = work / 'payload'
# Older full installations can seed the cache without a network request.
for candidate in (base, store / 'state/current'):
try:
verify(candidate, expected)
except (OSError, ValueError):
continue
payload.mkdir()
for line in expected.splitlines():
relative = line[66:]
target = payload / relative
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(candidate / relative, target)
target.chmod(0o755 if relative.startswith('lib/cef/') and (candidate / relative).stat().st_mode & 0o111 else 0o644)
break
else:
spec = importlib.util.spec_from_file_location('bootstrap', base / 'tools/bootstrap.py')
engine = importlib.util.module_from_spec(spec)
spec.loader.exec_module(engine)
archive_name = f'framely-cef-{runtime_id}-linux-arm64.tar.gz'
if requirement['archive'] != archive_name:
raise ValueError('Invalid CEF archive name')
archive = work / archive_name
print('Downloading CEF ' + requirement['version'], file=sys.stderr, flush=True)
def progress(completed):
if os.environ.get('FRAMELY_PROGRESS_JSON') == '1':
print('FRAMELY_PROGRESS ' + json.dumps(dict(step='cef-download', completed=completed, total=requirement['size'])), file=sys.stderr, flush=True)
engine.download(requirement['url'], archive, engine.MAX_ARCHIVE, progress, expected_size=requirement['size'])
if archive.stat().st_size != requirement['size']:
raise ValueError('CEF archive length mismatch')
with archive.open('rb') as source:
if hashlib.file_digest(source, 'sha256').hexdigest() != requirement['sha256']:
raise ValueError('CEF archive SHA256 mismatch')
# The same link-free extraction policy protects standalone runtimes.
payload = engine.extract_archive(archive, work / 'unpacked', runtime=True)
if (payload / 'VERSION').read_text().strip() != runtime_id:
raise ValueError('CEF archive version mismatch')
subprocess.run(['sha256sum', '--quiet', '-c', 'SHA256SUMS'], cwd=payload, check=True)
verify(payload, expected)
for directory, _, _ in os.walk(payload):
pathlib.Path(directory).chmod(0o755)
payload.rename(cached)
return cached
def attach(release, cached):
release, cached = pathlib.Path(release), pathlib.Path(cached)
for folder in ('lib/cef', 'share/licenses'):
target_dir = release / folder
target_dir.mkdir(parents=True, exist_ok=True)
for source in (cached / folder).iterdir():
target = target_dir / source.name
if target.is_symlink() or target.is_file():
target.unlink()
elif target.is_dir():
shutil.rmtree(target)
target.symlink_to(source)
if __name__ == '__main__':
try:
if sys.argv[1] == 'prepare':
print(prepare(*sys.argv[2:]))
elif sys.argv[1] == 'attach':
attach(*sys.argv[2:])
else:
raise ValueError('Invalid CEF operation')
except (ValueError, OSError, subprocess.CalledProcessError) as error:
print('Framely CEF: ' + str(error), file=sys.stderr)
sys.exit(1)