1// Alchemy modifications are licensed under Apache-2.0.
2// This file includes third-party code; see /THIRD_PARTY_LICENSES.md.
3/**
4 * Vendored from `@astrojs/cloudflare` v14.1.3
5 * (`src/entrypoints/image-passthrough-endpoint.ts`).
6 *
7 * The production image endpoint for the `passthrough` image service: serves
8 * local images through the ASSETS binding and allowed remote images via fetch,
9 * without any transformation (workerd cannot run sharp).
10 */
11import { isRemotePath } from "@astrojs/internal-helpers/path";
12import { isRemoteAllowed } from "@astrojs/internal-helpers/remote";
13import type { APIRoute } from "astro";
14import { fetchWithRedirects } from "astro/assets";
15import { imageConfig } from "astro:assets";
16import { env } from "cloudflare:workers";
17
18export const prerender = false;
19
20export const GET: APIRoute = async ({ request }) => {
21 try {
22 const url = new URL(request.url);
23 const href = url.searchParams.get("href");
24 if (!href) return new Response("Bad Request", { status: 400 });
25
26 const isRemote = isRemotePath(href);
27
28 let response: Response;
29
30 if (isRemote) {
31 if (!isRemoteAllowed(href, imageConfig)) {
32 return new Response("Forbidden", { status: 403 });
33 }
34 response = await fetchWithRedirects({
35 url: href,
36 imageConfig,
37 });
38 } else {
39 const sourceUrl = new URL(href, url.origin);
40 if (sourceUrl.origin !== url.origin) {
41 return new Response("Forbidden", { status: 403 });
42 }
43 response = (await (env as unknown as Env).ASSETS.fetch(
44 new Request(sourceUrl, { headers: request.headers }),
45 )) as unknown as Response;
46 }
47
48 if (response.status >= 300 && response.status < 400) {
49 return new Response("Not Found", { status: 404 });
50 }
51
52 if (!response.ok) {
53 return new Response("Not Found", { status: 404 });
54 }
55
56 const contentType = response.headers.get("Content-Type") ?? "";
57 if (!contentType.startsWith("image/")) {
58 return new Response("Forbidden", { status: 403 });
59 }
60
61 const headers = new Headers();
62 headers.set("Content-Type", contentType);
63 headers.set("Cache-Control", "public, max-age=31536000");
64 headers.set("Date", new Date().toUTCString());
65 const etag = response.headers.get("ETag");
66 if (etag) headers.set("ETag", etag);
67
68 return new Response(response.body, { status: 200, headers });
69 } catch {
70 return new Response("Internal Server Error", { status: 500 });
71 }
72};