From 0d4ae13fef7fa9037e5777aed204b7024d1e9367 Mon Sep 17 00:00:00 2001 From: Archkon <180910180+Archkon@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:31:47 +0800 Subject: [PATCH] zlib: restore unzip auto-detection on reset Restore the initial UNZIP mode and clear partial gzip header state when resetting an Unzip stream. Preserve GUNZIP mode for internal resets between concatenated gzip members. Signed-off-by: Archkon <180910180+Archkon@users.noreply.github.com> --- src/node_zlib.cc | 17 +++++++++-- test/parallel/test-zlib-unzip-reset.js | 39 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-zlib-unzip-reset.js diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 95201624cfa2fa..2dbfa27de9f1ea 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -188,7 +188,10 @@ class ZlibContext final : public MemoryRetainer { void SetFlush(int flush); void GetAfterWriteOffsets(uint32_t* avail_in, uint32_t* avail_out) const; CompressionError GetErrorInfo() const; - inline void SetMode(node_zlib_mode mode) { mode_ = mode; } + inline void SetMode(node_zlib_mode mode) { + mode_ = mode; + initial_mode_ = mode; + } CompressionError ResetStream(); // Zlib-specific: @@ -212,6 +215,7 @@ class ZlibContext final : public MemoryRetainer { private: CompressionError ErrorForMessage(const char* message) const; + CompressionError ResetStream(node_zlib_mode target_mode); CompressionError SetDictionary(); bool InitZlib(); @@ -222,6 +226,7 @@ class ZlibContext final : public MemoryRetainer { int level_ = 0; int mem_level_ = 0; node_zlib_mode mode_ = NONE; + node_zlib_mode initial_mode_ = NONE; int strategy_ = 0; int window_bits_ = 0; bool reject_garbage_after_end_ = false; @@ -1144,7 +1149,7 @@ void ZlibContext::DoThreadPoolWork() { // Trailing zero bytes are okay, though, since they are frequently // used for padding. - ResetStream(); + ResetStream(mode_); err_ = inflate(&strm_, flush_); } break; @@ -1209,6 +1214,10 @@ CompressionError ZlibContext::GetErrorInfo() const { CompressionError ZlibContext::ResetStream() { + return ResetStream(initial_mode_); +} + +CompressionError ZlibContext::ResetStream(node_zlib_mode target_mode) { bool first_init_call = InitZlib(); if (first_init_call && err_ != Z_OK) { return ErrorForMessage("Failed to init stream before reset"); @@ -1225,6 +1234,7 @@ CompressionError ZlibContext::ResetStream() { case INFLATE: case INFLATERAW: case GUNZIP: + case UNZIP: err_ = inflateReset(&strm_); break; default: @@ -1234,6 +1244,9 @@ CompressionError ZlibContext::ResetStream() { if (err_ != Z_OK) return ErrorForMessage("Failed to reset stream"); + mode_ = target_mode; + if (mode_ == UNZIP) gzip_id_bytes_read_ = 0; + return SetDictionary(); } diff --git a/test/parallel/test-zlib-unzip-reset.js b/test/parallel/test-zlib-unzip-reset.js new file mode 100644 index 00000000000000..0b20b40c51d027 --- /dev/null +++ b/test/parallel/test-zlib-unzip-reset.js @@ -0,0 +1,39 @@ +'use strict'; + +const common = require('../common'); +const assert = require('node:assert'); +const zlib = require('node:zlib'); + +function testReset(prefix, input, expected) { + const output = []; + const unzip = zlib.createUnzip() + .on('error', common.mustNotCall()) + .on('data', (chunk) => output.push(chunk)) + .on('end', common.mustCall(() => { + assert.strictEqual(Buffer.concat(output).toString(), expected); + })); + + unzip.write(prefix, common.mustCall(() => { + unzip.reset(); + unzip.end(input); + })); +} + +// Reset while gzip auto-detection is halfway through. +testReset( + Buffer.from([0x1f]), + zlib.gzipSync('hello'), + 'hello', +); + +// Reset after auto-detection selected INFLATE. The new input must be detected +// as GUNZIP so that every concatenated gzip member is processed. +testReset( + zlib.deflateSync('discarded').subarray(0, 2), + Buffer.concat([ + zlib.gzipSync('abc'), + zlib.gzipSync('def'), + zlib.gzipSync('ghi'), + ]), + 'abcdefghi', +);