From 4dd8767597d0e996c5dd3d6407bc2978c8fe70da Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Tue, 24 Mar 2026 09:38:22 +0100 Subject: [PATCH] fix Dechunk HTTP Response leaks terminating chunk and trailers into output The loop did not break on chunkSize === 0, causing the terminating chunk's trailing metadata (CRLF or trailer headers) to be appended to the decoded output. Now break on the zero-length terminating chunk and return only the decoded body. closes #2247 --- src/core/operations/DechunkHTTPResponse.mjs | 5 +- tests/operations/index.mjs | 1 + .../operations/tests/DechunkHTTPResponse.mjs | 66 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 tests/operations/tests/DechunkHTTPResponse.mjs diff --git a/src/core/operations/DechunkHTTPResponse.mjs b/src/core/operations/DechunkHTTPResponse.mjs index da2eb437bd..40b97c7a07 100644 --- a/src/core/operations/DechunkHTTPResponse.mjs +++ b/src/core/operations/DechunkHTTPResponse.mjs @@ -45,12 +45,15 @@ class DechunkHTTPResponse extends Operation { const lineEndingsLength = lineEndings.length; let chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16); while (!isNaN(chunkSize)) { + if (chunkSize === 0) { + break; + } chunks.push(input.slice(chunkSizeEnd, chunkSize + chunkSizeEnd)); input = input.slice(chunkSizeEnd + chunkSize + lineEndingsLength); chunkSizeEnd = input.indexOf(lineEndings) + lineEndingsLength; chunkSize = parseInt(input.slice(0, chunkSizeEnd), 16); } - return chunks.join("") + input; + return chunks.join(""); } } diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index f030349d2a..b9dc0bfae2 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -61,6 +61,7 @@ import "./tests/CRCChecksum.mjs"; import "./tests/Crypt.mjs"; import "./tests/CSV.mjs"; import "./tests/DateTime.mjs"; +import "./tests/DechunkHTTPResponse.mjs"; import "./tests/DefangIP.mjs"; import "./tests/DisassembleARM.mjs"; import "./tests/DropNthBytes.mjs"; diff --git a/tests/operations/tests/DechunkHTTPResponse.mjs b/tests/operations/tests/DechunkHTTPResponse.mjs new file mode 100644 index 0000000000..2a678c8926 --- /dev/null +++ b/tests/operations/tests/DechunkHTTPResponse.mjs @@ -0,0 +1,66 @@ +/** + * DechunkHTTPResponse operation tests. + * + * @author Willi Ballenthin + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Dechunk HTTP response: CRLF line endings", + input: "7\r\nMozilla\r\n9\r\nDeveloper\r\n7\r\nNetwork\r\n0\r\n\r\n", + expectedOutput: "MozillaDeveloperNetwork", + recipeConfig: [ + { + op: "Dechunk HTTP response", + args: [], + }, + ], + }, + { + name: "Dechunk HTTP response: LF line endings", + input: "7\nMozilla\n9\nDeveloper\n7\nNetwork\n0\n\n", + expectedOutput: "MozillaDeveloperNetwork", + recipeConfig: [ + { + op: "Dechunk HTTP response", + args: [], + }, + ], + }, + { + name: "Dechunk HTTP response: single chunk", + input: "5\r\nHello\r\n0\r\n\r\n", + expectedOutput: "Hello", + recipeConfig: [ + { + op: "Dechunk HTTP response", + args: [], + }, + ], + }, + { + name: "Dechunk HTTP response: trailing headers discarded", + input: "7\nMozilla\n9\nDeveloper\n7\nNetwork\n0\nExpires: Wed, 21 Oct 2015 07:28:00 GMT\n", + expectedOutput: "MozillaDeveloperNetwork", + recipeConfig: [ + { + op: "Dechunk HTTP response", + args: [], + }, + ], + }, + { + name: "Dechunk HTTP response: hex chunk sizes", + input: "a\r\n0123456789\r\n0\r\n\r\n", + expectedOutput: "0123456789", + recipeConfig: [ + { + op: "Dechunk HTTP response", + args: [], + }, + ], + }, +]);