From 9eb04822a89bc6760d81dadd45d5dde52787b5d1 Mon Sep 17 00:00:00 2001 From: agustin18 Date: Wed, 23 Sep 2026 18:53:20 +0000 Subject: [PATCH] fix(listener): avoid mutating caller header object when setting Content-Length --- src/listener.ts | 14 +++++++++++--- test/server.test.ts | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/listener.ts b/src/listener.ts index c8c2763..9d40e2f 100644 --- a/src/listener.ts +++ b/src/listener.ts @@ -226,12 +226,20 @@ const responseViaCache = async ( // in `responseViaCache`, if body is not stream, Transfer-Encoding is considered not chunked if (!hasContentLength) { + let contentLength: number | undefined if (typeof body === 'string') { - header['Content-Length'] = Buffer.byteLength(body) + contentLength = Buffer.byteLength(body) } else if (body instanceof Uint8Array) { - header['Content-Length'] = body.byteLength + contentLength = body.byteLength } else if (body instanceof Blob) { - header['Content-Length'] = body.size + contentLength = body.size + } + + if (contentLength !== undefined) { + header = { + ...header, + 'Content-Length': contentLength, + } } } diff --git a/test/server.test.ts b/test/server.test.ts index 387353d..bce8261 100644 --- a/test/server.test.ts +++ b/test/server.test.ts @@ -130,6 +130,43 @@ describe('Basic', () => { const res = await requestServer(server, { method: 'TRACE', path: '/' }) expect(await res.text()).toBe('headers: {}') }) + + it.each([ + { name: 'string', body: 'hello', expectedLen: '5' }, + { name: 'Uint8Array', body: new Uint8Array([104, 105]), expectedLen: '2' }, + { name: 'Blob', body: new Blob(['blob-test']), expectedLen: '9' }, + ])( + 'Should not mutate caller header object when setting Content-Length with $name body', + async ({ body, expectedLen }) => { + const customHeaders: Record = { 'x-custom': 'val' } + const testApp = new Hono() + testApp.get('/test', () => new Response(body, { headers: customHeaders })) + const testServer = createAdaptorServer(testApp) + + const res1 = await requestServer(testServer, { method: 'GET', path: '/test' }) + expect(res1.status).toBe(200) + expect(res1.headers.get('content-length')).toBe(expectedLen) + expect(customHeaders).toEqual({ 'x-custom': 'val' }) + expect('Content-Length' in customHeaders).toBe(false) + + const res2 = await requestServer(testServer, { method: 'GET', path: '/test' }) + expect(res2.status).toBe(200) + expect(res2.headers.get('content-length')).toBe(expectedLen) + expect('Content-Length' in customHeaders).toBe(false) + } + ) + + it('Should handle Object.freeze headers without error', async () => { + const frozenHeaders = Object.freeze({ 'x-frozen': 'true' }) + const testApp = new Hono() + testApp.get('/frozen', () => new Response('frozen', { headers: frozenHeaders })) + const testServer = createAdaptorServer(testApp) + + const res = await requestServer(testServer, { method: 'GET', path: '/frozen' }) + expect(res.status).toBe(200) + expect(await res.text()).toBe('frozen') + expect(res.headers.get('content-length')).toBe('6') + }) }) describe('various response body types', () => {