90 lines
3.0 KiB
JavaScript
90 lines
3.0 KiB
JavaScript
import { createServer } from "node:http";
|
|
import { readFile, stat } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = path.resolve(
|
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
"..",
|
|
"dist",
|
|
);
|
|
const nestedPrefix = "/deep/nested/regex/";
|
|
const mediaTypes = new Map([
|
|
[".css", "text/css; charset=utf-8"],
|
|
[".html", "text/html; charset=utf-8"],
|
|
[".js", "text/javascript; charset=utf-8"],
|
|
[".mjs", "text/javascript; charset=utf-8"],
|
|
[".json", "application/json; charset=utf-8"],
|
|
[".svg", "image/svg+xml"],
|
|
[".wasm", "application/wasm"],
|
|
]);
|
|
const testCatalogue = {
|
|
schemaVersion: 1,
|
|
id: "de.add-ideas.regex-tools.browser-test",
|
|
name: "Regex Tools browser-test Toolbox",
|
|
home: "./",
|
|
theme: { mode: "system", brand: "add·ideas" },
|
|
apps: [{ manifest: "./toolbox-app.json", enabled: true }],
|
|
};
|
|
const productionSecurityHeaders = {
|
|
"Content-Security-Policy":
|
|
"default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self' data:; connect-src 'self'; worker-src 'self' blob:; manifest-src 'self'",
|
|
"Cross-Origin-Embedder-Policy": "require-corp",
|
|
"Cross-Origin-Opener-Policy": "same-origin",
|
|
"Cross-Origin-Resource-Policy": "same-origin",
|
|
"X-Content-Type-Options": "nosniff",
|
|
};
|
|
|
|
function safeFile(requestPath) {
|
|
const decoded = decodeURIComponent(requestPath);
|
|
const relative = decoded.startsWith(nestedPrefix)
|
|
? decoded.slice(nestedPrefix.length)
|
|
: decoded.replace(/^\/+/u, "");
|
|
const normalized = path.posix.normalize(relative || "index.html");
|
|
if (
|
|
normalized === ".." ||
|
|
normalized.startsWith("../") ||
|
|
path.isAbsolute(normalized)
|
|
) {
|
|
return null;
|
|
}
|
|
return path.join(root, normalized);
|
|
}
|
|
|
|
const server = createServer(async (request, response) => {
|
|
try {
|
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
if (url.pathname === "/toolbox.catalog.json") {
|
|
response.writeHead(200, {
|
|
"Content-Type": "application/json; charset=utf-8",
|
|
"Cache-Control": "no-cache",
|
|
...productionSecurityHeaders,
|
|
});
|
|
response.end(JSON.stringify(testCatalogue));
|
|
return;
|
|
}
|
|
let file = safeFile(url.pathname);
|
|
if (!file) {
|
|
response.writeHead(400).end("Bad request");
|
|
return;
|
|
}
|
|
const details = await stat(file).catch(() => null);
|
|
if (details?.isDirectory()) file = path.join(file, "index.html");
|
|
const content = await readFile(file);
|
|
response.writeHead(200, {
|
|
"Content-Type":
|
|
mediaTypes.get(path.extname(file)) ?? "application/octet-stream",
|
|
"Cache-Control": "no-cache",
|
|
...productionSecurityHeaders,
|
|
});
|
|
response.end(content);
|
|
} catch {
|
|
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
response.end("Not found");
|
|
}
|
|
});
|
|
|
|
server.listen(4173, "127.0.0.1", () => {
|
|
console.log("Test static server listening on http://127.0.0.1:4173");
|
|
});
|