import { existsSync } from "node:fs"; import { readFile, stat } from "node:fs/promises"; import { extname, join, relative, resolve, sep } from "node:path"; import { isSafeRequestPath } from "@wrnexus/core"; import type { Mode } from "@wrnexus/core"; const CONTENT_TYPES: Record = { ".avif": "image/avif", ".css": "text/css; charset=utf-8", ".gif": "image/gif", ".html": "text/html; charset=utf-8", ".ico": "image/x-icon", ".jpeg": "image/jpeg", ".jpg": "image/jpeg", ".js": "text/javascript; charset=utf-8", ".json": "application/json; charset=utf-8", ".mjs": "text/javascript; charset=utf-8", ".pdf": "application/pdf", ".png": "image/png", ".svg": "image/svg+xml", ".txt": "text/plain; charset=utf-8", ".wasm": "application/wasm", ".webp": "image/webp", ".woff": "font/woff", ".woff2": "font/woff2", ".xml": "application/xml; charset=utf-8", }; const IMMUTABLE_CACHE = "public, max-age=31536000, immutable"; const REVALIDATE_CACHE = "public, max-age=0, must-revalidate"; function cachePolicy(filePath: string, mode: Mode): string { if (mode !== "production") return "no-cache"; const name = filePath.split(/[\\/]/).pop() ?? ""; if (/\.html?$/i.test(name)) return REVALIDATE_CACHE; // Font URLs are content assets with expensive transfer costs and are almost // always renamed when changed. Cache them like fingerprinted build assets. if (/\.(?:woff2?|ttf|otf|eot)$/i.test(name)) return IMMUTABLE_CACHE; return /(?:^|[.-])[a-f0-9]{8,}(?:[.-]|$)/i.test(name) ? IMMUTABLE_CACHE : "public, max-age=3600"; } /** Cache the public-dir existence check so it isn't a sync stat on every request. */ const publicDirExistsCache = new Map(); function publicDirExists(dir: string): boolean { let exists = publicDirExistsCache.get(dir); if (exists === undefined) { exists = existsSync(dir); publicDirExistsCache.set(dir, exists); } return exists; } const DEFAULT_FAVICON = ` `; export async function servePublicAsset( publicDir: string | undefined, pathname: string, mode: Mode, ): Promise { if (pathname === "/" || pathname.startsWith("/__wrnexus/")) return null; if (!isSafeRequestPath(pathname)) return null; const fallback = pathname === "/favicon.ico" ? defaultFaviconResponse(mode) : null; if (!publicDir || !publicDirExists(publicDir)) return fallback; const rel = safePublicRelativePath(pathname); if (!rel) return fallback; const base = resolve(publicDir); let filePath = resolve(base, rel); if (!isInside(base, filePath)) return fallback; try { const info = await stat(filePath); if (info.isDirectory()) { filePath = resolve(filePath, "index.html"); if (!isInside(base, filePath)) return null; } const fileInfo = await stat(filePath); if (!fileInfo.isFile()) return null; const body = await readFile(filePath); const contentType = CONTENT_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream"; return new Response(body, { headers: { "content-type": contentType, "cache-control": cachePolicy(filePath, mode), "x-content-type-options": "nosniff", }, }); } catch { return fallback; } } function defaultFaviconResponse(mode: Mode): Response { return new Response(DEFAULT_FAVICON, { headers: { "content-type": "image/svg+xml; charset=utf-8", "cache-control": mode === "production" ? "public, max-age=86400" : "no-cache", "x-content-type-options": "nosniff", }, }); } function safePublicRelativePath(pathname: string): string | null { let decoded: string; try { decoded = decodeURIComponent(pathname); } catch { return null; } const segments = decoded.split("/").filter(Boolean); if (segments.some((segment) => segment.startsWith(".") || segment.includes("\\"))) { return null; } return segments.length ? join(...segments) : null; } function isInside(base: string, target: string): boolean { const rel = relative(base, target); return rel === "" || (!!rel && !rel.startsWith("..") && !rel.includes(`..${sep}`)); }