#!/usr/bin/env python3 """Stdlib-only prebuilt artifact inspection, document closure and tar verification. Never builds, downloads, launches an input executable, or overwrites an archive. """ import argparse import gzip import hashlib import html from html.parser import HTMLParser import json from pathlib import Path, PurePosixPath import re import stat import struct import subprocess import sys import tarfile from urllib.parse import unquote, urlsplit def digest(path): with Path(path).open('rb') as stream: return hashlib.file_digest(stream, 'sha256').hexdigest() def ordinary(path): path = Path(path).absolute() if not path.is_file(): raise ValueError(f'Expected ordinary file: {path}') reject_reparse(path) return path def reject_reparse(path): for candidate in (path, *path.parents): info = candidate.lstat() if stat.S_ISLNK(info.st_mode) or getattr(info, 'st_file_attributes', 0) & getattr(stat, 'FILE_ATTRIBUTE_REPARSE_POINT', 0): raise ValueError(f'Symlink/reparse ancestor not permitted in package input: {candidate}') def local_links(path): text = path.read_text(encoding='utf-8-sig') if path.suffix.lower() == '.md': # Commands/example syntax are not rendered Markdown links. text = re.sub(r'(?ms)^\s*(`{3,}|~{3,}).*?^\s*\1\s*$', '', text) text = re.sub(r'`[^`\n]*`', '', text) links = re.findall(r'!?\[[^\]\n]*\]\(\s*(<[^>]*>|[^\s)]+)(?:\s+[^)]*)?\)', text) links += re.findall(r'(?m)^\s*\[[^]\n]+\]:\s*(<[^>]*>|\S+)', text) elif path.suffix.lower() == '.rst': links = re.findall(r'`[^`\n]*<([^>]+)>`_', text) links += re.findall(r'(?m)^\.\.\s+_[^:\n]+:\s*(\S+)', text) elif path.suffix.lower() in ('.html', '.htm'): links = [] else: return [] class Links(HTMLParser): def handle_starttag(self, tag, attrs): if tag == 'base' and any(name == 'href' for name, _ in attrs): raise ValueError(f'HTML base URL requires explicit document-closure support: {path}') links.extend(value for name, value in attrs if name in ('href', 'src') and value) Links().feed(text) return [html.unescape(link.strip('<>')) for link in links] def linked_path(origin, link): parsed = urlsplit(link) if parsed.scheme or parsed.netloc or not parsed.path: return None decoded = unquote(parsed.path) if '\\' in decoded or decoded.startswith('/'): raise ValueError(f'Nonportable document link: {origin}: {link}') parts = [] for part in (PurePosixPath(origin).parent / decoded).parts: if part == '..': if not parts: raise ValueError(f'Document link escapes package: {origin}: {link}') parts.pop() elif part not in ('', '.'): parts.append(part) return '/'.join(parts) def document_inputs(repository, platform): repository = Path(repository).resolve() raw = subprocess.check_output(['git', '-C', str(repository), 'ls-files', '-z']) tracked = set(raw.decode('utf-8').split('\0')) - {''} roots = ['LICENSE', 'docs/server.md', 'docs/api.rst', 'docs/api.html', 'docs/language.rst', 'docs/language.html', f'docs/{platform}-packaging.md', 'deployment/Dockerfile', 'deployment/Dockerfile.dockerignore', 'deployment/arcadia-server.service'] roots += sorted(name for name in tracked if name.startswith('examples/')) # Two explicitly authored inputs can be packaged before their first commit. permitted = tracked | set(roots) | {'scripts/package-readme.md', 'docs/linux-packaging.md', 'docs/windows-packaging.md'} def repository_file(name): candidate = ordinary(repository / name) candidate.resolve().relative_to(repository) return candidate sources = {'README.md': repository_file('scripts/package-readme.md')} for name in roots: sources[name] = repository_file(name) queue = list(sources) while queue: origin = queue.pop() for link in local_links(sources[origin]): relative = linked_path(origin, link) if relative is None or relative in sources: continue if relative not in permitted: raise ValueError(f'Untracked/missing package document link: {origin}: {link}') candidate = repository_file(relative) sources[relative] = candidate queue.append(relative) return {'files': [{'path': name, 'source': str(source)} for name, source in sorted(sources.items())]} def check_documents(stage): stage = Path(stage).resolve() count = 0 for source in sorted(stage.rglob('*')): if not source.is_file() or source.suffix.lower() not in ('.md', '.rst', '.html', '.htm'): continue relative = source.relative_to(stage).as_posix() # Third-party/Rust source notices can contain their own source-tree links; # preserve those upstream texts verbatim, rather than rewriting licenses. if relative.startswith('licenses/'): continue for link in local_links(source): target = linked_path(relative, link) if target is not None: ordinary(stage / target) count += 1 return {'checked_local_document_links': count} def inspect_elf(path): path = ordinary(path) with path.open('rb') as stream: data = stream.read() if len(data) < 64 or data[:6] != b'\x7fELF\x02\x01': raise ValueError(f'Expected ELF64 little-endian input: {path}') kind, machine = struct.unpack_from(' len(data): raise ValueError('Invalid/bounded ELF program table') programs = [struct.unpack_from(' len(data): raise ValueError('ELF segment escapes file') def string(offset, end): if not 0 <= offset < end <= len(data): raise ValueError('Invalid ELF string range') stop = data.find(b'\0', offset, min(end, offset + 4096)) if stop < 0: raise ValueError('Unterminated/bounded ELF string') return data[offset:stop].decode('ascii') interpreter = None dynamic = [] for ptype, _, offset, _, _, size, _, _ in programs: if ptype == 3: interpreter = string(offset, offset + size) if ptype == 2: if size // 16 > 65536 or size % 16: raise ValueError('Invalid/bounded ELF dynamic table') for entry in range(offset, offset + size, 16): tag, value = struct.unpack_from('