From 46fe35083e2676e31c4e0a81639dce6da7aaa356 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 26 Jul 2021 16:10:30 -0700 Subject: [PATCH] Remove paths from dirCache when no longer dirs --- lib/unpack.js | 23 ++++++++++++++++++++++ test/unpack.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/lib/unpack.js b/lib/unpack.js index 1d86c185..3a29a651 100644 --- a/lib/unpack.js +++ b/lib/unpack.js @@ -414,6 +414,20 @@ class Unpack extends Parser { // check if a thing is there, and if so, try to clobber it [CHECKFS] (entry) { this[PEND]() + + // 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) return this[ONERROR](er, entry) @@ -475,6 +489,15 @@ 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) if (er) return this[ONERROR](er, entry) diff --git a/test/unpack.js b/test/unpack.js index e190dfcb..a0756a8b 100644 --- a/test/unpack.js +++ b/test/unpack.js @@ -2493,3 +2493,55 @@ t.test('do not reuse hardlinks, only nlink=1 files', t => { t.end() }) + +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], [ + 'Cannot extract through symbolic link', + ]) + t.end() + } + t.test('async', t => { + const path = dir + '/async' + new Unpack({ cwd: path }) + .on('warn', (msg) => WARNINGS[path] = [msg]) + .on('end', () => check(t, path)) + .end(data) + }) + t.test('sync', t => { + const path = dir + '/sync' + new UnpackSync({ cwd: path }) + .on('warn', (msg) => WARNINGS[path] = [msg]) + .end(data) + check(t, path) + }) +})