From c0de8ce7dbc8c16a5f770c4d7b8d9f96cb3b9f5d Mon Sep 17 00:00:00 2001 From: blankll Date: Tue, 30 Jun 2026 14:21:07 +0800 Subject: [PATCH 1/3] fix: preserve binary response body as Buffer in transformFCResponse When isBase64Encoded:true and the content type is binary (image/, audio/, video/, application/octet-stream, application/pdf, font/), keep the decoded body as a Buffer instead of converting to UTF-8 string. Previously, Buffer.from(body, 'base64').toString('utf-8') always converted the decoded data to a UTF-8 string, which corrupted binary content (bytes >= 0x80 become U+FFFD). This caused image/png responses to arrive mangled at the HTTP client. Now binary content types stay as Buffer, allowing respondRaw() to send raw bytes via res.end(buffer). Text content types continue to be converted to UTF-8 string for JSON parsing compatibility. --- src/stack/localStack/aliyunFc.ts | 11 ++++++++++- tests/unit/stack/localStack/aliyunFc.unit.test.ts | 9 +++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/stack/localStack/aliyunFc.ts b/src/stack/localStack/aliyunFc.ts index 7da2082..f9618d0 100644 --- a/src/stack/localStack/aliyunFc.ts +++ b/src/stack/localStack/aliyunFc.ts @@ -155,7 +155,16 @@ export const transformFCResponse = ( let body = rawBody; if (isBase64Encoded && typeof body === 'string') { - body = Buffer.from(body, 'base64').toString('utf-8'); + const decoded = Buffer.from(body, 'base64'); + const contentType = String( + typeof headers === 'object' && headers ? headers['content-type'] || '' : '', + ); + const isBinary = + /^(image|audio|video)\//.test(contentType) || + contentType === 'application/octet-stream' || + contentType === 'application/pdf' || + contentType.includes('font/'); + body = isBinary ? decoded : decoded.toString('utf-8'); } // Only JSON-parse if the response Content-Type indicates JSON — otherwise pass through as-is. diff --git a/tests/unit/stack/localStack/aliyunFc.unit.test.ts b/tests/unit/stack/localStack/aliyunFc.unit.test.ts index 93d9867..2aa2e58 100644 --- a/tests/unit/stack/localStack/aliyunFc.unit.test.ts +++ b/tests/unit/stack/localStack/aliyunFc.unit.test.ts @@ -425,22 +425,23 @@ describe('Aliyun FC Utilities', () => { expect(typeof result.body).toBe('string'); }); - it('should pass through PNG binary (base64 decoded) body unchanged', () => { + it('should pass through PNG binary body as Buffer (not string)', () => { const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); const base64Png = pngData.toString('base64'); const fcResponse = { statusCode: 200, body: base64Png, - headers: { 'Content-Type': 'image/png' }, + headers: { 'content-type': 'image/png' }, isBase64Encoded: true, }; const result = transformFCResponse(fcResponse); expect(result.body).not.toBe(base64Png); - expect(Buffer.isBuffer(result.body)).toBe(false); - expect(result.headers['Content-Type']).toBe('image/png'); + expect(Buffer.isBuffer(result.body)).toBe(true); + expect((result.body as Buffer).equals(pngData)).toBe(true); + expect(result.headers['content-type']).toBe('image/png'); }); it('should still JSON-parse body when Content-Type is application/json', () => { From 797997baa2bfa0b4e7053e844628b36068ac053c Mon Sep 17 00:00:00 2001 From: blankll Date: Tue, 30 Jun 2026 14:54:11 +0800 Subject: [PATCH 2/3] test: add comprehensive binary content-type tests for transformFCResponse - 7 new tests covering all binary content types (JPEG, audio, video, PDF) - Text content types verified to stay as UTF-8 string (not Buffer) - application/octet-stream excluded from binary detection (too generic) - application/json base64 body properly decoded then JSON-parsed Also synced binary detection list with serverless-adapter: removed application/octet-stream. --- src/stack/localStack/aliyunFc.ts | 1 - .../stack/localStack/aliyunFc.unit.test.ts | 98 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/stack/localStack/aliyunFc.ts b/src/stack/localStack/aliyunFc.ts index f9618d0..c7d50bd 100644 --- a/src/stack/localStack/aliyunFc.ts +++ b/src/stack/localStack/aliyunFc.ts @@ -161,7 +161,6 @@ export const transformFCResponse = ( ); const isBinary = /^(image|audio|video)\//.test(contentType) || - contentType === 'application/octet-stream' || contentType === 'application/pdf' || contentType.includes('font/'); body = isBinary ? decoded : decoded.toString('utf-8'); diff --git a/tests/unit/stack/localStack/aliyunFc.unit.test.ts b/tests/unit/stack/localStack/aliyunFc.unit.test.ts index 2aa2e58..a634c6b 100644 --- a/tests/unit/stack/localStack/aliyunFc.unit.test.ts +++ b/tests/unit/stack/localStack/aliyunFc.unit.test.ts @@ -444,6 +444,104 @@ describe('Aliyun FC Utilities', () => { expect(result.headers['content-type']).toBe('image/png'); }); + it('should preserve JPEG binary body as Buffer', () => { + const jpgData = Buffer.from([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46]); + const fcResponse = { + statusCode: 200, + body: jpgData.toString('base64'), + headers: { 'content-type': 'image/jpeg' }, + isBase64Encoded: true, + }; + + const result = transformFCResponse(fcResponse); + + expect(Buffer.isBuffer(result.body)).toBe(true); + expect((result.body as Buffer).equals(jpgData)).toBe(true); + }); + + it('should preserve audio/mpeg binary body as Buffer', () => { + const fcResponse = { + statusCode: 200, + body: Buffer.from([0xff, 0xfb]).toString('base64'), + headers: { 'content-type': 'audio/mpeg' }, + isBase64Encoded: true, + }; + + const result = transformFCResponse(fcResponse); + + expect(Buffer.isBuffer(result.body)).toBe(true); + }); + + it('should preserve video/mp4 binary body as Buffer', () => { + const fcResponse = { + statusCode: 200, + body: Buffer.from([0x00, 0x00, 0x00, 0x18]).toString('base64'), + headers: { 'content-type': 'video/mp4' }, + isBase64Encoded: true, + }; + + const result = transformFCResponse(fcResponse); + + expect(Buffer.isBuffer(result.body)).toBe(true); + }); + + it('should preserve application/pdf binary body as Buffer', () => { + const pdfData = Buffer.from([0x25, 0x50, 0x44, 0x46]); + const fcResponse = { + statusCode: 200, + body: pdfData.toString('base64'), + headers: { 'content-type': 'application/pdf' }, + isBase64Encoded: true, + }; + + const result = transformFCResponse(fcResponse); + + expect(Buffer.isBuffer(result.body)).toBe(true); + expect((result.body as Buffer).equals(pdfData)).toBe(true); + }); + + it('should convert text/plain base64 body to UTF-8 string (not Buffer)', () => { + const fcResponse = { + statusCode: 200, + body: Buffer.from('hello world').toString('base64'), + headers: { 'content-type': 'text/plain' }, + isBase64Encoded: true, + }; + + const result = transformFCResponse(fcResponse); + + expect(typeof result.body).toBe('string'); + expect(result.body).toBe('hello world'); + }); + + it('should convert application/json base64 body to string, then JSON-parse', () => { + const jsonStr = '{"key":"value"}'; + const fcResponse = { + statusCode: 200, + body: Buffer.from(jsonStr).toString('base64'), + headers: { 'content-type': 'application/json' }, + isBase64Encoded: true, + }; + + const result = transformFCResponse(fcResponse); + + expect(result.body).toEqual({ key: 'value' }); + }); + + it('should NOT treat application/octet-stream as binary (too generic)', () => { + const fcResponse = { + statusCode: 200, + body: Buffer.from('buffer response').toString('base64'), + headers: { 'content-type': 'application/octet-stream' }, + isBase64Encoded: true, + }; + + const result = transformFCResponse(fcResponse); + + expect(typeof result.body).toBe('string'); + expect(result.body).toBe('buffer response'); + }); + it('should still JSON-parse body when Content-Type is application/json', () => { const fcResponse = { statusCode: 200, From 106d50a8163c380dac78a359914d5df6a5f89fe9 Mon Sep 17 00:00:00 2001 From: blankll Date: Tue, 30 Jun 2026 16:21:29 +0800 Subject: [PATCH 3/3] test: add missing coverage for font/ headers=null and isBase64Encoded=false branches - font/woff2 and font/ttf binary content-type tests (Buffer preservation) - headers=null with base64 body fallback to string - isBase64Encoded=false with binary Content-Type passthrough This brings transformFCResponse binary detection to 100% branch coverage. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../stack/localStack/aliyunFc.unit.test.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/unit/stack/localStack/aliyunFc.unit.test.ts b/tests/unit/stack/localStack/aliyunFc.unit.test.ts index a634c6b..9f7e4d6 100644 --- a/tests/unit/stack/localStack/aliyunFc.unit.test.ts +++ b/tests/unit/stack/localStack/aliyunFc.unit.test.ts @@ -542,6 +542,64 @@ describe('Aliyun FC Utilities', () => { expect(result.body).toBe('buffer response'); }); + it('should preserve font/woff2 binary body as Buffer', () => { + const woffData = Buffer.from([0x77, 0x4f, 0x46, 0x46]); + const fcResponse = { + statusCode: 200, + body: woffData.toString('base64'), + headers: { 'content-type': 'font/woff2' }, + isBase64Encoded: true, + }; + + const result = transformFCResponse(fcResponse); + + expect(Buffer.isBuffer(result.body)).toBe(true); + expect((result.body as Buffer).equals(woffData)).toBe(true); + }); + + it('should preserve font/ttf binary body as Buffer', () => { + const ttfData = Buffer.from([0x00, 0x01, 0x00, 0x00, 0x00]); + const fcResponse = { + statusCode: 200, + body: ttfData.toString('base64'), + headers: { 'content-type': 'font/ttf' }, + isBase64Encoded: true, + }; + + const result = transformFCResponse(fcResponse); + + expect(Buffer.isBuffer(result.body)).toBe(true); + expect((result.body as Buffer).equals(ttfData)).toBe(true); + }); + + it('should fall back to string when headers is null with base64 body', () => { + const fcResponse = { + statusCode: 200, + body: Buffer.from('some text').toString('base64'), + headers: null, + isBase64Encoded: true, + }; + + const result = transformFCResponse(fcResponse); + + expect(typeof result.body).toBe('string'); + expect(result.body).toBe('some text'); + }); + + it('should skip base64 decode when isBase64Encoded is false despite binary Content-Type', () => { + const fcResponse = { + statusCode: 200, + body: 'some raw string body', + headers: { 'content-type': 'image/png' }, + isBase64Encoded: false, + }; + + const result = transformFCResponse(fcResponse); + + expect(typeof result.body).toBe('string'); + expect(result.body).toBe('some raw string body'); + }); + it('should still JSON-parse body when Content-Type is application/json', () => { const fcResponse = { statusCode: 200,