UNPKG

astro

Version:

Astro is a modern site builder with web best practices, performance, and DX front-of-mind.

84 lines (83 loc) • 3.18 kB
import { markFeatureUsed, FetchFeatures } from "../fetch/features.js"; import { getRouteGenerator } from "../routing/generator.js"; import { rejectInvalidEncoding } from "../routing/invalid-encoding.js"; function isExternalURL(url) { return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//"); } export function redirectIsExternal(redirect) { if (typeof redirect === "string") { return isExternalURL(redirect); } else { return isExternalURL(redirect.destination); } } /** * Computes the HTTP status code for a redirect response. * * - If the route has a `redirectRoute` and an explicit numeric status, that status is used. * - Otherwise: GET → 301, non-GET (e.g. POST) → 308. */ export function computeRedirectStatus(method, redirect, redirectRoute) { return redirectRoute && typeof redirect === "object" ? redirect.status : method === "GET" ? 301 : 308; } /** * Resolves the final redirect target URL by substituting dynamic params into * the redirect string (e.g. `/[slug]/page` → `/hello/page`). * * When `redirectRoute` is provided its route generator is used; otherwise params * are substituted manually into the string redirect target. */ export function resolveRedirectTarget(params, redirect, redirectRoute, trailingSlash) { if (typeof redirectRoute !== "undefined") { const generate = getRouteGenerator(redirectRoute.segments, trailingSlash); return generate(params) || redirectRoute?.pathname || "/"; } else if (typeof redirect === "string") { if (redirectIsExternal(redirect)) { return redirect; } else { let target = redirect; for (const param of Object.keys(params)) { const paramValue = params[param]; // Pass a replacer so `$`-prefixed sequences in the param value are inserted literally (#18010) target = target.replace(`[${param}]`, () => paramValue).replace(`[...${param}]`, () => paramValue); } return target; } } else if (typeof redirect === "undefined") { return "/"; } return redirect.destination; } export async function renderRedirect(state) { markFeatureUsed(state.manifest, FetchFeatures.redirects); const routeData = state.routeData; const { redirect, redirectRoute } = routeData; const status = computeRedirectStatus(state.request.method, redirect, redirectRoute); const headers = { location: encodeURI(resolveRedirectTarget(state.params, redirect, redirectRoute, state.manifest.trailingSlash)) }; if (redirect && redirectIsExternal(redirect)) { if (typeof redirect === "string") { return Response.redirect(redirect, status); } else { return Response.redirect(redirect.destination, status); } } return new Response(null, { status, headers }); } /** * Renders the redirect `Response` when the matched route is a redirect, or * returns `undefined` so the caller continues processing. Returns an empty * `400` when the request path is over-encoded. */ export function handleRedirects(state) { const invalidEncodingResponse = rejectInvalidEncoding(state); if (invalidEncodingResponse) { return Promise.resolve(invalidEncodingResponse); } if (state.routeData?.type === "redirect") { return renderRedirect(state); } return undefined; }