create(); function path_only() { $p = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH); $p = '/' . trim($p ?: '/', '/'); return $p === '/' ? '/' : $p; } function scheme() { if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { return strtolower(trim(explode(',', $_SERVER['HTTP_X_FORWARDED_PROTO'])[0])); } return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; } function host_name() { return $_SERVER['HTTP_HOST'] ?? 'localhost'; } function make_url($path, $query = array()) { global $NEXT_ID; $query['next'] = $NEXT_ID; return scheme() . '://' . host_name() . $path . '?' . http_build_query($query); } function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function html_response($html, $status = 200) { http_response_code($status); header('Content-Type: text/html; charset=utf-8'); header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Pragma: no-cache'); echo $html; exit; } function text_response($text, $status = 200) { http_response_code($status); header('Content-Type: text/plain; charset=us-ascii'); header('Content-Disposition: inline'); header('X-Content-Type-Options: nosniff'); header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Pragma: no-cache'); echo $text; if ($text === '' || substr($text, -1) !== "\n") { echo "\n"; } exit; } function fail_html($message, $status = 500) { html_response( RacketSandboxTemplate::renderFile('protocol-error.html', array( 'title' => 'Error', 'message' => $message, )), $status ); } function fail_text($message, $status = 500) { text_response("error: " . $message . "\n", $status); } function load_parts() { try { $manifest = load_racket_zip_parts_manifest(); } catch (Throwable $e) { fail_html($e->getMessage() . "\n\nSla de configuratiepagina op om racket.zip in parts te verdelen."); } if (!racket_zip_parts_current($manifest, RACKET_ZIP_MAX_BASE64_KB)) { fail_html( "racket.zip parts ontbreken of passen niet bij de huidige chunk-grootte.\n\n" . "Sla de configuratiepagina op om de parts opnieuw te maken." ); } $parts = array(); foreach (($manifest['parts'] ?? array()) as $part) { $number = (string)($part['number'] ?? ''); $parts[] = array( 'number' => $number, 'name' => (string)($part['name'] ?? racket_zip_part_name((int)$number)), 'size' => (int)($part['size'] ?? 0), 'url' => make_url('/bootstrap-racket-part', array('n' => $number)), ); } $manifest['parts'] = $parts; return $manifest; } function serve_bootstrap() { global $NEXT_ID; $manifest = load_parts(); $parts = $manifest['parts']; $rows = ''; foreach ($parts as $i => $part) { $rows .= RacketSandboxTemplate::renderFile('partials/racket-part-row.html', array( 'index' => (string)($i + 1), 'number' => $part['number'], 'size' => (string)$part['size'], 'url' => $part['url'], )) . "\n"; } $zipSize = (int)($manifest['source_bytes'] ?? 0); $pkg_url = make_url('/racket-pkg-index'); html_response(RacketSandboxTemplate::renderFile('bootstrap-racket.html', array( 'pkg_url' => $pkg_url, 'zip_size' => (string)$zipSize, 'max_base64_kb' => (string)RACKET_ZIP_MAX_BASE64_KB, 'max_base64_bytes' => (string)RACKET_ZIP_MAX_BASE64_BYTES, 'binary_chunk_bytes' => (string)RACKET_ZIP_BINARY_CHUNK_BYTES, 'part_count' => (string)count($parts), 'created_at' => (string)($manifest['created_at'] ?? ''), 'next_id' => $NEXT_ID, 'part_rows_html' => $rows, ))); } function serve_part() { $n = $_GET['n'] ?? ''; if (!is_string($n) || !preg_match('/^[0-9]{6}$/', $n)) { fail_text('ongeldig partnummer; gebruik bijvoorbeeld n=000001', 400); } $file = racket_zip_part_file($n); if (!is_file($file) || !is_readable($file)) { fail_text('part ontbreekt of is niet leesbaar: ' . $n, 404); } $bin = file_get_contents($file); if ($bin === false) { fail_text('kan part niet lezen: ' . $n, 500); } text_response(base64_encode($bin)); } $path = path_only(); if ($path === '/' || $path === '/index.php') { header('Location: ' . make_url('/bootstrap-racket'), true, 302); exit; } if ($path === '/bootstrap-racket') { $TOKENS->check_valid_next('html'); serve_bootstrap(); } if ($path === '/bootstrap-racket-part') { $TOKENS->check_valid_next('text'); serve_part(); } fail_html('Onbekende route: ' . $path, 404);