{"version":3,"sources":["../../src/image-sharp.ts","../../src/image-server.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","import type {\n  FarmImageFormat,\n  FarmImageLocalPattern,\n  FarmImageRemotePattern,\n  ResolvedFarmImageConfig,\n} from \"./image-config\";\nimport { matchesFarmIfNoneMatch } from \"./server-http\";\n\nexport interface FarmImageTransformInput {\n  source: Uint8Array;\n  sourceUrl: URL;\n  sourceType: string;\n  width: number;\n  quality: number;\n  accept: string;\n  formats: readonly FarmImageFormat[];\n  signal: AbortSignal;\n  /**\n   * Byte ceiling for any source the transformer fetches itself. Supplied by the\n   * image handler; transformers that re-fetch the origin (Cloudflare) must\n   * enforce it, since they bypass the handler's bounded read.\n   */\n  maximumResponseBody?: number;\n}\n\nexport interface FarmImageTransformResult {\n  body: Uint8Array;\n  contentType: string;\n}\n\nexport type FarmImageTransformer = (\n  input: FarmImageTransformInput,\n) => Promise<FarmImageTransformResult>;\n\nexport interface CreateFarmImageHandlerOptions {\n  fetch?: typeof globalThis.fetch;\n  /** Node-only fetcher that validates the DNS result used for remote connections. @internal */\n  fetchRemote?: typeof globalThis.fetch;\n  transform: FarmImageTransformer;\n  validateRemoteUrl?: (url: URL) => void | Promise<void>;\n  onError?: (error: unknown, request: Request) => void;\n  cacheEntries?: number;\n}\n\nexport type FarmImageHandler = (request: Request) => Promise<Response | null>;\n\ntype OptimizedImage = FarmImageTransformResult & {\n  etag: string;\n  cacheControl: string;\n  expiresAt: number;\n};\n\ntype FarmImageRequestErrorCode =\n  | \"BODY_TOO_LARGE\"\n  | \"DISALLOWED_SOURCE\"\n  | \"INVALID_METHOD\"\n  | \"INVALID_PARAMETER\"\n  | \"PRIVATE_SOURCE\"\n  | \"TOO_MANY_REDIRECTS\"\n  | \"UNSUPPORTED_IMAGE\";\n\nexport class FarmImageRequestError extends Error {\n  readonly code: FarmImageRequestErrorCode;\n  readonly status: number;\n\n  constructor(code: FarmImageRequestErrorCode, status: number, message: string) {\n    super(message);\n    this.name = \"FarmImageRequestError\";\n    this.code = code;\n    this.status = status;\n  }\n}\n\nexport function createFarmImageHandler(\n  config: ResolvedFarmImageConfig,\n  options: CreateFarmImageHandlerOptions,\n): FarmImageHandler {\n  const fetcher = options.fetch ?? globalThis.fetch;\n  const cache = new FarmImageMemoryCache(options.cacheEntries ?? 100);\n  // Identical concurrent misses share one fetch + transform. Without this a\n  // burst for an uncached image (a new page going live, a CDN cold start)\n  // fetches the origin and runs the codec once per request.\n  const inflight = new Map<string, InflightOptimization>();\n  const allowedWidths = new Set([...config.deviceSizes, ...config.imageSizes]);\n  const allowedQualities = new Set(config.qualities);\n\n  return async function handleFarmImage(request): Promise<Response | null> {\n    const requestUrl = new URL(request.url);\n    if (requestUrl.pathname !== config.path) return null;\n\n    try {\n      if (config.provider === \"none\") {\n        throw new FarmImageRequestError(\n          \"DISALLOWED_SOURCE\",\n          404,\n          \"The Farm image optimizer is disabled\",\n        );\n      }\n      if (request.method !== \"GET\" && request.method !== \"HEAD\") {\n        throw new FarmImageRequestError(\n          \"INVALID_METHOD\",\n          405,\n          \"The Farm image optimizer only accepts GET and HEAD\",\n        );\n      }\n\n      const sourceUrl = await resolveImageSourceUrl(requestUrl, config, options.validateRemoteUrl);\n      const width = parseAllowedInteger(requestUrl.searchParams.get(\"w\"), allowedWidths, \"width\");\n      const quality = parseAllowedInteger(\n        requestUrl.searchParams.get(\"q\"),\n        allowedQualities,\n        \"quality\",\n      );\n      const accept = request.headers.get(\"accept\") ?? \"\";\n      // Key on the format the Accept header negotiates to, not the header text.\n      // Both transformers derive their output from `selectOutputFormat(accept,\n      // formats)` alone, so every header that negotiates to the same format\n      // produces byte-identical output. Keying on the raw header let a caller\n      // vary it freely (`image/webp,*/*;q=0.8`, reordered lists, extra params)\n      // and force an uncached fetch and transform each time.\n      const negotiatedFormat = selectOutputFormat(accept, config.formats) ?? \"\";\n      const cacheKey = `${sourceUrl.href}\\n${width}\\n${quality}\\n${negotiatedFormat}`;\n      let optimized = cache.get(cacheKey);\n\n      if (!optimized) {\n        optimized = await runCoalesced(inflight, cacheKey, request.signal, async (signal) => {\n          const fetchedSource = await fetchImageSource(\n            sourceUrl,\n            requestUrl.origin,\n            config,\n            fetcher,\n            options.fetchRemote,\n            options.validateRemoteUrl,\n            signal,\n          );\n          const source = await readResponseWithLimit(\n            fetchedSource.response,\n            config.maximumResponseBody,\n          );\n          const sourceType = detectImageContentType(source);\n          validateSourceType(sourceType, config);\n          throwIfAborted(signal);\n\n          const result = await options.transform({\n            source,\n            sourceUrl: fetchedSource.url,\n            sourceType,\n            width,\n            quality,\n            accept,\n            formats: config.formats,\n            signal,\n            maximumResponseBody: config.maximumResponseBody,\n          });\n          throwIfAborted(signal);\n          validateTransformedResult(result, config);\n\n          const entry = {\n            ...result,\n            etag: createImageEtag(result.body),\n            cacheControl: `public, max-age=${config.minimumCacheTTL}, stale-while-revalidate=${Math.max(\n              config.minimumCacheTTL,\n              60,\n            )}`,\n            expiresAt: Date.now() + config.minimumCacheTTL * 1_000,\n          };\n          cache.set(cacheKey, entry);\n          return entry;\n        });\n      }\n\n      return createOptimizedImageResponse(request, optimized, config);\n    } catch (error) {\n      if (!(error instanceof FarmImageRequestError) && !isAbortError(error)) {\n        try {\n          options.onError?.(error, request);\n        } catch {\n          // Error reporting must not replace the optimizer's sanitized response.\n        }\n      }\n      return createFarmImageErrorResponse(error);\n    }\n  };\n}\n\n/** Mirrors the `images.maximumResponseBody` default (\"10mb\"). */\nconst DEFAULT_IMAGE_TRANSFORM_BODY_LIMIT = 10 * 1024 * 1024;\n\nexport function createCloudflareImageTransformer(\n  fetcher: typeof globalThis.fetch = globalThis.fetch,\n): FarmImageTransformer {\n  return async ({ sourceUrl, width, quality, accept, formats, signal, maximumResponseBody }) => {\n    const format = selectOutputFormat(accept, formats);\n    // Cloudflare resizing works by letting the edge fetch the origin, so this\n    // request cannot reuse the bytes the handler already read. It still must not\n    // be a weaker fetch than the validated one: `redirect: \"manual\"` keeps it\n    // from silently following a hop the handler never validated, and the body is\n    // read under the same ceiling as the handler's own read.\n    const response = await fetcher(sourceUrl, {\n      signal,\n      redirect: \"manual\",\n      headers: { accept: \"image/*\" },\n      cf: {\n        image: {\n          fit: \"scale-down\",\n          width,\n          quality,\n          ...(format ? { format: format === \"image/avif\" ? \"avif\" : \"webp\" } : {}),\n        },\n      },\n    } as RequestInit);\n\n    if (response.status >= 300 && response.status < 400) {\n      void cancelResponseBody(response);\n      throw new FarmImageRequestError(\n        \"UNSUPPORTED_IMAGE\",\n        502,\n        \"Source image redirected after validation\",\n      );\n    }\n\n    if (!response.ok) {\n      throw new FarmImageRequestError(\n        \"UNSUPPORTED_IMAGE\",\n        response.status === 404 ? 404 : 502,\n        \"Cloudflare could not transform the source image\",\n      );\n    }\n\n    const body = await readResponseWithLimit(\n      response,\n      maximumResponseBody ?? DEFAULT_IMAGE_TRANSFORM_BODY_LIMIT,\n    );\n    return {\n      body,\n      contentType:\n        normalizeImageContentType(response.headers.get(\"content-type\")) ||\n        detectImageContentType(body),\n    };\n  };\n}\n\nexport function selectOutputFormat(\n  accept: string,\n  formats: readonly FarmImageFormat[],\n): FarmImageFormat | undefined {\n  const qualityByFormat = new Map<string, number>();\n\n  for (const range of accept.split(\",\")) {\n    const [rawType, ...parameters] = range.split(\";\");\n    const type = rawType.trim().toLowerCase();\n    if (!type) continue;\n\n    let quality = 1;\n    for (const parameter of parameters) {\n      const [rawName, rawValue] = parameter.split(\"=\", 2);\n      if (rawName.trim().toLowerCase() !== \"q\") continue;\n      const parsed = Number(rawValue?.trim());\n      quality = Number.isFinite(parsed) && parsed >= 0 && parsed <= 1 ? parsed : 0;\n      break;\n    }\n\n    qualityByFormat.set(type, Math.max(qualityByFormat.get(type) ?? 0, quality));\n  }\n\n  let selected: FarmImageFormat | undefined;\n  let selectedQuality = 0;\n  for (const format of formats) {\n    const quality = qualityByFormat.get(format) ?? 0;\n    if (quality > selectedQuality) {\n      selected = format;\n      selectedQuality = quality;\n    }\n  }\n  return selected;\n}\n\n/**\n * Expand an IPv6 address into its eight 16-bit hextets, or null when the value\n * is not a parseable IPv6 address.\n *\n * Textual comparison is not enough for this boundary: the same address has many\n * spellings, and `new URL()` rewrites some of them. `::ffff:127.0.0.1` becomes\n * `::ffff:7f00:1`, and `::1` may arrive fully expanded, so every form has to be\n * reduced to numbers before any range check.\n */\nfunction parseIpv6Hextets(value: string): number[] | null {\n  // Drop any zone index (fe80::1%eth0); it does not affect the address.\n  let text = value.split(\"%\", 1)[0] ?? \"\";\n  if (!text.includes(\":\")) return null;\n\n  // A trailing dotted quad (::ffff:127.0.0.1) contributes the low two hextets.\n  let tail: number[] = [];\n  const lastColon = text.lastIndexOf(\":\");\n  const candidate = text.slice(lastColon + 1);\n  if (candidate.includes(\".\")) {\n    const octets = parseIpv4Octets(candidate);\n    if (!octets) return null;\n    tail = [(octets[0]! << 8) | octets[1]!, (octets[2]! << 8) | octets[3]!];\n    text = text.slice(0, lastColon);\n    // \"::1.2.3.4\" leaves \"::\" here, and \"1.2.3.4\" alone leaves \"\" — not IPv6.\n    if (text === \"\") return null;\n  }\n\n  const compressionParts = text.split(\"::\");\n  if (compressionParts.length > 2) return null;\n\n  const parseGroup = (group: string): number[] | null => {\n    if (group === \"\") return [];\n    const hextets: number[] = [];\n    for (const part of group.split(\":\")) {\n      if (!/^[0-9a-f]{1,4}$/.test(part)) return null;\n      hextets.push(Number.parseInt(part, 16));\n    }\n    return hextets;\n  };\n\n  const head = parseGroup(compressionParts[0] ?? \"\");\n  const rest = parseGroup(compressionParts[1] ?? \"\");\n  if (!head || !rest) return null;\n\n  const explicit = [...head, ...rest, ...tail];\n  if (compressionParts.length === 1) {\n    return explicit.length === 8 ? explicit : null;\n  }\n\n  // \"::\" stands for at least one zero hextet.\n  if (explicit.length >= 8) return null;\n  const zeros = Array.from({ length: 8 - explicit.length }, () => 0);\n  return [...head, ...zeros, ...rest, ...tail];\n}\n\nfunction parseIpv4Octets(value: string): number[] | null {\n  const parts = value.split(\".\");\n  if (parts.length !== 4 || parts.some((part) => !/^\\d{1,3}$/.test(part))) return null;\n  const octets = parts.map(Number);\n  return octets.some((part) => part > 255) ? null : octets;\n}\n\nfunction isPrivateIpv4(octets: readonly number[]): boolean {\n  const [a, b] = octets as [number, number];\n  return (\n    a === 0 ||\n    a === 10 ||\n    a === 127 ||\n    (a === 100 && b >= 64 && b <= 127) ||\n    (a === 169 && b === 254) ||\n    (a === 172 && b >= 16 && b <= 31) ||\n    (a === 192 && (b === 0 || b === 168)) ||\n    (a === 198 && (b === 18 || b === 19)) ||\n    a >= 224\n  );\n}\n\nexport function isPrivateImageAddress(address: string): boolean {\n  const value = address\n    .trim()\n    .toLowerCase()\n    .replace(/^\\[|\\]$/g, \"\");\n\n  const hextets = parseIpv6Hextets(value);\n  if (hextets) {\n    const [h0, h1, h2, h3, h4, h5, h6, h7] = hextets as [\n      number,\n      number,\n      number,\n      number,\n      number,\n      number,\n      number,\n      number,\n    ];\n    const zeroPrefix = h0 === 0 && h1 === 0 && h2 === 0 && h3 === 0;\n\n    // An address that embeds IPv4 is only as safe as that IPv4 address:\n    // IPv4-mapped (::ffff:0:0/96), IPv4-translated (::ffff:0:0:0/96), and the\n    // deprecated IPv4-compatible (::/96) forms all reach the v4 host.\n    const embedsIpv4 =\n      zeroPrefix &&\n      ((h4 === 0 && h5 === 0xffff) || (h4 === 0xffff && h5 === 0) || (h4 === 0 && h5 === 0));\n    if (embedsIpv4 && (h6 !== 0 || h7 !== 0)) {\n      return isPrivateIpv4([h6 >> 8, h6 & 0xff, h7 >> 8, h7 & 0xff]);\n    }\n\n    // Unspecified (::) and loopback (::1) in any spelling.\n    if (zeroPrefix && h4 === 0 && h5 === 0 && h6 === 0 && (h7 === 0 || h7 === 1)) return true;\n    // Unique-local fc00::/7, link-local fe80::/10, multicast ff00::/8.\n    if ((h0 & 0xfe00) === 0xfc00) return true;\n    if ((h0 & 0xffc0) === 0xfe80) return true;\n    if ((h0 & 0xff00) === 0xff00) return true;\n    return false;\n  }\n\n  const octets = parseIpv4Octets(value);\n  return octets ? isPrivateIpv4(octets) : false;\n}\n\nfunction parseAllowedInteger(\n  raw: string | null,\n  allowed: ReadonlySet<number>,\n  name: string,\n): number {\n  if (!raw || !/^\\d+$/.test(raw)) {\n    throw new FarmImageRequestError(\n      \"INVALID_PARAMETER\",\n      400,\n      `Image ${name} must be an allowed integer`,\n    );\n  }\n  const value = Number(raw);\n  if (!allowed.has(value)) {\n    throw new FarmImageRequestError(\"INVALID_PARAMETER\", 400, `Image ${name} is not configured`);\n  }\n  return value;\n}\n\nasync function resolveImageSourceUrl(\n  requestUrl: URL,\n  config: ResolvedFarmImageConfig,\n  validateRemoteUrl: CreateFarmImageHandlerOptions[\"validateRemoteUrl\"],\n): Promise<URL> {\n  const raw = requestUrl.searchParams.get(\"url\");\n  if (!raw || raw.length > 4096 || raw.startsWith(\"//\")) {\n    throw new FarmImageRequestError(\"INVALID_PARAMETER\", 400, \"Invalid image source URL\");\n  }\n\n  let sourceUrl: URL;\n  try {\n    sourceUrl = raw.startsWith(\"/\") ? new URL(raw, requestUrl.origin) : new URL(raw);\n  } catch {\n    throw new FarmImageRequestError(\"INVALID_PARAMETER\", 400, \"Invalid image source URL\");\n  }\n\n  await validateImageSourceUrl(sourceUrl, requestUrl.origin, config, validateRemoteUrl);\n  return sourceUrl;\n}\n\nasync function validateImageSourceUrl(\n  sourceUrl: URL,\n  requestOrigin: string,\n  config: ResolvedFarmImageConfig,\n  validateRemoteUrl: CreateFarmImageHandlerOptions[\"validateRemoteUrl\"],\n): Promise<void> {\n  if (sourceUrl.protocol !== \"http:\" && sourceUrl.protocol !== \"https:\") {\n    throw new FarmImageRequestError(\"DISALLOWED_SOURCE\", 400, \"Unsupported image protocol\");\n  }\n  if (sourceUrl.username || sourceUrl.password || sourceUrl.hash) {\n    throw new FarmImageRequestError(\"DISALLOWED_SOURCE\", 400, \"Unsafe image source URL\");\n  }\n\n  if (sourceUrl.origin === requestOrigin) {\n    if (\n      sourceUrl.pathname === config.path ||\n      !matchesLocalPatterns(sourceUrl, config.localPatterns)\n    ) {\n      throw new FarmImageRequestError(\n        \"DISALLOWED_SOURCE\",\n        400,\n        \"Local image source is not allowed\",\n      );\n    }\n    return;\n  }\n\n  if (!matchesRemoteSource(sourceUrl, config)) {\n    throw new FarmImageRequestError(\"DISALLOWED_SOURCE\", 400, \"Remote image source is not allowed\");\n  }\n  if (!config.dangerouslyAllowLocalIP && isPrivateImageAddress(sourceUrl.hostname)) {\n    throw new FarmImageRequestError(\"PRIVATE_SOURCE\", 400, \"Private image source is not allowed\");\n  }\n  if (!config.dangerouslyAllowLocalIP) {\n    await validateRemoteUrl?.(sourceUrl);\n  }\n}\n\nasync function fetchImageSource(\n  initialUrl: URL,\n  requestOrigin: string,\n  config: ResolvedFarmImageConfig,\n  fetcher: typeof globalThis.fetch,\n  fetchRemote: typeof globalThis.fetch | undefined,\n  validateRemoteUrl: CreateFarmImageHandlerOptions[\"validateRemoteUrl\"],\n  signal: AbortSignal,\n): Promise<{ response: Response; url: URL }> {\n  let currentUrl = initialUrl;\n\n  for (let redirectCount = 0; ; redirectCount += 1) {\n    throwIfAborted(signal);\n    const sourceFetcher = currentUrl.origin === requestOrigin ? fetcher : (fetchRemote ?? fetcher);\n    const response = await sourceFetcher(currentUrl, {\n      method: \"GET\",\n      redirect: \"manual\",\n      signal,\n      headers: {\n        accept: \"image/avif,image/webp,image/*,*/*;q=0.8\",\n        \"user-agent\": \"Farm.js Image Optimizer\",\n      },\n    });\n\n    if (![301, 302, 303, 307, 308].includes(response.status)) {\n      if (!response.ok) {\n        await cancelResponseBody(response);\n        throw new FarmImageRequestError(\n          \"UNSUPPORTED_IMAGE\",\n          response.status === 404 ? 404 : 502,\n          \"Could not fetch source image\",\n        );\n      }\n      return { response, url: currentUrl };\n    }\n\n    if (redirectCount >= config.maximumRedirects) {\n      await cancelResponseBody(response);\n      throw new FarmImageRequestError(\n        \"TOO_MANY_REDIRECTS\",\n        400,\n        \"Source image exceeded the redirect limit\",\n      );\n    }\n    const location = response.headers.get(\"location\");\n    if (!location) {\n      await cancelResponseBody(response);\n      throw new FarmImageRequestError(\"UNSUPPORTED_IMAGE\", 502, \"Invalid image redirect\");\n    }\n    await cancelResponseBody(response);\n    currentUrl = new URL(location, currentUrl);\n    await validateImageSourceUrl(currentUrl, requestOrigin, config, validateRemoteUrl);\n  }\n}\n\ntype InflightOptimization = {\n  promise: Promise<OptimizedImage>;\n  controller: AbortController;\n  waiters: number;\n};\n\n/**\n * Share one in-flight optimization between identical concurrent requests.\n *\n * The shared work runs under its own AbortController rather than any single\n * request's signal, so one caller going away cannot cancel the image everyone\n * else is waiting for. The controller is aborted only when the last waiter\n * leaves, so an abandoned burst still stops promptly.\n */\nasync function runCoalesced(\n  inflight: Map<string, InflightOptimization>,\n  key: string,\n  requestSignal: AbortSignal,\n  run: (signal: AbortSignal) => Promise<OptimizedImage>,\n): Promise<OptimizedImage> {\n  let entry = inflight.get(key);\n  if (!entry) {\n    const controller = new AbortController();\n    const created: InflightOptimization = {\n      controller,\n      waiters: 0,\n      promise: undefined as unknown as Promise<OptimizedImage>,\n    };\n    created.promise = run(controller.signal).finally(() => {\n      if (inflight.get(key) === created) inflight.delete(key);\n    });\n    // Every waiter can detach before the shared work settles: an already\n    // aborted request returns early without ever attaching to this promise,\n    // and the last waiter leaving aborts the controller. Keep one no-op\n    // handler so that rejection is never reported as unhandled. Waiters still\n    // observe it, because this does not replace the promise they await.\n    created.promise.catch(() => {});\n    inflight.set(key, created);\n    entry = created;\n  }\n\n  const pending = entry;\n  pending.waiters += 1;\n  try {\n    return await raceRequestAbort(pending.promise, requestSignal);\n  } finally {\n    pending.waiters -= 1;\n    if (pending.waiters === 0 && inflight.get(key) === pending) {\n      inflight.delete(key);\n      pending.controller.abort();\n    }\n  }\n}\n\nfunction raceRequestAbort(\n  promise: Promise<OptimizedImage>,\n  signal: AbortSignal,\n): Promise<OptimizedImage> {\n  if (!signal) return promise;\n  if (signal.aborted) return Promise.reject(signal.reason ?? new Error(\"Aborted\"));\n\n  return new Promise<OptimizedImage>((resolve, reject) => {\n    const onAbort = () => reject(signal.reason ?? new Error(\"Aborted\"));\n    signal.addEventListener(\"abort\", onAbort, { once: true });\n    promise.then(resolve, reject).finally(() => signal.removeEventListener(\"abort\", onAbort));\n  });\n}\n\nasync function readResponseWithLimit(response: Response, limit: number): Promise<Uint8Array> {\n  const contentLength = response.headers.get(\"content-length\");\n  if (contentLength && Number(contentLength) > limit) {\n    // Cleanup (including an unread tee branch) must not delay the size rejection.\n    void cancelResponseBody(response);\n    throw new FarmImageRequestError(\"BODY_TOO_LARGE\", 413, \"Source image is too large\");\n  }\n\n  if (!response.body) return new Uint8Array();\n  const reader = response.body.getReader();\n  const chunks: Uint8Array[] = [];\n  let byteLength = 0;\n\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      byteLength += value.byteLength;\n      if (byteLength > limit) {\n        const error = new FarmImageRequestError(\"BODY_TOO_LARGE\", 413, \"Source image is too large\");\n        void reader.cancel(error).catch(() => {});\n        throw error;\n      }\n      chunks.push(value);\n    }\n  } finally {\n    reader.releaseLock();\n  }\n\n  const result = new Uint8Array(byteLength);\n  let offset = 0;\n  for (const chunk of chunks) {\n    result.set(chunk, offset);\n    offset += chunk.byteLength;\n  }\n  return result;\n}\n\nasync function cancelResponseBody(response: Response): Promise<void> {\n  try {\n    await response.body?.cancel();\n  } catch {\n    // Cleanup must not replace the request error or redirect result.\n  }\n}\n\nfunction detectImageContentType(bytes: Uint8Array): string {\n  if (\n    bytes.length >= 8 &&\n    bytes[0] === 0x89 &&\n    bytes[1] === 0x50 &&\n    bytes[2] === 0x4e &&\n    bytes[3] === 0x47\n  ) {\n    return \"image/png\";\n  }\n  if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {\n    return \"image/jpeg\";\n  }\n  if (bytes.length >= 6) {\n    const signature = new TextDecoder().decode(bytes.slice(0, 6));\n    if (signature === \"GIF87a\" || signature === \"GIF89a\") return \"image/gif\";\n  }\n  if (bytes.length >= 12) {\n    const riff = new TextDecoder().decode(bytes.slice(0, 4));\n    const webp = new TextDecoder().decode(bytes.slice(8, 12));\n    if (riff === \"RIFF\" && webp === \"WEBP\") return \"image/webp\";\n    const box = new TextDecoder().decode(bytes.slice(4, 12));\n    if (box.startsWith(\"ftypavif\") || box.startsWith(\"ftypavis\")) return \"image/avif\";\n  }\n\n  const prefix = new TextDecoder().decode(bytes.slice(0, 512)).trimStart().toLowerCase();\n  if (prefix.startsWith(\"<svg\") || (prefix.startsWith(\"<?xml\") && prefix.includes(\"<svg\"))) {\n    return \"image/svg+xml\";\n  }\n  return \"\";\n}\n\nfunction validateSourceType(type: string, config: ResolvedFarmImageConfig): void {\n  if (!type || (type === \"image/svg+xml\" && !config.dangerouslyAllowSVG)) {\n    throw new FarmImageRequestError(\"UNSUPPORTED_IMAGE\", 415, \"Unsupported source image\");\n  }\n}\n\nfunction validateTransformedResult(\n  result: FarmImageTransformResult,\n  config: ResolvedFarmImageConfig,\n): void {\n  if (!(result.body instanceof Uint8Array) || result.body.byteLength === 0) {\n    throw new Error(\"The image transformer returned an empty response\");\n  }\n  if (result.body.byteLength > config.maximumResponseBody) {\n    throw new FarmImageRequestError(\"BODY_TOO_LARGE\", 413, \"Optimized image is too large\");\n  }\n  const contentType = normalizeImageContentType(result.contentType);\n  if (!contentType || (contentType === \"image/svg+xml\" && !config.dangerouslyAllowSVG)) {\n    throw new Error(\"The image transformer returned an unsupported content type\");\n  }\n  result.contentType = contentType;\n}\n\nfunction normalizeImageContentType(value: string | null): string {\n  const type = value?.split(\";\", 1)[0].trim().toLowerCase() ?? \"\";\n  return type.startsWith(\"image/\") ? type : \"\";\n}\n\nfunction matchesRemoteSource(url: URL, config: ResolvedFarmImageConfig): boolean {\n  if (config.domains.includes(url.hostname.toLowerCase())) return true;\n  return config.remotePatterns.some((pattern) => matchesRemotePattern(url, pattern));\n}\n\nfunction matchesRemotePattern(url: URL, pattern: FarmImageRemotePattern): boolean {\n  return (\n    (!pattern.protocol || url.protocol === `${pattern.protocol}:`) &&\n    matchesHostname(url.hostname, pattern.hostname) &&\n    (pattern.port === undefined || url.port === pattern.port) &&\n    matchesGlob(url.pathname, pattern.pathname ?? \"/**\") &&\n    (pattern.search === undefined || url.search === pattern.search)\n  );\n}\n\nfunction matchesLocalPatterns(url: URL, patterns: readonly FarmImageLocalPattern[]): boolean {\n  return patterns.some(\n    (pattern) =>\n      matchesGlob(url.pathname, pattern.pathname) &&\n      (pattern.search === undefined || url.search === pattern.search),\n  );\n}\n\nfunction matchesHostname(hostname: string, pattern: string): boolean {\n  const normalizedHostname = hostname.toLowerCase();\n  const normalizedPattern = pattern.toLowerCase();\n  if (normalizedPattern.startsWith(\"**.\")) {\n    const suffix = normalizedPattern.slice(3);\n    return normalizedHostname === suffix || normalizedHostname.endsWith(`.${suffix}`);\n  }\n  if (normalizedPattern.startsWith(\"*.\")) {\n    const suffix = normalizedPattern.slice(2);\n    const prefix = normalizedHostname.slice(0, -(suffix.length + 1));\n    return normalizedHostname.endsWith(`.${suffix}`) && !!prefix && !prefix.includes(\".\");\n  }\n  return normalizedHostname === normalizedPattern;\n}\n\nfunction matchesGlob(value: string, pattern: string): boolean {\n  const escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n  const source = escaped.replace(/\\*\\*/g, \"\\0\").replace(/\\*/g, \"[^/]*\").replace(/\\0/g, \".*\");\n  return new RegExp(`^${source}$`).test(value);\n}\n\nfunction createOptimizedImageResponse(\n  request: Request,\n  image: OptimizedImage,\n  config: ResolvedFarmImageConfig,\n): Response {\n  const headers = new Headers({\n    \"cache-control\": image.cacheControl,\n    \"content-type\": image.contentType,\n    \"content-length\": String(image.body.byteLength),\n    \"content-disposition\": \"inline\",\n    etag: image.etag,\n    vary: \"Accept\",\n    \"x-content-type-options\": \"nosniff\",\n  });\n  if (image.contentType === \"image/svg+xml\" && config.dangerouslyAllowSVG) {\n    headers.set(\"content-security-policy\", \"default-src 'none'; sandbox\");\n  }\n  if (matchesFarmIfNoneMatch(request.headers.get(\"if-none-match\"), image.etag)) {\n    headers.delete(\"content-length\");\n    return new Response(null, { status: 304, headers });\n  }\n  const body =\n    request.method === \"HEAD\"\n      ? null\n      : image.body.buffer.slice(\n          image.body.byteOffset,\n          image.body.byteOffset + image.body.byteLength,\n        );\n  return new Response(body as ArrayBuffer | null, { status: 200, headers });\n}\n\nfunction createFarmImageErrorResponse(error: unknown): Response {\n  const status =\n    error instanceof FarmImageRequestError ? error.status : isAbortError(error) ? 499 : 500;\n  const headers = new Headers({\n    \"cache-control\": \"no-store\",\n    \"content-type\": \"text/plain; charset=utf-8\",\n    \"x-content-type-options\": \"nosniff\",\n  });\n  if (status === 405) headers.set(\"allow\", \"GET, HEAD\");\n\n  const message =\n    status === 400\n      ? \"Invalid image request\"\n      : status === 404\n        ? \"Image not found\"\n        : status === 405\n          ? \"Method not allowed\"\n          : status === 413\n            ? \"Image is too large\"\n            : status === 415\n              ? \"Unsupported image\"\n              : status === 499\n                ? \"Image request cancelled\"\n                : \"Image optimization failed\";\n  return new Response(message, { status, headers });\n}\n\nfunction createImageEtag(bytes: Uint8Array): string {\n  let hash = 0x811c9dc5;\n  for (const byte of bytes) {\n    hash ^= byte;\n    hash = Math.imul(hash, 0x01000193);\n  }\n  return `W/\"farm-${bytes.byteLength.toString(16)}-${(hash >>> 0).toString(16)}\"`;\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\nfunction isAbortError(error: unknown): boolean {\n  return error instanceof Error && error.name === \"AbortError\";\n}\n\nclass FarmImageMemoryCache {\n  private readonly entries = new Map<string, OptimizedImage>();\n\n  constructor(private readonly capacity: number) {}\n\n  get(key: string): OptimizedImage | undefined {\n    const value = this.entries.get(key);\n    if (!value) return undefined;\n    if (value.expiresAt <= Date.now()) {\n      this.entries.delete(key);\n      return undefined;\n    }\n    this.entries.delete(key);\n    this.entries.set(key, value);\n    return value;\n  }\n\n  set(key: string, value: OptimizedImage): void {\n    if (this.capacity <= 0) return;\n    this.entries.delete(key);\n    this.entries.set(key, value);\n    while (this.entries.size > this.capacity) {\n      const oldestKey = this.entries.keys().next().value as string | undefined;\n      if (oldestKey === undefined) break;\n      this.entries.delete(oldestKey);\n    }\n  }\n}\n"],"mappings":"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","names":["address"]}