From ca7e3aa4a16296bcafe47aa0854293d8b955abb1 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 31 Jul 2025 17:02:57 +0200 Subject: [PATCH 1/2] detect more image types Sync of --- .changeset/lucky-cups-live.md | 7 ++++++ .../cloudflare/src/cli/templates/images.ts | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 .changeset/lucky-cups-live.md diff --git a/.changeset/lucky-cups-live.md b/.changeset/lucky-cups-live.md new file mode 100644 index 00000000..b9ed1d3e --- /dev/null +++ b/.changeset/lucky-cups-live.md @@ -0,0 +1,7 @@ +--- +"@opennextjs/cloudflare": patch +--- + +detect more image types + +Sync of diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 777dfa49..5e58ed6b 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -167,12 +167,16 @@ const AVIF = "image/avif"; const WEBP = "image/webp"; const PNG = "image/png"; const JPEG = "image/jpeg"; +const JXL = "image/jxl"; +const JP2 = "image/jp2"; +const HEIC = "image/heic"; const GIF = "image/gif"; const SVG = "image/svg+xml"; const ICO = "image/x-icon"; const ICNS = "image/x-icns"; const TIFF = "image/tiff"; const BMP = "image/bmp"; +const PDF = "application/pdf"; /** * Detects the content type by looking at the first few bytes of a file @@ -216,6 +220,25 @@ export function detectContentType(buffer: Uint8Array) { if ([0x42, 0x4d].every((b, i) => buffer[i] === b)) { return BMP; } + if ([0xff, 0x0a].every((b, i) => buffer[i] === b)) { + return JXL; + } + if ( + [0x00, 0x00, 0x00, 0x0c, 0x4a, 0x58, 0x4c, 0x20, 0x0d, 0x0a, 0x87, 0x0a].every((b, i) => buffer[i] === b) + ) { + return JXL; + } + if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63].every((b, i) => !b || buffer[i] === b)) { + return HEIC; + } + if ([0x25, 0x50, 0x44, 0x46, 0x2d].every((b, i) => buffer[i] === b)) { + return PDF; + } + if ( + [0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a].every((b, i) => buffer[i] === b) + ) { + return JP2; + } } declare global { From a9f499c0afd18df1b7e08feae54dc4664a87d02d Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 1 Aug 2025 08:45:59 +0200 Subject: [PATCH 2/2] fixup! reject non image/... --- packages/cloudflare/src/cli/templates/images.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 5e58ed6b..18590fe0 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -93,12 +93,16 @@ export async function fetchImage(fetcher: Fetcher | undefined, imageUrl: string, } if (!contentType) { - // Fallback to the sanitized upstream header when the type can not be detected + // Fallback to upstream header when the type can not be detected // https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/server/image-optimizer.ts#L748 - const header = imgResponse.headers.get("content-type") ?? ""; - if (header.startsWith("image/") && !header.includes(",")) { - contentType = header; - } + contentType = imgResponse.headers.get("content-type") ?? ""; + } + + // Sanitize the content type: + // - Accept images only + // - Reject multiple content types + if (!contentType.startsWith("image/") || contentType.includes(",")) { + contentType = undefined; } if (contentType && !(contentType === SVG && !__IMAGES_ALLOW_SVG__)) { @@ -176,6 +180,7 @@ const ICO = "image/x-icon"; const ICNS = "image/x-icns"; const TIFF = "image/tiff"; const BMP = "image/bmp"; +// pdf will be rejected (not an `image/...` type) const PDF = "application/pdf"; /**