import { createServer } from 'node:http'; import { realpath as portableRealpath } from 'node:fs'; import { readFile, stat } from 'node:fs/promises'; import { extname, isAbsolute, relative, resolve, sep } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { promisify } from 'node:util'; // fs.promises.realpath uses the native API, which fails on some Windows SMB drives. // The portable implementation still resolves every symlink; never fall back to resolve(). const realpath = promisify(portableRealpath); const PUBLIC_ROOT = fileURLToPath(new URL('../htdocs/', import.meta.url)); const INCLUDES = new Map([ ['', 'common/header.html'], ['', 'common/topnav.html'], ['', 'common/footer.html'], ]); const MIME = new Map([ ['.html', 'text/html; charset=utf-8'], ['.htm', 'text/html; charset=utf-8'], ['.css', 'text/css; charset=utf-8'], ['.js', 'text/javascript; charset=utf-8'], ['.mjs', 'text/javascript; charset=utf-8'], ['.json', 'application/json; charset=utf-8'], ['.txt', 'text/plain; charset=utf-8'], ['.xml', 'application/xml; charset=utf-8'], ['.svg', 'image/svg+xml'], ['.ico', 'image/x-icon'], ['.png', 'image/png'], ['.jpg', 'image/jpeg'], ['.jpeg', 'image/jpeg'], ['.gif', 'image/gif'], ['.webp', 'image/webp'], ['.avif', 'image/avif'], ['.woff', 'font/woff'], ['.woff2', 'font/woff2'], ['.ttf', 'font/ttf'], ['.otf', 'font/otf'], ['.mp3', 'audio/mpeg'], ['.ogg', 'audio/ogg'], ['.wav', 'audio/wav'], ['.mp4', 'video/mp4'], ['.webm', 'video/webm'], ['.pdf', 'application/pdf'], ['.wasm', 'application/wasm'], ['.pub', 'text/plain; charset=utf-8'], ]); const PORTAL_NOTICE = ` Local preview only · Membership unavailable

Local preview only

The dynamic membership portal is unavailable in this static preview. Registration, sign-in, profiles, and billing require the deployed portal. This preview does not emulate authentication or accept payments.

Back to the homepage preview

`; function httpError(status) { return Object.assign(new Error('Preview request rejected'), { status }); } function safeSegments(path) { // Also reject Windows alternate streams, device names and normalization aliases. return !/[\\\x00-\x1f\x7f:<>"|?*#%]/u.test(path) && path.split('/').every(segment => !segment.startsWith('.') && !/[. ]$/u.test(segment) && !/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(segment)); } function requestPath(target) { // Inspect the raw target BEFORE URL parsing can silently normalize ../ or backslashes. if (typeof target !== 'string' || !target.startsWith('/') || target.startsWith('//')) { throw httpError(400); } const raw = target.split('?')[0]; if (/%(?:2f|5c)/iu.test(raw)) throw httpError(400); let decoded; try { decoded = decodeURIComponent(raw); } catch { throw httpError(400); } if (!safeSegments(decoded)) throw httpError(400); return decoded; } async function containedFile(root, path) { const candidate = await realpath(resolve(root, path)); const inside = relative(root, candidate); if (!inside || isAbsolute(inside) || inside === '..' || inside.startsWith(`..${sep}`) || !safeSegments(inside.split(sep).join('/'))) throw httpError(403); if (!(await stat(candidate)).isFile()) throw httpError(404); return candidate; } async function assemble(root) { let html = await readFile(await containedFile(root, 'index.html'), 'utf8'); // Literal allowlist only: no general SSI parsing, recursive includes, or execution. for (const [directive, path] of INCLUDES) { if (!html.includes(directive)) continue; const snippet = await readFile(await containedFile(root, path), 'utf8'); if (/