From ac04f8141807d44160d169c498ec757eaa93d316 Mon Sep 17 00:00:00 2001 From: Raashish Aggarwal <94279692+raashish1601@users.noreply.github.com> Date: Mon, 11 May 2026 22:24:37 +0530 Subject: [PATCH] fix: allow lenient chunk-size whitespace --- lib/dispatcher/client-h1.js | 1 + test/parser-issues.js | 59 +++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/lib/dispatcher/client-h1.js b/lib/dispatcher/client-h1.js index f1c52fb5f11..8e65560964c 100644 --- a/lib/dispatcher/client-h1.js +++ b/lib/dispatcher/client-h1.js @@ -211,6 +211,7 @@ class Parser { constructor (client, socket, { exports }) { this.llhttp = exports this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE) + this.llhttp.llhttp_set_lenient_spaces_after_chunk_size(this.ptr, 1) this.client = client /** * @type {import('net').Socket} diff --git a/test/parser-issues.js b/test/parser-issues.js index 4e398cdba01..27d00fa7fe8 100644 --- a/test/parser-issues.js +++ b/test/parser-issues.js @@ -14,6 +14,17 @@ const truncatedChunkedResponse = Buffer.from( 'hel\r\n' ) +const chunkedResponseWithTrailingSpaceAfterChunkSize = Buffer.from( + 'HTTP/1.1 200 OK\r\n' + + 'Transfer-Encoding: chunked\r\n' + + 'Connection: close\r\n' + + '\r\n' + + '7\r\n' + + 'hello, \r\n' + + '0 \r\n' + + '\r\n' +) + function createTrackedServer (onConnection) { const sockets = new Set() const server = net.createServer(socket => { @@ -255,17 +266,18 @@ test('refreshes wasm input view after reallocating parser buffer', async (t) => }) test('truncated chunked responses terminated by EOF error the response body', async (t) => { + const ctx = t t = tspl(t, { plan: 3 }) const server = net.createServer((socket) => { socket.end(truncatedChunkedResponse) }) - after(() => server.close()) + ctx.after(() => server.close()) await new Promise(resolve => server.listen(0, resolve)) const client = new Client(`http://localhost:${server.address().port}`) - after(() => client.destroy()) + ctx.after(() => client.destroy()) client.request({ method: 'GET', @@ -286,13 +298,54 @@ test('truncated chunked responses terminated by EOF error the response body', as await t.completed }) +test('accepts chunked response with spaces after chunk size', async (t) => { + const ctx = t + t = tspl(t, { plan: 2 }) + + const server = net.createServer((socket) => { + socket.end(chunkedResponseWithTrailingSpaceAfterChunkSize) + }) + ctx.after(() => server.close()) + + await new Promise(resolve => server.listen(0, resolve)) + + const client = new Client(`http://localhost:${server.address().port}`) + ctx.after(() => client.destroy()) + + client.request({ + method: 'GET', + path: '/' + }, (err, data) => { + t.ifError(err) + if (err) { + return + } + + const chunks = [] + + data.body + .on('data', chunk => { + chunks.push(chunk) + }) + .on('error', err => { + t.ifError(err) + }) + .on('end', () => { + t.strictEqual(Buffer.concat(chunks).toString(), 'hello, ') + }) + }) + + await t.completed +}) + test('fetch rejects truncated chunked responses terminated by EOF', async (t) => { + const ctx = t t = tspl(t, { plan: 3 }) const server = net.createServer((socket) => { socket.end(truncatedChunkedResponse) }) - after(() => server.close()) + ctx.after(() => server.close()) await new Promise(resolve => server.listen(0, resolve))