import fs from "node:fs"; import path from "node:path"; import { hasFileExtension, isInternalPath } from "@astrojs/internal-helpers/path"; import send from "send"; import { resolveClientDir } from "./shared.js"; import { createRequest } from "astro/app/node"; function resolveStaticPath(client, urlPath) { const filePath = path.join(client, urlPath); const resolved = path.resolve(filePath); const resolvedClient = path.resolve(client); if (resolved !== resolvedClient && !resolved.startsWith(resolvedClient + path.sep)) { return { filePath: resolved, isDirectory: false }; } let isDirectory = false; try { isDirectory = fs.lstatSync(filePath).isDirectory(); } catch { } return { filePath: resolved, isDirectory }; } function createStaticHandler(app, options, headersMap) { const client = resolveClientDir(options); return (req, res, ssr) => { if (req.url) { let fullUrl = req.url; if (req.url.includes("#")) { fullUrl = fullUrl.slice(0, req.url.indexOf("#")); } const [urlPath, urlQuery] = fullUrl.split("?"); const { isDirectory } = resolveStaticPath(client, app.removeBase(urlPath)); const hasSlash = urlPath.endsWith("/"); let pathname = urlPath; if (headersMap && headersMap.length > 0) { const request = createRequest(req, { allowedDomains: app.getAllowedDomains?.() ?? [], port: options.port }); const routeData = app.match(request, true); if (routeData && routeData.prerender) { const baselessPathname = prependForwardSlash(app.removeBase(urlPath)); const matchedRoute = headersMap.find( (header) => header.pathname.includes(baselessPathname) ); if (matchedRoute) { for (const header of matchedRoute.headers) { res.setHeader(header.key, header.value); } } } } switch (app.manifest.trailingSlash) { case "never": { if (isDirectory && urlPath !== "/" && hasSlash) { pathname = urlPath.slice(0, -1) + (urlQuery ? "?" + urlQuery : ""); res.statusCode = 301; res.setHeader("Location", pathname); return res.end(); } if (isDirectory && !hasSlash) { pathname = `${urlPath}/index.html`; } break; } case "ignore": { if (isDirectory && !hasSlash) { pathname = `${urlPath}/index.html`; } break; } case "always": { if (!hasSlash && !hasFileExtension(urlPath) && !isInternalPath(urlPath)) { pathname = urlPath + "/" + (urlQuery ? "?" + urlQuery : ""); res.statusCode = 301; res.setHeader("Location", pathname); return res.end(); } break; } } pathname = prependForwardSlash(app.removeBase(pathname)); const normalizedPathname = path.posix.normalize(pathname); const stream = send(req, normalizedPathname, { root: client, dotfiles: normalizedPathname.startsWith("/.well-known/") ? "allow" : "deny" }); let forwardError = false; stream.on("error", (err) => { if (forwardError) { const status = "statusCode" in err ? err.statusCode : 500; if (status >= 500) { console.error(err.toString()); } res.writeHead(status); res.end(status >= 500 ? "Internal server error" : ""); return; } ssr(); }); stream.on("file", () => { forwardError = true; }); stream.on("stream", () => { if (normalizedPathname.startsWith(`/${app.manifest.assetsDir}/`)) { res.setHeader("Cache-Control", "public, max-age=31536000, immutable"); } }); stream.pipe(res); } else { ssr(); } }; } function prependForwardSlash(pth) { return pth.startsWith("/") ? pth : "/" + pth; } export { createStaticHandler, resolveStaticPath };