diff --git a/lib/unpack.js b/lib/unpack.js index 216fa71b..edaf7833 100644 --- a/lib/unpack.js +++ b/lib/unpack.js @@ -465,6 +465,19 @@ class Unpack extends Parser { } [CHECKFS2] (entry, done) { + // if we are not creating a directory, and the path is in the dirCache, + // then that means we are about to delete the directory we created + // previously, and it is no longer going to be a directory, and neither + // is any of its children. + if (entry.type !== 'Directory') { + for (const path of this.dirCache.keys()) { + if (path === entry.absolute || + path.indexOf(entry.absolute + '/') === 0 || + path.indexOf(entry.absolute + '\\') === 0) + this.dirCache.delete(path) + } + } + this[MKDIR](path.dirname(entry.absolute), this.dmode, er => { if (er) { done() @@ -529,6 +542,15 @@ class Unpack extends Parser { class UnpackSync extends Unpack { [CHECKFS] (entry) { + if (entry.type !== 'Directory') { + for (const path of this.dirCache.keys()) { + if (path === entry.absolute || + path.indexOf(entry.absolute + '/') === 0 || + path.indexOf(entry.absolute + '\\') === 0) + this.dirCache.delete(path) + } + } + const er = this[MKDIR](path.dirname(entry.absolute), this.dmode, neverCalled) if (er) return this[ONERROR](er, entry) diff --git a/test/unpack.js b/test/unpack.js index 1b2a0334..db7c8f54 100644 --- a/test/unpack.js +++ b/test/unpack.js @@ -2605,3 +2605,56 @@ t.test('handle errors on fs.close', t => { cwd: dir + '/sync', strict: true, }).end(data), poop, 'sync') }) + +t.test('drop entry from dirCache if no longer a directory', t => { + const dir = path.resolve(unpackdir, 'dir-cache-error') + mkdirp.sync(dir + '/sync/y') + mkdirp.sync(dir + '/async/y') + const data = makeTar([ + { + path: 'x', + type: 'Directory', + }, + { + path: 'x', + type: 'SymbolicLink', + linkpath: './y', + }, + { + path: 'x/ginkoid', + type: 'File', + size: 'ginkoid'.length, + }, + 'ginkoid', + '', + '', + ]) + t.plan(2) + const WARNINGS = {} + const check = (t, path) => { + t.equal(fs.statSync(path + '/x').isDirectory(), true) + t.equal(fs.lstatSync(path + '/x').isSymbolicLink(), true) + t.equal(fs.statSync(path + '/y').isDirectory(), true) + t.strictSame(fs.readdirSync(path + '/y'), []) + t.throws(() => fs.readFileSync(path + '/x/ginkoid'), { code: 'ENOENT' }) + t.strictSame(WARNINGS[path], [ + 'TAR_ENTRY_ERROR', + 'Cannot extract through symbolic link', + ]) + t.end() + } + t.test('async', t => { + const path = dir + '/async' + new Unpack({ cwd: path }) + .on('warn', (code, msg) => WARNINGS[path] = [code, msg]) + .on('end', () => check(t, path)) + .end(data) + }) + t.test('sync', t => { + const path = dir + '/sync' + new UnpackSync({ cwd: path }) + .on('warn', (code, msg) => WARNINGS[path] = [code, msg]) + .end(data) + check(t, path) + }) +})