// Alchemy modifications are licensed under Apache-2.0. // This file includes third-party code; see /THIRD_PARTY_LICENSES.md. /** * CloudFront Function injection code ported from SST's Router component. * These are JavaScript code strings that get injected into CloudFront Function handlers. */ /** * Host-based 301 handling injected at the very top of a generated * viewer-request handler (before route matching). Redirects requests whose * `Host` is one of the configured redirect hostnames — and, when * `cloudfrontUrl: false`, requests arriving via the distribution's default * `*.cloudfront.net` domain (the default domain is not knowable inside its * own function config before the distribution exists, but only requests * served via the default domain ever carry a `.cloudfront.net` Host; * alternate domain names never do) — to the canonical hostname with path * and query preserved. * * Relies on `buildHostRedirectResponse` declared by * {@link CF_ROUTER_INJECTION} (function declarations hoist within the * handler). Returns an empty string when there is nothing to redirect. */ export const buildHostRedirectInjection = ({ to, hosts, cloudfrontDefault, }: { /** Canonical hostname redirected to. */ to: string | undefined; /** Exact redirect hostnames. */ hosts: string[]; /** Also redirect the distribution's default `*.cloudfront.net` domain. */ cloudfrontDefault: boolean; }): string => { if (!to || (hosts.length === 0 && !cloudfrontDefault)) return ""; const conditions = [ ...(hosts.length > 0 ? [`${JSON.stringify(hosts)}.indexOf(redirectHost) !== -1`] : []), ...(cloudfrontDefault ? [`redirectHost.endsWith(".cloudfront.net")`] : []), ]; return ` var redirectHost = event.request.headers.host.value; if (${conditions.join(" || ")}) { return buildHostRedirectResponse(${JSON.stringify(to)}); }`; }; const CLOUDFRONT_FUNCTION_SAFE_HEADER_LIMIT = 10240 - 512; export const CF_ROUTER_INJECTION = ` async function routeSite(kvNamespace, metadata) { if (metadata.redirect && metadata.redirect.hosts.indexOf(event.request.headers.host.value) !== -1) { return buildHostRedirectResponse(metadata.redirect.to); } var baselessUri = metadata.base ? event.request.uri.replace(metadata.base, "") : event.request.uri; try { var u = decodeURIComponent(baselessUri); var postfixes = u.endsWith("/") ? ["index.html"] : ["", ".html", "/index.html"]; var v = await Promise.any(postfixes.map(function(p) { return cf.kvs().get(kvNamespace + ":" + u + p).then(function() { return p; }); })); event.request.uri = metadata.s3.dir + event.request.uri + v; setS3Origin(metadata.s3.domain); return; } catch (e) {} if (metadata.s3 && metadata.s3.routes) { for (var i=0, l=metadata.s3.routes.length; i ${CLOUDFRONT_FUNCTION_SAFE_HEADER_LIMIT}; } function buildOversizedHeadersResponse() { return { statusCode: 431, statusDescription: "Request Header Fields Too Large", headers: { "cache-control": { value: "no-store" }, "content-type": { value: "text/plain; charset=utf-8" } }, body: { encoding: "text", data: "Request headers are too large. Reduce cookie size and try again." }, }; } function getRequestHeaderSize() { var size = 0; for (var key in event.request.headers) { var header = event.request.headers[key]; if (header.multiValue) { for (var i=0; i