From ab9558a43c32619c14b977523d690c07a17b0d76 Mon Sep 17 00:00:00 2001 From: Diretnan Domnan Date: Thu, 20 Aug 2026 19:37:50 +0200 Subject: [PATCH] IMAGES-2451: Fix negative text input check to allow streamable data --- src/cloudflare/internal/images-api.ts | 11 +++- .../images/images-api-instrumentation-test.js | 57 +++++++++++++++++++ .../internal/test/images/images-api-test.js | 51 +++++++++++++++++ 3 files changed, 116 insertions(+), 3 deletions(-) diff --git a/src/cloudflare/internal/images-api.ts b/src/cloudflare/internal/images-api.ts index 7487aeb6167..c7ae2d29017 100644 --- a/src/cloudflare/internal/images-api.ts +++ b/src/cloudflare/internal/images-api.ts @@ -14,7 +14,12 @@ type Fetcher = { }; function isTextSource(source: ImageSource): source is TextRasterize { - return !(source instanceof ReadableStream); + return ( + typeof source === 'object' && + source !== null && + 'content' in source && + 'options' in source + ); } function serializeTextSource(source: TextRasterize): string { @@ -153,7 +158,7 @@ class ImageTransformerImpl implements ImageTransformer { formData.append('text_input', serializeTextSource(this.#source)); } else { span.setAttribute('cloudflare.images.canvas.type', 'image'); - formData.append('image', this.#source as ReadableStream, { + formData.append('image', this.#source, { type: 'file', }); } @@ -221,7 +226,7 @@ class ImageTransformerImpl implements ImageTransformer { formData.append('draw_text', serializeTextSource(source)); } else { overlayStats.image++; - formData.append('draw_image', source as ReadableStream, { + formData.append('draw_image', source, { type: 'file', }); } diff --git a/src/cloudflare/internal/test/images/images-api-instrumentation-test.js b/src/cloudflare/internal/test/images/images-api-instrumentation-test.js index 07705b90275..d7768c5a08d 100644 --- a/src/cloudflare/internal/test/images/images-api-instrumentation-test.js +++ b/src/cloudflare/internal/test/images/images-api-instrumentation-test.js @@ -723,4 +723,61 @@ const expectedSpans = [ 'cloudflare.binding.type': 'Images', closed: true, }, + { + name: 'images_output', + 'cloudflare.binding.type': 'Images', + 'cloudflare.images.canvas.type': 'image', + 'cloudflare.images.options.format': 'image/avif', + closed: true, + }, + { + name: 'fetch', + 'network.protocol.name': 'http', + 'network.protocol.version': 'HTTP/1.1', + 'http.request.method': 'POST', + 'url.full': 'https://js.images.cloudflare.com/transform', + 'http.request.header.content-type': + 'multipart/form-data; boundary=', + 'http.response.status_code': 200n, + 'http.response.body.size': 22n, + closed: true, + }, + { + name: 'images_output', + 'cloudflare.binding.type': 'Images', + 'cloudflare.images.canvas.type': 'image', + 'cloudflare.images.options.format': 'image/avif', + closed: true, + }, + { + name: 'fetch', + 'network.protocol.name': 'http', + 'network.protocol.version': 'HTTP/1.1', + 'http.request.method': 'POST', + 'url.full': 'https://js.images.cloudflare.com/transform', + 'http.request.header.content-type': + 'multipart/form-data; boundary=', + 'http.response.status_code': 200n, + 'http.response.body.size': 88n, + closed: true, + }, + { + name: 'images_output', + 'cloudflare.binding.type': 'Images', + 'cloudflare.images.canvas.type': 'image', + 'cloudflare.images.options.format': 'image/png', + closed: true, + }, + { + name: 'fetch', + 'network.protocol.name': 'http', + 'network.protocol.version': 'HTTP/1.1', + 'http.request.method': 'POST', + 'url.full': 'https://js.images.cloudflare.com/transform', + 'http.request.header.content-type': + 'multipart/form-data; boundary=', + 'http.response.status_code': 200n, + 'http.response.body.size': 60n, + closed: true, + }, ]; diff --git a/src/cloudflare/internal/test/images/images-api-test.js b/src/cloudflare/internal/test/images/images-api-test.js index 8a47cd8caed..a20be613c99 100644 --- a/src/cloudflare/internal/test/images/images-api-test.js +++ b/src/cloudflare/internal/test/images/images-api-test.js @@ -1049,3 +1049,54 @@ export const test_images_create_direct_upload_with_options = { assert.equal(result.uploadURL.includes('custom-upload-id'), true); }, }; + +export const test_images_input_accepts_arraybuffer = { + /** + * @param {unknown} _ + * @param {Env} env + */ + async test(_, env) { + const buffer = new TextEncoder().encode('test image data').buffer; + const result = await env.images + .input(buffer) + .output({ format: 'image/avif' }); + + const response = await result.response(); + assert.equal(response.status, 200); + }, +}; + +export const test_images_input_accepts_uint8array = { + /** + * @param {unknown} _ + * @param {Env} env + */ + async test(_, env) { + const uint8 = new TextEncoder().encode('test image data'); + const result = await env.images + .input(uint8) + .output({ format: 'image/avif' }); + + const response = await result.response(); + assert.equal(response.status, 200); + }, +}; + +export const test_images_draw_accepts_arraybuffer = { + /** + * @param {unknown} _ + * @param {Env} env + */ + async test(_, env) { + const imageBuffer = new TextEncoder().encode('base image').buffer; + const overlayBuffer = new TextEncoder().encode('overlay image').buffer; + + const result = await env.images + .input(imageBuffer) + .draw(env.images.input(overlayBuffer)) + .output({ format: 'image/png' }); + + const response = await result.response(); + assert.equal(response.status, 200); + }, +};