Files
racket-chatgpt-bootstrap/rkt.php
T
www-data 475765e31f Move rendering into private templates
Add an explicit template renderer with HTML views and partials for the app, bootstrap, package, and catalog pages. Move shared reporting setup into config/reporting.php and relocate stylesheet assets under css/.
2026-05-26 12:50:26 +02:00

253 lines
6.5 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.
*/
require_once __DIR__ . '/config/reporting.php';
require_once __DIR__ . '/private/nexttoken.php';
require_once __DIR__ . '/private/base64config.php';
require_once __DIR__ . '/private/racketzip.php';
require_once __DIR__ . '/private/Template.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(
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);