Files
XZBT-NGN/server/serve.js
T
2026-09-14 07:57:18 -07:00

37 lines
2.3 KiB
JavaScript

import http from 'node:http';
import { readFile, realpath, stat } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const root = fileURLToPath(new URL('../', import.meta.url));
const mime = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8',
'.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.wav': 'audio/wav', '.mp3': 'audio/mpeg' };
export function createServer() {
return http.createServer(async (req, res) => {
try {
if (!['GET', 'HEAD'].includes(req.method)) { res.writeHead(405); res.end('Method not allowed'); return; }
const url = new URL(req.url, 'http://localhost');
const pathname = decodeURIComponent(url.pathname);
// Serve only the product surface and explicitly owned fixture subtree.
const route = pathname === '/' ? '/public/index.html' : pathname;
const mount = ['/public/', '/src/', '/test-fixtures/'].find(prefix => route.startsWith(prefix));
if (!mount || route.includes('\\') || route.includes('\0')) throw new Error('Not found');
const base = await realpath(path.join(root, mount));
let candidate = await realpath(path.join(root, route));
const relative = path.relative(base, candidate);
if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error('Not found');
if ((await stat(candidate)).isDirectory()) candidate = await realpath(path.join(candidate, 'index.html'));
const finalRelative = path.relative(base, candidate);
if (finalRelative.startsWith('..') || path.isAbsolute(finalRelative)) throw new Error('Not found');
const content = await readFile(candidate);
res.writeHead(200, { 'Content-Type': mime[path.extname(candidate)] ?? 'application/octet-stream',
'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff', 'Content-Length': content.length });
res.end(req.method === 'HEAD' ? undefined : content);
} catch { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not found'); }
});
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const port = Number(process.env.PORT ?? 4173);
createServer().listen(port, '127.0.0.1', () => console.log(`XZBT-NGN: http://127.0.0.1:${port}`));
}