| 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 | */ |
| 11 | import { isRemotePath } from "@astrojs/internal-helpers/path"; |
| 12 | import { isRemoteAllowed } from "@astrojs/internal-helpers/remote"; |
| 13 | import type { APIRoute } from "astro"; |
| 14 | import { fetchWithRedirects } from "astro/assets"; |
| 15 | import { imageConfig } from "astro:assets"; |
| 16 | import { env } from "cloudflare:workers"; |
| 17 | |
| 18 | export const prerender = false; |
| 19 | |
| 20 | export 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 | }; |