astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
63 lines (62 loc) • 2.36 kB
JavaScript
import { appendForwardSlash, collapseDuplicateTrailingSlashes, hasFileExtension, isInternalPath, removeTrailingForwardSlash } from "@astrojs/internal-helpers/path";
import { prepareResponse } from "../app/prepare-response.js";
import { redirectTemplate } from "./3xx.js";
import { rejectInvalidEncoding } from "./invalid-encoding.js";
/**
* Handles trailing-slash normalization for incoming requests. If the
* request's pathname does not match the manifest's configured
* `trailingSlash` policy, a redirect `Response` is returned. Otherwise,
* returns `undefined` so the caller can continue processing the request.
* Paths encoded too many times to decode get a 400 instead of a redirect.
*/
export function handleTrailingSlash(state) {
const invalidEncodingResponse = rejectInvalidEncoding(state);
if (invalidEncodingResponse) {
return invalidEncodingResponse;
}
// Use a fresh URL parse from the raw request so we see the
// un-normalized pathname (e.g. duplicate slashes like `///`).
// state.url has already been normalized by the FetchState
// constructor, which would hide the redirect targets.
const url = new URL(state.request.url);
const redirect = redirectTrailingSlash(state.manifest.trailingSlash, url.pathname);
// Not a redirect.
if (redirect === url.pathname) {
return undefined;
}
const addCookieHeader = state.renderOptions.addCookieHeader;
const status = state.request.method === "GET" ? 301 : 308;
const location = redirect + url.search;
const response = new Response(redirectTemplate({
status,
relativeLocation: location,
absoluteLocation: location,
from: state.request.url
}), {
status,
headers: { location }
});
prepareResponse(response, { addCookieHeader });
return response;
}
function redirectTrailingSlash(trailingSlash, pathname) {
// Ignore root and internal paths
if (pathname === "/" || isInternalPath(pathname)) {
return pathname;
}
// Redirect multiple trailing slashes to collapsed path
const path = collapseDuplicateTrailingSlashes(pathname, trailingSlash !== "never");
if (path !== pathname) {
return path;
}
if (trailingSlash === "ignore") {
return pathname;
}
if (trailingSlash === "always" && !hasFileExtension(pathname)) {
return appendForwardSlash(pathname);
}
if (trailingSlash === "never") {
return removeTrailingForwardSlash(pathname);
}
return pathname;
}