Files
racket-chatgpt-bootstrap/rkt.php
T
2026-05-25 13:47:46 +02:00

313 lines
7.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);