diff --git a/node_stream_zip.js b/node_stream_zip.js index ce5667e..0a9af79 100644 --- a/node_stream_zip.js +++ b/node_stream_zip.js @@ -667,6 +667,7 @@ StreamZip.debugLog = (...args) => { util.inherits(StreamZip, events.EventEmitter); const propZip = Symbol('zip'); +const propZipInstance = Symbol('zipInstance'); StreamZip.async = class StreamZipAsync extends events.EventEmitter { constructor(config) { @@ -677,6 +678,7 @@ StreamZip.async = class StreamZipAsync extends events.EventEmitter { zip.on('entry', (entry) => this.emit('entry', entry)); zip.on('extract', (entry, outPath) => this.emit('extract', entry, outPath)); + this[propZipInstance] = zip; this[propZip] = new Promise((resolve, reject) => { zip.on('ready', () => { zip.removeListener('error', reject); @@ -746,9 +748,9 @@ StreamZip.async = class StreamZipAsync extends events.EventEmitter { } async close() { - const zip = await this[propZip]; + await this[propZip].catch(() => {}); return new Promise((resolve, reject) => { - zip.close((err) => { + this[propZipInstance].close((err) => { if (err) { reject(err); } else { diff --git a/release-notes.md b/release-notes.md index 9bb3b10..d2e5df6 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,9 @@ Release notes ------------- +##### v1.16.1 (2026-09-15) +`-` fix #96, #94: file handle leak in the async API when the archive could not be read +`*` async `close()` now resolves instead of rejecting when the archive could not be read, matching the callback API + ##### v1.16.0 (2026-07-22) `+` fixed reading compressed entry size to prevent exploitation reported by @DumDumSu `*` updated dependencies diff --git a/test/tests.js b/test/tests.js index a814b2e..97bc071 100644 --- a/test/tests.js +++ b/test/tests.js @@ -485,6 +485,32 @@ module.exports['async.entryData'] = async (test) => { test.done(); }; +module.exports['async.close'] = async (test) => { + test.expect(2); + const fd = fs.openSync('test/ok/normal.zip', 'r'); + const zip = new StreamZip.async({ fd }); + test.equal(await zip.entriesCount, 10); + await zip.close(); + test.throws(() => fs.fstatSync(fd), /EBADF/); + test.done(); +}; + +module.exports['async.close on a bad archive'] = async (test) => { + test.expect(3); + const fd = fs.openSync('test/err/rar.rar', 'r'); + const zip = new StreamZip.async({ fd }); + try { + await zip.entries(); + test.ok(false, 'Should throw an error'); + } catch (e) { + test.equal(e.message, 'Bad archive'); + } + test.ok(fs.fstatSync(fd)); + await zip.close(); + test.throws(() => fs.fstatSync(fd), /EBADF/); + test.done(); +}; + module.exports.setUp = function (done) { testPathTmp = basePathTmp + testNum++ + '/'; if (!fs.existsSync(basePathTmp)) {