Comparing version 5.0.9 to 5.0.10
@@ -11,4 +11,8 @@ // A path exclusive reservation system | ||
const normPath = require('./normalize-windows-path.js') | ||
const stripSlashes = require('./strip-trailing-slashes.js') | ||
const { join } = require('path') | ||
const platform = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform | ||
const isWindows = platform === 'win32' | ||
module.exports = () => { | ||
@@ -24,6 +28,12 @@ // path => [function or Set] | ||
// return a set of parent dirs for a given path | ||
const getDirs = path => | ||
path.split('/').slice(0, -1).reduce((set, path) => | ||
set.length ? set.concat(normPath(join(set[set.length - 1], path))) | ||
: [path], []) | ||
// '/a/b/c/d' -> ['/', '/a', '/a/b', '/a/b/c', '/a/b/c/d'] | ||
const getDirs = path => { | ||
const dirs = path.split('/').slice(0, -1).reduce((set, path) => { | ||
if (set.length) | ||
path = normPath(join(set[set.length - 1], path)) | ||
set.push(path || '/') | ||
return set | ||
}, []) | ||
return dirs | ||
} | ||
@@ -104,3 +114,14 @@ // functions currently running | ||
const reserve = (paths, fn) => { | ||
paths = paths.map(p => normPath(join(p)).toLowerCase()) | ||
// collide on matches across case and unicode normalization | ||
// On windows, thanks to the magic of 8.3 shortnames, it is fundamentally | ||
// impossible to determine whether two paths refer to the same thing on | ||
// disk, without asking the kernel for a shortname. | ||
// So, we just pretend that every path matches every other path here, | ||
// effectively removing all parallelization on windows. | ||
paths = isWindows ? ['win32 parallelization disabled'] : paths.map(p => { | ||
return stripSlashes(normPath(join(p))) | ||
.normalize('NFKD') | ||
.toLowerCase() | ||
}) | ||
const dirs = new Set( | ||
@@ -107,0 +128,0 @@ paths.map(path => getDirs(path)).reduce((a, b) => a.concat(b)) |
@@ -5,11 +5,21 @@ // unix absolute paths are also absolute on win32, so we use this for both | ||
// returns [root, stripped] | ||
// Note that windows will think that //x/y/z/a has a "root" of //x/y, and in | ||
// those cases, we want to sanitize it to x/y/z/a, not z/a, so we strip / | ||
// explicitly if it's the first character. | ||
// drive-specific relative paths on Windows get their root stripped off even | ||
// though they are not absolute, so `c:../foo` becomes ['c:', '../foo'] | ||
module.exports = path => { | ||
let r = '' | ||
while (isAbsolute(path)) { | ||
let parsed = parse(path) | ||
while (isAbsolute(path) || parsed.root) { | ||
// windows will think that //x/y/z has a "root" of //x/y/ | ||
const root = path.charAt(0) === '/' ? '/' : parse(path).root | ||
// but strip the //?/C:/ off of //?/C:/path | ||
const root = path.charAt(0) === '/' && path.slice(0, 4) !== '//?/' ? '/' | ||
: parsed.root | ||
path = path.substr(root.length) | ||
r += root | ||
parsed = parse(path) | ||
} | ||
return [r, path] | ||
} |
@@ -21,2 +21,3 @@ 'use strict' | ||
const normPath = require('./normalize-windows-path.js') | ||
const stripSlash = require('./strip-trailing-slashes.js') | ||
@@ -26,2 +27,3 @@ const ONENTRY = Symbol('onEntry') | ||
const CHECKFS2 = Symbol('checkFs2') | ||
const PRUNECACHE = Symbol('pruneCache') | ||
const ISREUSABLE = Symbol('isReusable') | ||
@@ -51,2 +53,4 @@ const MAKEFS = Symbol('makeFs') | ||
const getFlag = require('./get-write-flag.js') | ||
const platform = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform | ||
const isWindows = platform === 'win32' | ||
@@ -70,3 +74,3 @@ // Unlinks on Windows are not atomic. | ||
const unlinkFile = (path, cb) => { | ||
if (process.platform !== 'win32') | ||
if (!isWindows) | ||
return fs.unlink(path, cb) | ||
@@ -84,3 +88,3 @@ | ||
const unlinkFileSync = path => { | ||
if (process.platform !== 'win32') | ||
if (!isWindows) | ||
return fs.unlinkSync(path) | ||
@@ -99,9 +103,20 @@ | ||
// clear the cache if it's a case-insensitive unicode-squashing match. | ||
// we can't know if the current file system is case-sensitive or supports | ||
// unicode fully, so we check for similarity on the maximally compatible | ||
// representation. Err on the side of pruning, since all it's doing is | ||
// preventing lstats, and it's not the end of the world if we get a false | ||
// positive. | ||
// Note that on windows, we always drop the entire cache whenever a | ||
// symbolic link is encountered, because 8.3 filenames are impossible | ||
// to reason about, and collisions are hazards rather than just failures. | ||
const cacheKeyNormalize = path => stripSlash(normPath(path)) | ||
.normalize('NFKD') | ||
.toLowerCase() | ||
const pruneCache = (cache, abs) => { | ||
// clear the cache if it's a case-insensitive match, since we can't | ||
// know if the current file system is case-sensitive or not. | ||
abs = normPath(abs).toLowerCase() | ||
abs = cacheKeyNormalize(abs) | ||
for (const path of cache.keys()) { | ||
const plower = path.toLowerCase() | ||
if (plower === abs || plower.toLowerCase().indexOf(abs + '/') === 0) | ||
const pnorm = cacheKeyNormalize(path) | ||
if (pnorm === abs || pnorm.indexOf(abs + '/') === 0) | ||
cache.delete(path) | ||
@@ -111,2 +126,7 @@ } | ||
const dropCache = cache => { | ||
for (const key of cache.keys()) | ||
cache.delete(key) | ||
} | ||
class Unpack extends Parser { | ||
@@ -170,3 +190,3 @@ constructor (opt) { | ||
// turn ><?| in filenames into 0xf000-higher encoded forms | ||
this.win32 = !!opt.win32 || process.platform === 'win32' | ||
this.win32 = !!opt.win32 || isWindows | ||
@@ -237,3 +257,4 @@ // do not unpack over files that are newer than what's in the archive | ||
const p = normPath(entry.path) | ||
if (p.split('/').includes('..')) { | ||
const parts = p.split('/') | ||
if (parts.includes('..') || isWindows && /^[a-z]:\.\.$/i.test(parts[0])) { | ||
this.warn('TAR_ENTRY_ERROR', `path contains '..'`, { | ||
@@ -246,4 +267,3 @@ entry, | ||
// absolutes on posix are also absolutes on win32 | ||
// so we only need to test this one to get both | ||
// strip off the root | ||
const [root, stripped] = stripAbsolutePath(p) | ||
@@ -264,2 +284,18 @@ if (root) { | ||
// if we somehow ended up with a path that escapes the cwd, and we are | ||
// not in preservePaths mode, then something is fishy! This should have | ||
// been prevented above, so ignore this for coverage. | ||
/* istanbul ignore if - defense in depth */ | ||
if (!this.preservePaths && | ||
entry.absolute.indexOf(this.cwd + '/') !== 0 && | ||
entry.absolute !== this.cwd) { | ||
this.warn('TAR_ENTRY_ERROR', 'path escaped extraction target', { | ||
entry, | ||
path: normPath(entry.path), | ||
resolvedPath: entry.absolute, | ||
cwd: this.cwd, | ||
}) | ||
return false | ||
} | ||
// an archive can set properties on the extraction directory, but it | ||
@@ -508,3 +544,3 @@ // may not replace the cwd with a different kind of thing entirely. | ||
st.nlink <= 1 && | ||
process.platform !== 'win32' | ||
!isWindows | ||
} | ||
@@ -520,3 +556,4 @@ | ||
} | ||
[CHECKFS2] (entry, done) { | ||
[PRUNECACHE] (entry) { | ||
// if we are not creating a directory, and the path is in the dirCache, | ||
@@ -526,5 +563,22 @@ // then that means we are about to delete the directory we created | ||
// is any of its children. | ||
if (entry.type !== 'Directory') | ||
// If a symbolic link is encountered on Windows, all bets are off. | ||
// There is no reasonable way to sanitize the cache in such a way | ||
// we will be able to avoid having filesystem collisions. If this | ||
// happens with a non-symlink entry, it'll just fail to unpack, | ||
// but a symlink to a directory, using an 8.3 shortname, can evade | ||
// detection and lead to arbitrary writes to anywhere on the system. | ||
if (isWindows && entry.type === 'SymbolicLink') | ||
dropCache(this.dirCache) | ||
else if (entry.type !== 'Directory') | ||
pruneCache(this.dirCache, entry.absolute) | ||
} | ||
[CHECKFS2] (entry, fullyDone) { | ||
this[PRUNECACHE](entry) | ||
const done = er => { | ||
this[PRUNECACHE](entry) | ||
fullyDone(er) | ||
} | ||
const checkCwd = () => { | ||
@@ -579,3 +633,9 @@ this[MKDIR](this.cwd, this.dmode, er => { | ||
} | ||
// not a dir entry, have to remove it. | ||
// Not a dir entry, have to remove it. | ||
// NB: the only way to end up with an entry that is the cwd | ||
// itself, in such a way that == does not detect, is a | ||
// tricky windows absolute path with UNC or 8.3 parts (and | ||
// preservePaths:true, or else it will have been stripped). | ||
// In that case, the user has opted out of path protections | ||
// explicitly, so if they blow away the cwd, c'est la vie. | ||
if (entry.absolute !== this.cwd) { | ||
@@ -655,4 +715,3 @@ return fs.rmdir(entry.absolute, er => | ||
[CHECKFS] (entry) { | ||
if (entry.type !== 'Directory') | ||
pruneCache(this.dirCache, entry.absolute) | ||
this[PRUNECACHE](entry) | ||
@@ -706,3 +765,3 @@ if (!this[CHECKED_CWD]) { | ||
[FILE] (entry, _) { | ||
[FILE] (entry, done) { | ||
const mode = entry.mode & 0o7777 || this.fmode | ||
@@ -719,2 +778,3 @@ | ||
this[ONERROR](er || closeError, entry) | ||
done() | ||
} | ||
@@ -780,7 +840,10 @@ | ||
[DIRECTORY] (entry, _) { | ||
[DIRECTORY] (entry, done) { | ||
const mode = entry.mode & 0o7777 || this.dmode | ||
const er = this[MKDIR](entry.absolute, mode) | ||
if (er) | ||
return this[ONERROR](er, entry) | ||
if (er) { | ||
this[ONERROR](er, entry) | ||
done() | ||
return | ||
} | ||
if (entry.mtime && !this.noMtime) { | ||
@@ -796,2 +859,3 @@ try { | ||
} | ||
done() | ||
entry.resume() | ||
@@ -819,5 +883,6 @@ } | ||
[LINK] (entry, linkpath, link, _) { | ||
[LINK] (entry, linkpath, link, done) { | ||
try { | ||
fs[link + 'Sync'](linkpath, entry.absolute) | ||
done() | ||
entry.resume() | ||
@@ -824,0 +889,0 @@ } catch (er) { |
@@ -5,3 +5,3 @@ { | ||
"description": "tar for node", | ||
"version": "5.0.9", | ||
"version": "5.0.10", | ||
"publishConfig": { | ||
@@ -8,0 +8,0 @@ "tag": "v5-legacy" |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
159561
3564
18