generated from Labyricorn/labyricorn-project-template
37 lines
2.3 KiB
JavaScript
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}`));
|
|
}
|