BetterFrame/server/src/plugins/service-admin-http/routes-static.ts
2026-05-10 01:09:13 +02:00

54 lines
1.5 KiB
TypeScript

/**
* Static file serving for /static/* paths.
*
* In production the Angie proxy serves these directly;
* this handler is for dev mode only.
*/
import { existsSync, readFileSync } from "node:fs";
import { join, extname, resolve } from "node:path";
import { type H3, getRouterParam, createError } from "h3";
const STATIC_DIR = resolve(
import.meta.dirname ?? ".",
"../../web-static",
);
const MIME_TYPES: Record<string, string> = {
".html": "text/html; charset=utf-8",
".css": "text/css; charset=utf-8",
".js": "application/javascript; charset=utf-8",
".mjs": "application/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".svg": "image/svg+xml",
".png": "image/png",
".ico": "image/x-icon",
".woff2": "font/woff2",
};
export function registerStaticRoutes(app: H3): void {
app.get("/static/**:path", (event) => {
const reqPath = getRouterParam(event, "path");
if (!reqPath) throw createError({ statusCode: 404 });
// Prevent directory traversal
const resolved = resolve(STATIC_DIR, reqPath);
if (!resolved.startsWith(STATIC_DIR)) {
throw createError({ statusCode: 403 });
}
if (!existsSync(resolved)) {
throw createError({ statusCode: 404 });
}
const ext = extname(resolved).toLowerCase();
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
const body = readFileSync(resolved);
return new Response(body, {
headers: {
"content-type": contentType,
"cache-control": "public, max-age=86400",
},
});
});
}