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