diff --git a/lib/strip-absolute-path.js b/lib/strip-absolute-path.js new file mode 100644 index 00000000..49161ddc --- /dev/null +++ b/lib/strip-absolute-path.js @@ -0,0 +1,14 @@ +// unix absolute paths are also absolute on win32, so we use this for both +const { isAbsolute, parse } = require('path').win32 + +// returns [root, stripped] +module.exports = path => { + let r = '' + while (isAbsolute(path)) { + // windows will think that //x/y/z has a "root" of //x/y/ + const root = path.charAt(0) === '/' ? '/' : parse(path).root + path = path.substr(root.length) + r += root + } + return [r, path] +} diff --git a/lib/unpack.js b/lib/unpack.js index 7d4b79d9..216fa71b 100644 --- a/lib/unpack.js +++ b/lib/unpack.js @@ -14,6 +14,7 @@ const path = require('path') const mkdir = require('./mkdir.js') const wc = require('./winchars.js') const pathReservations = require('./path-reservations.js') +const stripAbsolutePath = require('./strip-absolute-path.js') const ONENTRY = Symbol('onEntry') const CHECKFS = Symbol('checkFs') @@ -224,11 +225,10 @@ class Unpack extends Parser { // absolutes on posix are also absolutes on win32 // so we only need to test this one to get both - if (path.win32.isAbsolute(p)) { - const parsed = path.win32.parse(p) - entry.path = p.substr(parsed.root.length) - const r = parsed.root - this.warn('TAR_ENTRY_INFO', `stripping ${r} from absolute path`, { + const [root, stripped] = stripAbsolutePath(p) + if (root) { + entry.path = stripped + this.warn('TAR_ENTRY_INFO', `stripping ${root} from absolute path`, { entry, path: p, }) diff --git a/lib/write-entry.js b/lib/write-entry.js index 1d0b746c..0301759a 100644 --- a/lib/write-entry.js +++ b/lib/write-entry.js @@ -23,6 +23,7 @@ const CLOSE = Symbol('close') const MODE = Symbol('mode') const warner = require('./warn-mixin.js') const winchars = require('./winchars.js') +const stripAbsolutePath = require('./strip-absolute-path.js') const modeFix = require('./mode-fix.js') @@ -52,12 +53,12 @@ const WriteEntry = warner(class WriteEntry extends MiniPass { this.on('warn', opt.onwarn) let pathWarn = false - if (!this.preservePaths && path.win32.isAbsolute(p)) { - // absolutes on posix are also absolutes on win32 - // so we only need to test this one to get both - const parsed = path.win32.parse(p) - this.path = p.substr(parsed.root.length) - pathWarn = parsed.root + if (!this.preservePaths) { + const [root, stripped] = stripAbsolutePath(this.path) + if (root) { + this.path = stripped + pathWarn = root + } } this.win32 = !!opt.win32 || process.platform === 'win32' @@ -351,10 +352,12 @@ const WriteEntryTar = warner(class WriteEntryTar extends MiniPass { this.on('warn', opt.onwarn) let pathWarn = false - if (path.isAbsolute(this.path) && !this.preservePaths) { - const parsed = path.parse(this.path) - pathWarn = parsed.root - this.path = this.path.substr(parsed.root.length) + if (!this.preservePaths) { + const [root, stripped] = stripAbsolutePath(this.path) + if (root) { + this.path = stripped + pathWarn = root + } } this.remain = readEntry.size diff --git a/test/strip-absolute-path.js b/test/strip-absolute-path.js new file mode 100644 index 00000000..beb057ff --- /dev/null +++ b/test/strip-absolute-path.js @@ -0,0 +1,14 @@ +const t = require('tap') +const stripAbsolutePath = require('../lib/strip-absolute-path.js') + +const cases = { + '/': ['/', ''], + '////': ['////', ''], + 'c:///a/b/c': ['c:///', 'a/b/c'], + '\\\\foo\\bar\\baz': ['\\\\foo\\bar\\', 'baz'], + '//foo//bar//baz': ['//', 'foo//bar//baz'], + 'c:\\c:\\c:\\c:\\\\d:\\e/f/g': ['c:\\c:\\c:\\c:\\\\d:\\', 'e/f/g'], +} + +for (const [input, [root, stripped]] of Object.entries(cases)) + t.strictSame(stripAbsolutePath(input), [root, stripped], input) diff --git a/test/unpack.js b/test/unpack.js index cc3538ae..1b2a0334 100644 --- a/test/unpack.js +++ b/test/unpack.js @@ -776,6 +776,9 @@ t.test('absolute paths', t => { }) const absolute = path.resolve(dir, 'd/i/r/absolute') + const root = path.parse(absolute).root + const extraAbsolute = root + root + root + absolute + t.ok(path.isAbsolute(extraAbsolute)) t.ok(path.isAbsolute(absolute)) const parsed = path.parse(absolute) const relative = absolute.substr(parsed.root.length) @@ -783,7 +786,7 @@ t.test('absolute paths', t => { const data = makeTar([ { - path: absolute, + path: extraAbsolute, type: 'File', size: 1, atime: new Date('1979-07-01T19:10:00.000Z'), @@ -798,7 +801,7 @@ t.test('absolute paths', t => { t.test('warn and correct', t => { const check = t => { t.match(warnings, [[ - 'stripping / from absolute path', + `stripping ${root}${root}${root}${root} from absolute path`, { path: absolute, code: 'TAR_ENTRY_INFO' }, ]]) t.ok(fs.lstatSync(path.resolve(dir, relative)).isFile(), 'is file') diff --git a/test/write-entry.js b/test/write-entry.js index 89ddcc89..c177f89e 100644 --- a/test/write-entry.js +++ b/test/write-entry.js @@ -385,7 +385,10 @@ t.test('nonexistent file', t => { }) t.test('absolute path', t => { - const f = path.resolve(files, '512-bytes.txt') + const absolute = path.resolve(files, '512-bytes.txt') + const { root } = path.parse(absolute) + const f = root + root + root + absolute + const warn = root + root + root + root t.test('preservePaths=false strict=false', t => { const warnings = [] const ws = new WriteEntry(f, { @@ -398,13 +401,13 @@ t.test('absolute path', t => { out = Buffer.concat(out) t.equal(out.length, 1024) t.match(warnings, [[ - 'TAR_ENTRY_INFO', /stripping .* from absolute path/, { path: f }, + 'TAR_ENTRY_INFO', `stripping ${warn} from absolute path`, { path: f }, ]]) t.match(ws.header, { cksumValid: true, needPax: false, - path: f.replace(/^(\/|[a-z]:\\\\)/, ''), + path: f.replace(/^(\/|[a-z]:\\\\){4}/, ''), mode: 0o644, size: 512, linkpath: null,