UNPKG

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
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; }