astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
58 lines (57 loc) • 3.31 kB
JavaScript
import { appendForwardSlash, joinPaths, prependForwardSlash, removeTrailingForwardSlash, stripRequestBase } from "@astrojs/internal-helpers/path";
import { normalizeTheLocale } from "../../i18n/path.js";
import { getFirstForwardedValue, validateForwardedHeaders, validateHost } from "../app/validate-headers.js";
/**
* For domain-based i18n routing strategies, derives the locale-prefixed
* pathname from the request's `Host` header rather than its URL. For example,
* a request for `/foo` served from `https://example.fr` resolves to `/fr/foo`.
*
* Returns `undefined` when the strategy isn't domain-based or the host isn't
* mapped to a locale — in which case normal pathname routing applies.
*
*/
export function computePathnameFromDomain(request, url, i18n, base, trailingSlash, allowedDomains, logger, pathnameFromRequest) {
let pathname = undefined;
if (i18n && (i18n.strategy === "domains-prefix-always" || i18n.strategy === "domains-prefix-other-locales" || i18n.strategy === "domains-prefix-always-no-redirect")) {
const validated = validateForwardedHeaders(getFirstForwardedValue(request.headers.get("X-Forwarded-Proto") ?? undefined), getFirstForwardedValue(request.headers.get("X-Forwarded-Host") ?? undefined), getFirstForwardedValue(request.headers.get("X-Forwarded-Port") ?? undefined), allowedDomains);
const protocol = validated.protocol ? `${validated.protocol}:` : url.protocol;
const requestHost = request.headers.get("Host") ?? undefined;
const validatedRequestHost = allowedDomains?.length ? validateHost(requestHost, protocol.slice(0, -1), allowedDomains) : requestHost;
// Forwarded and original hosts are validated against security.allowedDomains when configured.
let host = validated.host ?? validatedRequestHost;
// If we don't have a host and a protocol, it's impossible to proceed
if (host && protocol) {
// The header might have a port in their name, so we remove it
host = host.split(":")[0];
try {
let locale;
const hostAsUrl = new URL(`${protocol}//${host}`);
for (const [domainKey, localeValue] of Object.entries(i18n.domainLookupTable)) {
// This operation should be safe because we force the protocol via zod inside the configuration
// If not, then it means that the manifest was tampered
const domainKeyAsUrl = new URL(domainKey);
if (hostAsUrl.host === domainKeyAsUrl.host && hostAsUrl.protocol === domainKeyAsUrl.protocol) {
locale = localeValue;
break;
}
}
if (locale) {
const requestPathname = pathnameFromRequest ?? stripRequestBase(url.pathname, base);
pathname = prependForwardSlash(joinPaths(normalizeTheLocale(locale), requestPathname));
if (trailingSlash === "always") {
pathname = appendForwardSlash(pathname);
} else if (trailingSlash === "never") {
pathname = removeTrailingForwardSlash(pathname);
} else if (url.pathname.endsWith("/")) {
// trailingSlash === 'ignore': preserve the original trailing slash
pathname = appendForwardSlash(pathname);
}
}
} catch (e) {
logger.error("router", `Astro tried to parse ${protocol}//${host} as an URL, but it threw a parsing error. Check the X-Forwarded-Host and X-Forwarded-Proto headers.`);
logger.error("router", `Error: ${e}`);
}
}
}
return pathname;
}