/** * Static file serving for the web shell: the starter MIME table and the * request handler with the semantics locked by the step1 acceptance list — * traversal outside the dist root is 403, any miss falls back to index.html * with HTTP 200 (SPA routing), unknown extensions ship as octet-stream. */ import type { ServerResponse } from 'node:http' import { extname, join, normalize, resolve, sep } from 'node:path' import { readFile } from 'node:fs/promises' const MIME: Record = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.svg': 'image/svg+xml', '.json': 'application/json', '.map': 'application/json', } /** * Serve one GET/HEAD static request from the dist root. * @param pathname - decoded URL pathname of the request. * @param res - the node:http response to write. * @param distRoot - absolute dist root directory (resolved by the caller). * @param distIndex - absolute path of index.html inside distRoot. * @param renderIndex - when set, produces the index.html body (boot-manifest * injection) for `/` and every SPA fallback; undefined serves the file verbatim. */ export async function serveStatic( pathname: string, res: ServerResponse, distRoot: string, distIndex: string, renderIndex?: () => Promise, ): Promise { const target = resolve(normalize(join(distRoot, pathname))) // Traversal rejection: the target must be distRoot itself (`/`) or stay under // it. `sep`, not '/': resolve() emits backslash paths on Windows, where a '/' // suffix would reject every legitimate subpath as traversal. if (target !== distRoot && !target.startsWith(distRoot + sep)) { res.writeHead(403) res.end() return } const serveIndex = async (): Promise => { const body = renderIndex === undefined ? await readFile(distIndex) : await renderIndex() res.writeHead(200, { 'content-type': MIME['.html'] }) res.end(body) } if (target === distRoot || target === distIndex) { await serveIndex() return } try { const body = await readFile(target) res.writeHead(200, { 'content-type': MIME[extname(target)] ?? 'application/octet-stream' }) res.end(body) } catch { // Miss (ENOENT/EISDIR) falls back to index.html with 200 (SPA routing). await serveIndex() } }