initial import

This commit is contained in:
www-data
2026-05-25 13:47:46 +02:00
parent 3e7f238cf4
commit 97f23260ed
32 changed files with 8898 additions and 0 deletions
+312
View File
@@ -0,0 +1,312 @@
<?php
/*
* Simpele Racket bootstrap.
*
* .htaccess:
*
* FallbackResource /index.php
*
* Bestanden:
*
* config/racket.zip
* data/ bevat vooraf gemaakte parts
*
* Routes:
*
* /bootstrap-racket?next=...
* Leest de vooraf gemaakte parts in data/ en toont een HTML-index
* met links.
*
* /bootstrap-racket-part?n=000001&next=...
* Geeft text/plain met base64 van precies één binair part.
*
* Regels:
*
* - Indexen/lijsten zijn HTML.
* - Payload/downloads zijn text/plain.
* - Payload/downloads zijn altijd base64.
* - Geen .bin, .zip of bestandsnaam in download-URLs.
* - next=... is alleen cache-busting.
* - Per gegenereerde HTML-pagina wordt één next-id gemaakt en voor alle
* links op die pagina gebruikt.
*/
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
ini_set('log_errors', '1');
error_reporting(E_ALL);
require_once __DIR__ . '/nexttoken.php';
require_once __DIR__ . '/base64config.php';
require_once __DIR__ . '/racketzip.php';
$TOKENS = new NextTokenStore(__DIR__ . '/data/racket-sandbox.sqlite');
@set_time_limit(300);
ignore_user_abort(false);
$chunkConfig = load_base64_chunk_config();
$racketZipMaxBase64Kb = (int)($chunkConfig['racket_zip_max_base64_kb'] ?? 8192);
if ($racketZipMaxBase64Kb < 1) {
$racketZipMaxBase64Kb = 1;
}
define('RACKET_ZIP_MAX_BASE64_KB', $racketZipMaxBase64Kb);
define('RACKET_ZIP_MAX_BASE64_BYTES', RACKET_ZIP_MAX_BASE64_KB * 1024);
define('RACKET_ZIP_BINARY_CHUNK_BYTES', racket_zip_binary_chunk_bytes_for_base64_kib(RACKET_ZIP_MAX_BASE64_KB));
if (RACKET_ZIP_BINARY_CHUNK_BYTES < 3) {
fail_html('Ongeldige racket.zip base64 chunk-instelling.');
}
/*
* Eén next-id voor alle URLs die deze request genereert.
*/
$NEXT_ID = $TOKENS->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(
'<!doctype html>' .
'<html><head><meta charset="utf-8"><title>Error</title></head>' .
'<body><h1>Error</h1><pre>' . h($message) . '</pre></body></html>',
$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 .=
'<tr>' .
'<td>' . h((string)($i + 1)) . '</td>' .
'<td>' . h($part['number']) . '</td>' .
'<td>' . h((string)$part['size']) . '</td>' .
'<td><a href="' . h($part['url']) . '">' . h($part['url']) . '</a></td>' .
'</tr>' . "\n";
}
$zipSize = (int)($manifest['source_bytes'] ?? 0);
$pkg_url = make_url('/racket-pkg-index');
html_response('<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8">
<title>Racket bootstrap</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body class="simple-doc">
<h1>Racket bootstrap</h1>
<p>
<code>racket.zip</code> is vooraf via de configuratiepagina gesplitst naar
parts in de map <code>data</code>.
</p>
<p>
Package index: <a href="' . $pkg_url . '"> Racket package index</a>
</p>
<p>
Bronbestand: <code>config/racket.zip</code><br>
Bronbestand bytes: <code>' . h((string)$zipSize) . '</code><br>
Maximale base64 part-grootte: <code>' . h((string)RACKET_ZIP_MAX_BASE64_KB) . '</code> KiB (<code>' . h((string)RACKET_ZIP_MAX_BASE64_BYTES) . '</code> bytes)<br>
Binaire chunk-grootte: <code>' . h((string)RACKET_ZIP_BINARY_CHUNK_BYTES) . '</code> bytes<br>
Aantal parts: <code>' . h((string)count($parts)) . '</code><br>
Parts gemaakt op: <code>' . h((string)($manifest['created_at'] ?? '')) . '</code><br>
next-id voor alle links op deze pagina: <code>' . h($NEXT_ID) . '</code>
</p>
<p>
Elke link hieronder geeft <strong>text/plain</strong> met de
<strong>base64-representatie van één binair part</strong>.
De URL bevat alleen een nummer, geen bestandsnaam en geen extensie.
</p>
<table>
<thead>
<tr>
<th>#</th>
<th>partnummer</th>
<th>bytes</th>
<th>base64 text/plain URL</th>
</tr>
</thead>
<tbody>
' . $rows . '
</tbody>
</table>
<h2>Reconstructie in de sandbox</h2>
<p>
Decodeer ieder base64-part afzonderlijk naar een binair part. Plak daarna de
binaire parts in numerieke volgorde aan elkaar.
</p>
<pre>
base64 -d part-000001.txt &gt; part-000001
base64 -d part-000002.txt &gt; part-000002
base64 -d part-000003.txt &gt; part-000003
# enzovoort
cat part-* &gt; racket.zip
unzip racket.zip -d /tmp/racket
</pre>
</body>
</html>');
}
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);