diff --git a/node_modules/tar/lib/extract.js b/node_modules/tar/lib/extract.js index f269145edef7a..98e946ec5bfbb 100644 --- a/node_modules/tar/lib/extract.js +++ b/node_modules/tar/lib/extract.js @@ -6,6 +6,7 @@ const Unpack = require('./unpack.js') const fs = require('fs') const fsm = require('fs-minipass') const path = require('path') +const stripSlash = require('./strip-trailing-slashes.js') module.exports = (opt_, files, cb) => { if (typeof opt_ === 'function') @@ -41,7 +42,7 @@ module.exports = (opt_, files, cb) => { // construct a filter that limits the file entries listed // include child entries if a dir is included const filesFilter = (opt, files) => { - const map = new Map(files.map(f => [f.replace(/\/+$/, ''), true])) + const map = new Map(files.map(f => [stripSlash(f), true])) const filter = opt.filter const mapHas = (file, r) => { @@ -55,8 +56,8 @@ const filesFilter = (opt, files) => { } opt.filter = filter - ? (file, entry) => filter(file, entry) && mapHas(file.replace(/\/+$/, '')) - : file => mapHas(file.replace(/\/+$/, '')) + ? (file, entry) => filter(file, entry) && mapHas(stripSlash(file)) + : file => mapHas(stripSlash(file)) } const extractFileSync = opt => { diff --git a/node_modules/tar/lib/list.js b/node_modules/tar/lib/list.js index 702cfea808d42..a0c1cf2fbc7ea 100644 --- a/node_modules/tar/lib/list.js +++ b/node_modules/tar/lib/list.js @@ -9,6 +9,7 @@ const Parser = require('./parse.js') const fs = require('fs') const fsm = require('fs-minipass') const path = require('path') +const stripSlash = require('./strip-trailing-slashes.js') module.exports = (opt_, files, cb) => { if (typeof opt_ === 'function') @@ -54,7 +55,7 @@ const onentryFunction = opt => { // construct a filter that limits the file entries listed // include child entries if a dir is included const filesFilter = (opt, files) => { - const map = new Map(files.map(f => [f.replace(/\/+$/, ''), true])) + const map = new Map(files.map(f => [stripSlash(f), true])) const filter = opt.filter const mapHas = (file, r) => { @@ -68,8 +69,8 @@ const filesFilter = (opt, files) => { } opt.filter = filter - ? (file, entry) => filter(file, entry) && mapHas(file.replace(/\/+$/, '')) - : file => mapHas(file.replace(/\/+$/, '')) + ? (file, entry) => filter(file, entry) && mapHas(stripSlash(file)) + : file => mapHas(stripSlash(file)) } const listFileSync = opt => { diff --git a/node_modules/tar/lib/pack.js b/node_modules/tar/lib/pack.js index 492fe18ec47ee..df7f9f1c8df5b 100644 --- a/node_modules/tar/lib/pack.js +++ b/node_modules/tar/lib/pack.js @@ -134,9 +134,6 @@ const Pack = warner(class Pack extends MiniPass { [ADDTARENTRY] (p) { const absolute = path.resolve(this.cwd, p.path) - if (this.prefix) - p.path = this.prefix + '/' + p.path.replace(/^\.(\/+|$)/, '') - // in this case, we don't have to wait for the stat if (!this.filter(p.path, p)) p.resume() @@ -153,9 +150,6 @@ const Pack = warner(class Pack extends MiniPass { [ADDFSENTRY] (p) { const absolute = path.resolve(this.cwd, p) - if (this.prefix) - p = this.prefix + '/' + p.replace(/^\.(\/+|$)/, '') - this[QUEUE].push(new PackJob(p, absolute)) this[PROCESS]() } @@ -298,6 +292,7 @@ const Pack = warner(class Pack extends MiniPass { statCache: this.statCache, noMtime: this.noMtime, mtime: this.mtime, + prefix: this.prefix, } } @@ -323,10 +318,7 @@ const Pack = warner(class Pack extends MiniPass { if (job.readdir) { job.readdir.forEach(entry => { - const p = this.prefix ? - job.path.slice(this.prefix.length + 1) || './' - : job.path - + const p = job.path const base = p === './' ? '' : p.replace(/\/*$/, '/') this[ADDFSENTRY](base + entry) }) @@ -381,10 +373,7 @@ class PackSync extends Pack { if (job.readdir) { job.readdir.forEach(entry => { - const p = this.prefix ? - job.path.slice(this.prefix.length + 1) || './' - : job.path - + const p = job.path const base = p === './' ? '' : p.replace(/\/*$/, '/') this[ADDFSENTRY](base + entry) }) diff --git a/node_modules/tar/lib/strip-trailing-slashes.js b/node_modules/tar/lib/strip-trailing-slashes.js new file mode 100644 index 0000000000000..f702ed5a5c0ce --- /dev/null +++ b/node_modules/tar/lib/strip-trailing-slashes.js @@ -0,0 +1,24 @@ +// this is the only approach that was significantly faster than using +// str.replace(/\/+$/, '') for strings ending with a lot of / chars and +// containing multiple / chars. +const batchStrings = [ + '/'.repeat(1024), + '/'.repeat(512), + '/'.repeat(256), + '/'.repeat(128), + '/'.repeat(64), + '/'.repeat(32), + '/'.repeat(16), + '/'.repeat(8), + '/'.repeat(4), + '/'.repeat(2), + '/', +] + +module.exports = str => { + for (const s of batchStrings) { + while (str.length >= s.length && str.slice(-1 * s.length) === s) + str = str.slice(0, -1 * s.length) + } + return str +} diff --git a/node_modules/tar/lib/unpack.js b/node_modules/tar/lib/unpack.js index edaf7833cdb5b..282e04f945c50 100644 --- a/node_modules/tar/lib/unpack.js +++ b/node_modules/tar/lib/unpack.js @@ -205,6 +205,8 @@ class Unpack extends Parser { if (parts.length < this.strip) return false entry.path = parts.slice(this.strip).join('/') + if (entry.path === '' && entry.type !== 'Directory' && entry.type !== 'GNUDumpDir') + return false if (entry.type === 'Link') { const linkparts = entry.linkpath.split(/\/|\\/) @@ -333,17 +335,35 @@ class Unpack extends Parser { mode: mode, autoClose: false, }) - stream.on('error', er => this[ONERROR](er, entry)) + stream.on('error', er => { + if (stream.fd) + fs.close(stream.fd, () => {}) + // flush all the data out so that we aren't left hanging + // if the error wasn't actually fatal. otherwise the parse + // is blocked, and we never proceed. + stream.write = () => true + this[ONERROR](er, entry) + fullyDone() + }) let actions = 1 const done = er => { - if (er) - return this[ONERROR](er, entry) + if (er) { + /* istanbul ignore else - we should always have a fd by now */ + if (stream.fd) + fs.close(stream.fd, () => {}) + this[ONERROR](er, entry) + fullyDone() + return + } if (--actions === 0) { fs.close(stream.fd, er => { + if (er) + this[ONERROR](er, entry) + else + this[UNPEND]() fullyDone() - er ? this[ONERROR](er, entry) : this[UNPEND]() }) } } @@ -378,7 +398,10 @@ class Unpack extends Parser { const tx = this.transform ? this.transform(entry) || entry : entry if (tx !== entry) { - tx.on('error', er => this[ONERROR](er, entry)) + tx.on('error', er => { + this[ONERROR](er, entry) + fullyDone() + }) entry.pipe(tx) } tx.pipe(stream) @@ -388,8 +411,9 @@ class Unpack extends Parser { const mode = entry.mode & 0o7777 || this.dmode this[MKDIR](entry.absolute, mode, er => { if (er) { + this[ONERROR](er, entry) fullyDone() - return this[ONERROR](er, entry) + return } let actions = 1 @@ -480,8 +504,9 @@ class Unpack extends Parser { this[MKDIR](path.dirname(entry.absolute), this.dmode, er => { if (er) { + this[ONERROR](er, entry) done() - return this[ONERROR](er, entry) + return } fs.lstat(entry.absolute, (er, st) => { if (st && (this.keep || this.newer && st.mtime > entry.mtime)) { @@ -489,7 +514,6 @@ class Unpack extends Parser { done() } else if (er || this[ISREUSABLE](entry, st)) this[MAKEFS](null, entry, done) - else if (st.isDirectory()) { if (entry.type === 'Directory') { if (!this.noChmod && (!entry.mode || (st.mode & 0o7777) === entry.mode)) @@ -507,8 +531,11 @@ class Unpack extends Parser { } [MAKEFS] (er, entry, done) { - if (er) - return this[ONERROR](er, entry) + if (er) { + this[ONERROR](er, entry) + done() + return + } switch (entry.type) { case 'File': @@ -532,10 +559,12 @@ class Unpack extends Parser { // XXX: get the type ('file' or 'dir') for windows fs[link](linkpath, entry.absolute, er => { if (er) - return this[ONERROR](er, entry) + this[ONERROR](er, entry) + else { + this[UNPEND]() + entry.resume() + } done() - this[UNPEND]() - entry.resume() }) } } diff --git a/node_modules/tar/lib/write-entry.js b/node_modules/tar/lib/write-entry.js index 0301759ad386f..0457f3aefe2a8 100644 --- a/node_modules/tar/lib/write-entry.js +++ b/node_modules/tar/lib/write-entry.js @@ -5,6 +5,13 @@ const Header = require('./header.js') const fs = require('fs') const path = require('path') +const prefixPath = (path, prefix) => { + if (!prefix) + return path + path = path.replace(/^\.([/\\]|$)/, '') + return prefix + '/' + path +} + const maxReadSize = 16 * 1024 * 1024 const PROCESS = Symbol('process') const FILE = Symbol('file') @@ -21,6 +28,9 @@ const OPENFILE = Symbol('openfile') const ONOPENFILE = Symbol('onopenfile') const CLOSE = Symbol('close') const MODE = Symbol('mode') +const AWAITDRAIN = Symbol('awaitDrain') +const ONDRAIN = Symbol('ondrain') +const PREFIX = Symbol('prefix') const warner = require('./warn-mixin.js') const winchars = require('./winchars.js') const stripAbsolutePath = require('./strip-absolute-path.js') @@ -48,6 +58,16 @@ const WriteEntry = warner(class WriteEntry extends MiniPass { this.noPax = !!opt.noPax this.noMtime = !!opt.noMtime this.mtime = opt.mtime || null + this.prefix = opt.prefix || null + + this.fd = null + this.blockLen = null + this.blockRemain = null + this.buf = null + this.offset = null + this.length = null + this.pos = null + this.remain = null if (typeof opt.onwarn === 'function') this.on('warn', opt.onwarn) @@ -117,13 +137,19 @@ const WriteEntry = warner(class WriteEntry extends MiniPass { return modeFix(mode, this.type === 'Directory', this.portable) } + [PREFIX] (path) { + return prefixPath(path, this.prefix) + } + [HEADER] () { if (this.type === 'Directory' && this.portable) this.noMtime = true this.header = new Header({ - path: this.path, - linkpath: this.linkpath, + path: this[PREFIX](this.path), + // only apply the prefix to hard links. + linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath) + : this.linkpath, // only the permissions and setuid/setgid/sticky bitflags // not the higher-order bits that specify file type mode: this[MODE](this.stat.mode), @@ -139,13 +165,14 @@ const WriteEntry = warner(class WriteEntry extends MiniPass { }) if (this.header.encode() && !this.noPax) { - this.write(new Pax({ + super.write(new Pax({ atime: this.portable ? null : this.header.atime, ctime: this.portable ? null : this.header.ctime, gid: this.portable ? null : this.header.gid, mtime: this.noMtime ? null : this.mtime || this.header.mtime, - path: this.path, - linkpath: this.linkpath, + path: this[PREFIX](this.path), + linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath) + : this.linkpath, size: this.header.size, uid: this.portable ? null : this.header.uid, uname: this.portable ? null : this.header.uname, @@ -154,7 +181,7 @@ const WriteEntry = warner(class WriteEntry extends MiniPass { nlink: this.portable ? null : this.stat.nlink, }).encode()) } - this.write(this.header.block) + super.write(this.header.block) } [DIRECTORY] () { @@ -214,74 +241,107 @@ const WriteEntry = warner(class WriteEntry extends MiniPass { } [ONOPENFILE] (fd) { - const blockLen = 512 * Math.ceil(this.stat.size / 512) - const bufLen = Math.min(blockLen, this.maxReadSize) - const buf = Buffer.allocUnsafe(bufLen) - this[READ](fd, buf, 0, buf.length, 0, this.stat.size, blockLen) + this.fd = fd + this.blockLen = 512 * Math.ceil(this.stat.size / 512) + this.blockRemain = this.blockLen + const bufLen = Math.min(this.blockLen, this.maxReadSize) + this.buf = Buffer.allocUnsafe(bufLen) + this.offset = 0 + this.pos = 0 + this.remain = this.stat.size + this.length = this.buf.length + this[READ]() } - [READ] (fd, buf, offset, length, pos, remain, blockRemain) { + [READ] () { + const { fd, buf, offset, length, pos } = this fs.read(fd, buf, offset, length, pos, (er, bytesRead) => { if (er) { // ignoring the error from close(2) is a bad practice, but at // this point we already have an error, don't need another one - return this[CLOSE](fd, () => this.emit('error', er)) + return this[CLOSE](() => this.emit('error', er)) } - this[ONREAD](fd, buf, offset, length, pos, remain, blockRemain, bytesRead) + this[ONREAD](bytesRead) }) } - [CLOSE] (fd, cb) { - fs.close(fd, cb) + [CLOSE] (cb) { + fs.close(this.fd, cb) } - [ONREAD] (fd, buf, offset, length, pos, remain, blockRemain, bytesRead) { - if (bytesRead <= 0 && remain > 0) { + [ONREAD] (bytesRead) { + if (bytesRead <= 0 && this.remain > 0) { const er = new Error('encountered unexpected EOF') er.path = this.absolute er.syscall = 'read' er.code = 'EOF' - return this[CLOSE](fd, () => this.emit('error', er)) + return this[CLOSE](() => this.emit('error', er)) } - if (bytesRead > remain) { + if (bytesRead > this.remain) { const er = new Error('did not encounter expected EOF') er.path = this.absolute er.syscall = 'read' er.code = 'EOF' - return this[CLOSE](fd, () => this.emit('error', er)) + return this[CLOSE](() => this.emit('error', er)) } // null out the rest of the buffer, if we could fit the block padding - if (bytesRead === remain) { - for (let i = bytesRead; i < length && bytesRead < blockRemain; i++) { - buf[i + offset] = 0 + // at the end of this loop, we've incremented bytesRead and this.remain + // to be incremented up to the blockRemain level, as if we had expected + // to get a null-padded file, and read it until the end. then we will + // decrement both remain and blockRemain by bytesRead, and know that we + // reached the expected EOF, without any null buffer to append. + if (bytesRead === this.remain) { + for (let i = bytesRead; i < this.length && bytesRead < this.blockRemain; i++) { + this.buf[i + this.offset] = 0 bytesRead++ - remain++ + this.remain++ } } - const writeBuf = offset === 0 && bytesRead === buf.length ? - buf : buf.slice(offset, offset + bytesRead) - remain -= bytesRead - blockRemain -= bytesRead - pos += bytesRead - offset += bytesRead + const writeBuf = this.offset === 0 && bytesRead === this.buf.length ? + this.buf : this.buf.slice(this.offset, this.offset + bytesRead) - this.write(writeBuf) + const flushed = this.write(writeBuf) + if (!flushed) + this[AWAITDRAIN](() => this[ONDRAIN]()) + else + this[ONDRAIN]() + } + + [AWAITDRAIN] (cb) { + this.once('drain', cb) + } - if (!remain) { - if (blockRemain) - this.write(Buffer.alloc(blockRemain)) - return this[CLOSE](fd, er => er ? this.emit('error', er) : this.end()) + write (writeBuf) { + if (this.blockRemain < writeBuf.length) { + const er = new Error('writing more data than expected') + er.path = this.absolute + return this.emit('error', er) } + this.remain -= writeBuf.length + this.blockRemain -= writeBuf.length + this.pos += writeBuf.length + this.offset += writeBuf.length + return super.write(writeBuf) + } - if (offset >= length) { - buf = Buffer.allocUnsafe(length) - offset = 0 + [ONDRAIN] () { + if (!this.remain) { + if (this.blockRemain) + super.write(Buffer.alloc(this.blockRemain)) + return this[CLOSE](er => er ? this.emit('error', er) : this.end()) } - length = buf.length - offset - this[READ](fd, buf, offset, length, pos, remain, blockRemain) + + if (this.offset >= this.length) { + // if we only have a smaller bit left to read, alloc a smaller buffer + // otherwise, keep it the same length it was before. + this.buf = Buffer.allocUnsafe(Math.min(this.blockRemain, this.buf.length)) + this.offset = 0 + } + this.length = this.buf.length - this.offset + this[READ]() } }) @@ -298,25 +358,30 @@ class WriteEntrySync extends WriteEntry { this[ONOPENFILE](fs.openSync(this.absolute, 'r')) } - [READ] (fd, buf, offset, length, pos, remain, blockRemain) { + [READ] () { let threw = true try { + const { fd, buf, offset, length, pos } = this const bytesRead = fs.readSync(fd, buf, offset, length, pos) - this[ONREAD](fd, buf, offset, length, pos, remain, blockRemain, bytesRead) + this[ONREAD](bytesRead) threw = false } finally { // ignoring the error from close(2) is a bad practice, but at // this point we already have an error, don't need another one if (threw) { try { - this[CLOSE](fd, () => {}) + this[CLOSE](() => {}) } catch (er) {} } } } - [CLOSE] (fd, cb) { - fs.closeSync(fd) + [AWAITDRAIN] (cb) { + cb() + } + + [CLOSE] (cb) { + fs.closeSync(this.fd) cb() } } @@ -336,6 +401,8 @@ const WriteEntryTar = warner(class WriteEntryTar extends MiniPass { if (this.type === 'Directory' && this.portable) this.noMtime = true + this.prefix = opt.prefix || null + this.path = readEntry.path this.mode = this[MODE](readEntry.mode) this.uid = this.portable ? null : readEntry.uid @@ -364,8 +431,9 @@ const WriteEntryTar = warner(class WriteEntryTar extends MiniPass { this.blockRemain = readEntry.startBlockSize this.header = new Header({ - path: this.path, - linkpath: this.linkpath, + path: this[PREFIX](this.path), + linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath) + : this.linkpath, // only the permissions and setuid/setgid/sticky bitflags // not the higher-order bits that specify file type mode: this.mode, @@ -392,8 +460,9 @@ const WriteEntryTar = warner(class WriteEntryTar extends MiniPass { ctime: this.portable ? null : this.ctime, gid: this.portable ? null : this.gid, mtime: this.noMtime ? null : this.mtime, - path: this.path, - linkpath: this.linkpath, + path: this[PREFIX](this.path), + linkpath: this.type === 'Link' ? this[PREFIX](this.linkpath) + : this.linkpath, size: this.size, uid: this.portable ? null : this.uid, uname: this.portable ? null : this.uname, @@ -407,6 +476,10 @@ const WriteEntryTar = warner(class WriteEntryTar extends MiniPass { readEntry.pipe(this) } + [PREFIX] (path) { + return prefixPath(path, this.prefix) + } + [MODE] (mode) { return modeFix(mode, this.type === 'Directory', this.portable) } @@ -421,7 +494,7 @@ const WriteEntryTar = warner(class WriteEntryTar extends MiniPass { end () { if (this.blockRemain) - this.write(Buffer.alloc(this.blockRemain)) + super.write(Buffer.alloc(this.blockRemain)) return super.end() } }) diff --git a/node_modules/tar/package.json b/node_modules/tar/package.json index 1c82ac7291631..fbde61edcc51e 100644 --- a/node_modules/tar/package.json +++ b/node_modules/tar/package.json @@ -2,7 +2,7 @@ "author": "Isaac Z. Schlueter (http://blog.izs.me/)", "name": "tar", "description": "tar for node", - "version": "6.1.2", + "version": "6.1.6", "repository": { "type": "git", "url": "https://github.com/npm/node-tar.git" diff --git a/package-lock.json b/package-lock.json index e124e1fe5338b..9a66b0cd9b98e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -144,7 +144,7 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "ssri": "^8.0.1", - "tar": "^6.1.2", + "tar": "^6.1.6", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "treeverse": "^1.0.4", @@ -9466,9 +9466,9 @@ } }, "node_modules/tar": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.2.tgz", - "integrity": "sha512-EwKEgqJ7nJoS+s8QfLYVGMDmAsj+StbI2AM/RTHeUSsOw6Z8bwNBRv5z3CY0m7laC5qUAqruLX5AhMuc5deY3Q==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.6.tgz", + "integrity": "sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==", "inBundle": true, "dependencies": { "chownr": "^2.0.0", @@ -17412,9 +17412,9 @@ } }, "tar": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.2.tgz", - "integrity": "sha512-EwKEgqJ7nJoS+s8QfLYVGMDmAsj+StbI2AM/RTHeUSsOw6Z8bwNBRv5z3CY0m7laC5qUAqruLX5AhMuc5deY3Q==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.6.tgz", + "integrity": "sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==", "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", diff --git a/package.json b/package.json index cbe1e1c5179ed..4048b40bfd2fd 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "ssri": "^8.0.1", - "tar": "^6.1.2", + "tar": "^6.1.6", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "treeverse": "^1.0.4",