{"version":3,"sources":["../../src/image-sharp.ts"],"sourcesContent":["import type { ResolvedFarmImageConfig } from \"./image-config\";\nimport {\n  FarmImageRequestError,\n  isPrivateImageAddress,\n  selectOutputFormat,\n  type FarmImageTransformer,\n} from \"./image-server\";\n\ntype NodeImageDnsLookup = (\n  hostname: string,\n  options: { all: true; verbatim: true },\n  callback: (\n    error: NodeJS.ErrnoException | null,\n    addresses: Array<{ address: string; family: number }>,\n  ) => void,\n) => void;\n\nexport function createSharpImageTransformer(): FarmImageTransformer {\n  return async ({ source, sourceType, width, quality, accept, formats, signal }) => {\n    // Sharp is an optional native runtime. Load it only when an image request\n    // actually needs a transform so disabled/unused image pipelines do not add\n    // a startup dependency or native-module initialization cost.\n    const { default: sharp } = await import(\"sharp\");\n    throwIfAborted(signal);\n    const outputFormat = selectOutputFormat(accept, formats);\n    let pipeline = sharp(source, {\n      animated: sourceType === \"image/gif\" || sourceType === \"image/webp\",\n      failOn: \"warning\",\n      limitInputPixels: 268_402_689,\n    })\n      .rotate()\n      .resize({ width, fit: \"inside\", withoutEnlargement: true });\n\n    // When the Accept header matches none of the configured formats, keep the\n    // source's own format (svg rasterizes to png) instead of forcing JPEG —\n    // that preserved neither transparency nor the truth of the content type.\n    let encodedType: string;\n    if (outputFormat === \"image/avif\") {\n      pipeline = pipeline.avif({ quality });\n      encodedType = \"image/avif\";\n    } else if (outputFormat === \"image/webp\") {\n      pipeline = pipeline.webp({ quality });\n      encodedType = \"image/webp\";\n    } else if (sourceType === \"image/png\" || sourceType === \"image/svg+xml\") {\n      pipeline = pipeline.png();\n      encodedType = \"image/png\";\n    } else if (sourceType === \"image/gif\") {\n      pipeline = pipeline.gif();\n      encodedType = \"image/gif\";\n    } else if (sourceType === \"image/webp\") {\n      pipeline = pipeline.webp({ quality });\n      encodedType = \"image/webp\";\n    } else if (sourceType === \"image/avif\") {\n      pipeline = pipeline.avif({ quality });\n      encodedType = \"image/avif\";\n    } else {\n      pipeline = pipeline.jpeg({ quality });\n      encodedType = \"image/jpeg\";\n    }\n\n    const body = await pipeline.toBuffer();\n    throwIfAborted(signal);\n    return {\n      body,\n      contentType: encodedType,\n    };\n  };\n}\n\nexport function createNodeImageUrlValidator(config: ResolvedFarmImageConfig) {\n  return async function validateNodeImageUrl(url: URL): Promise<void> {\n    if (config.dangerouslyAllowLocalIP) return;\n\n    let addresses: Array<{ address: string; family: number }>;\n    try {\n      // DNS is only needed by remote image requests. Keeping it out of the\n      // initial server module graph reduces normal page/API startup work.\n      const { lookup } = await import(\"node:dns/promises\");\n      addresses = await lookup(url.hostname, { all: true, verbatim: true });\n    } catch {\n      throw new FarmImageRequestError(\n        \"PRIVATE_SOURCE\",\n        400,\n        \"Could not safely resolve the image source\",\n      );\n    }\n    if (addresses.length === 0 || addresses.some(({ address }) => isPrivateImageAddress(address))) {\n      throw new FarmImageRequestError(\"PRIVATE_SOURCE\", 400, \"Private image source is not allowed\");\n    }\n  };\n}\n\n/**\n * Create a remote image fetcher whose socket lookup rejects private addresses.\n * Validation and connection share this lookup, closing the DNS-rebinding gap\n * left by resolving a hostname before a separate global fetch.\n */\nexport function createNodeImageFetcher(\n  config: Pick<ResolvedFarmImageConfig, \"dangerouslyAllowLocalIP\">,\n  lookup?: NodeImageDnsLookup,\n): typeof globalThis.fetch {\n  if (config.dangerouslyAllowLocalIP) return globalThis.fetch.bind(globalThis);\n\n  return (async (input: RequestInfo | URL, init: RequestInit = {}) => {\n    const inputRequest = input instanceof Request ? input : undefined;\n    const url = input instanceof URL ? input : new URL(inputRequest?.url ?? String(input));\n    if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n      throw new FarmImageRequestError(\"DISALLOWED_SOURCE\", 400, \"Unsupported image protocol\");\n    }\n\n    const [{ request }, { Readable }, dns] = await Promise.all([\n      url.protocol === \"https:\" ? import(\"node:https\") : import(\"node:http\"),\n      import(\"node:stream\"),\n      lookup ? Promise.resolve(null) : import(\"node:dns\"),\n    ]);\n    const resolveHostname = lookup ?? (dns!.lookup as unknown as NodeImageDnsLookup);\n    const headers = new Headers(inputRequest?.headers);\n    new Headers(init.headers).forEach((value, name) => headers.set(name, value));\n    if (!headers.has(\"accept-encoding\")) headers.set(\"accept-encoding\", \"identity\");\n\n    return new Promise<Response>((resolve, reject) => {\n      const nodeRequest = request(\n        url,\n        {\n          method: init.method ?? inputRequest?.method ?? \"GET\",\n          headers: Object.fromEntries(headers.entries()),\n          signal: init.signal ?? inputRequest?.signal,\n          lookup(hostname, options, callback) {\n            resolveHostname(hostname, { all: true, verbatim: true }, (error, addresses) => {\n              if (error) {\n                callback(error, \"\", 4);\n                return;\n              }\n              if (\n                addresses.length === 0 ||\n                addresses.some(({ address }) => isPrivateImageAddress(address))\n              ) {\n                callback(\n                  new FarmImageRequestError(\n                    \"PRIVATE_SOURCE\",\n                    400,\n                    \"Private image source is not allowed\",\n                  ),\n                  \"\",\n                  4,\n                );\n                return;\n              }\n\n              if (options.all) {\n                (\n                  callback as unknown as (\n                    error: null,\n                    addresses: Array<{ address: string; family: number }>,\n                  ) => void\n                )(null, addresses);\n                return;\n              }\n              const address = addresses[0]!;\n              callback(null, address.address, address.family);\n            });\n          },\n        },\n        (nodeResponse) => {\n          const status = nodeResponse.statusCode ?? 500;\n          if (status < 200 || status > 599) {\n            nodeResponse.resume();\n            reject(\n              new FarmImageRequestError(\n                \"UNSUPPORTED_IMAGE\",\n                502,\n                \"Image source returned an invalid HTTP status\",\n              ),\n            );\n            return;\n          }\n          const responseHeaders = new Headers();\n          for (let index = 0; index < nodeResponse.rawHeaders.length; index += 2) {\n            responseHeaders.append(\n              nodeResponse.rawHeaders[index]!,\n              nodeResponse.rawHeaders[index + 1]!,\n            );\n          }\n          const body =\n            status === 204 || status === 205 || status === 304\n              ? null\n              : (Readable.toWeb(nodeResponse) as ReadableStream);\n          resolve(\n            new Response(body, {\n              status,\n              statusText: nodeResponse.statusMessage,\n              headers: responseHeaders,\n            }),\n          );\n        },\n      );\n      nodeRequest.once(\"error\", reject);\n      nodeRequest.end();\n    });\n  }) as typeof globalThis.fetch;\n}\n\nfunction throwIfAborted(signal: AbortSignal): void {\n  if (signal.aborted) {\n    throw signal.reason instanceof Error\n      ? signal.reason\n      : new DOMException(\"The image request was aborted\", \"AbortError\");\n  }\n}\n"],"mappings":"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","names":["address"]}