Fix issue #262. Allow fs.realpath to traverse above the current working directory.
This commit is contained in:
parent
9a6d2c35a1
commit
ba0c32e2e1
219
lib/fs.js
219
lib/fs.js
@ -469,132 +469,119 @@ var path = require('path');
|
||||
var normalize = path.normalize;
|
||||
var normalizeArray = path.normalizeArray;
|
||||
|
||||
fs.realpathSync = function (path) {
|
||||
var seen_links = {}, knownHards = {}, buf, i = 0, part, x, stats;
|
||||
if (path.charAt(0) !== '/') {
|
||||
var cwd = process.cwd().split('/');
|
||||
path = cwd.concat(path.split('/'));
|
||||
path = normalizeArray(path);
|
||||
i = cwd.length;
|
||||
buf = [].concat(cwd);
|
||||
} else {
|
||||
path = normalizeArray(path.split('/'));
|
||||
buf = [''];
|
||||
// realpath
|
||||
// Not using realpath(2) because it's bad.
|
||||
// See: http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
|
||||
fs.realpathSync = realpathSync;
|
||||
fs.realpath = realpath;
|
||||
function realpathSync (p) {
|
||||
if (p.charAt(0) !== '/') {
|
||||
p = path.join(process.cwd(), p);
|
||||
}
|
||||
for (; i<path.length; i++) {
|
||||
part = path.slice(0, i+1).join('/');
|
||||
if (part.length !== 0) {
|
||||
if (part in knownHards) {
|
||||
buf.push(path[i]);
|
||||
} else {
|
||||
stats = fs.lstatSync(part);
|
||||
if (stats.isSymbolicLink()) {
|
||||
x = stats.dev.toString(32)+":"+stats.ino.toString(32);
|
||||
if (x in seen_links)
|
||||
throw new Error("cyclic link at "+part);
|
||||
seen_links[x] = true;
|
||||
part = fs.readlinkSync(part);
|
||||
if (part.charAt(0) === '/') {
|
||||
// absolute
|
||||
path = normalizeArray(part.split('/'));
|
||||
buf = [''];
|
||||
i = 0;
|
||||
} else {
|
||||
// relative
|
||||
Array.prototype.splice.apply(path, [i, 1].concat(part.split('/')));
|
||||
part = normalizeArray(path);
|
||||
var y = 0, L = Math.max(path.length, part.length), delta;
|
||||
for (; y<L && path[y] === part[y]; y++);
|
||||
if (y !== L) {
|
||||
path = part;
|
||||
delta = i-y;
|
||||
i = y-1;
|
||||
if (delta > 0) buf.splice(y, delta);
|
||||
} else {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buf.push(path[i]);
|
||||
knownHards[buf.join('/')] = true;
|
||||
}
|
||||
}
|
||||
p = p.split('/');
|
||||
var buf = [ '' ];
|
||||
var seenLinks = {};
|
||||
var knownHard = {};
|
||||
// walk down the path, swapping out linked pathparts for their real
|
||||
// values, and pushing non-link path bits onto the buffer.
|
||||
// then return the buffer.
|
||||
// NB: path.length changes.
|
||||
for (var i = 0; i < p.length; i ++) {
|
||||
// skip over empty path parts.
|
||||
if (p[i] === '') continue;
|
||||
var part = buf.join('/')+'/'+p[i];
|
||||
if (knownHard[part]) {
|
||||
buf.push( p[i] );
|
||||
continue;
|
||||
}
|
||||
var stat = fs.lstatSync(part);
|
||||
if (!stat.isSymbolicLink()) {
|
||||
// not a symlink. easy.
|
||||
knownHard[ part ] = true;
|
||||
buf.push(p[i]);
|
||||
continue;
|
||||
}
|
||||
var id = stat.dev.toString(32)+':'+stat.ino.toString(32);
|
||||
if (seenLinks[id]) throw new Error("cyclic link at "+part);
|
||||
seenLinks[id] = true;
|
||||
var target = fs.readlinkSync(part);
|
||||
if (target.charAt(0) === '/') {
|
||||
// absolute. Start over.
|
||||
buf = [''];
|
||||
p = path.normalizeArray(target.split('/'));
|
||||
i = 0;
|
||||
continue;
|
||||
}
|
||||
// not absolute. join and splice.
|
||||
target = target.split('/');
|
||||
Array.prototype.splice.apply(p, [i, 1].concat(target));
|
||||
p = path.normalizeArray(p);
|
||||
i = 0;
|
||||
buf = [''];
|
||||
}
|
||||
return buf.join('/');
|
||||
}
|
||||
|
||||
|
||||
fs.realpath = function (path, callback) {
|
||||
var seen_links = {}, knownHards = {}, buf = [''], i = 0, part, x;
|
||||
if (path.charAt(0) !== '/') {
|
||||
// assumes cwd is canonical
|
||||
var cwd = process.cwd().split('/');
|
||||
path = cwd.concat(path.split('/'));
|
||||
path = normalizeArray(path);
|
||||
i = cwd.length-1;
|
||||
buf = [].concat(cwd);
|
||||
} else {
|
||||
path = normalizeArray(path.split('/'));
|
||||
function realpath (p, cb) {
|
||||
if (p.charAt(0) !== '/') {
|
||||
p = path.join(process.cwd(), p);
|
||||
}
|
||||
function done(err) {
|
||||
if (callback) {
|
||||
if (!err) callback(err, buf.join('/'));
|
||||
else callback(err);
|
||||
p = p.split('/');
|
||||
var buf = [ '' ];
|
||||
var seenLinks = {};
|
||||
var knownHard = {};
|
||||
// walk down the path, swapping out linked pathparts for their real
|
||||
// values, and pushing non-link path bits onto the buffer.
|
||||
// then return the buffer.
|
||||
// NB: path.length changes.
|
||||
var i = 0;
|
||||
var part;
|
||||
LOOP();
|
||||
function LOOP () {
|
||||
i ++;
|
||||
if (!(i < p.length)) return exit();
|
||||
// skip over empty path parts.
|
||||
if (p[i] === '') return process.nextTick(LOOP);
|
||||
part = buf.join('/')+'/'+p[i];
|
||||
if (knownHard[part]) {
|
||||
buf.push( p[i] );
|
||||
return process.nextTick(LOOP);
|
||||
}
|
||||
return fs.lstat(part, gotStat);
|
||||
}
|
||||
function next() {
|
||||
if (++i === path.length) return done();
|
||||
part = path.slice(0, i+1).join('/');
|
||||
if (part.length === 0) return next();
|
||||
if (part in knownHards) {
|
||||
buf.push(path[i]);
|
||||
next();
|
||||
} else {
|
||||
fs.lstat(part, function(err, stats){
|
||||
if (err) return done(err);
|
||||
if (stats.isSymbolicLink()) {
|
||||
x = stats.dev.toString(32)+":"+stats.ino.toString(32);
|
||||
if (x in seen_links)
|
||||
return done(new Error("cyclic link at "+part));
|
||||
seen_links[x] = true;
|
||||
fs.readlink(part, function(err, npart){
|
||||
if (err) return done(err);
|
||||
part = npart;
|
||||
if (part.charAt(0) === '/') {
|
||||
// absolute
|
||||
path = normalizeArray(part.split('/'));
|
||||
buf = [''];
|
||||
i = 0;
|
||||
} else {
|
||||
// relative
|
||||
Array.prototype.splice.apply(path, [i, 1].concat(part.split('/')));
|
||||
part = normalizeArray(path);
|
||||
var y = 0, L = Math.max(path.length, part.length), delta;
|
||||
for (; y<L && path[y] === part[y]; y++);
|
||||
if (y !== L) {
|
||||
path = part;
|
||||
delta = i-y;
|
||||
i = y-1; // resolve new node if needed
|
||||
if (delta > 0) buf.splice(y, delta);
|
||||
}
|
||||
else {
|
||||
i--; // resolve new node if needed
|
||||
}
|
||||
}
|
||||
next();
|
||||
}); // binding.readlink
|
||||
}
|
||||
else {
|
||||
buf.push(path[i]);
|
||||
knownHards[buf.join('/')] = true;
|
||||
next();
|
||||
}
|
||||
}); // binding.lstat
|
||||
function gotStat (er, stat) {
|
||||
if (er) return cb(er);
|
||||
if (!stat.isSymbolicLink()) {
|
||||
// not a symlink. easy.
|
||||
knownHard[ part ] = true;
|
||||
buf.push(p[i]);
|
||||
return process.nextTick(LOOP);
|
||||
}
|
||||
var id = stat.dev.toString(32)+':'+stat.ino.toString(32);
|
||||
if (seenLinks[id]) return cb(new Error("cyclic link at "+part));
|
||||
seenLinks[id] = true;
|
||||
fs.readlink(part, gotTarget);
|
||||
}
|
||||
next();
|
||||
};
|
||||
function gotTarget (er, target) {
|
||||
if (er) return cb(er);
|
||||
if (target.charAt(0) === '/') {
|
||||
// absolute. Start over.
|
||||
buf = [''];
|
||||
p = path.normalizeArray(target.split('/'));
|
||||
i = 0;
|
||||
return process.nextTick(LOOP);
|
||||
}
|
||||
// not absolute. join and splice.
|
||||
target = target.split('/');
|
||||
Array.prototype.splice.apply(p, [i, 1].concat(target));
|
||||
p = path.normalizeArray(p);
|
||||
i = 0;
|
||||
buf = [''];
|
||||
return process.nextTick(LOOP);
|
||||
}
|
||||
function exit () {
|
||||
cb(null, buf.join('/') || '/');
|
||||
}
|
||||
}
|
||||
|
||||
var pool;
|
||||
function allocNewPool () {
|
||||
|
@ -6,12 +6,11 @@ var async_completed = 0, async_expected = 0, unlink = [];
|
||||
|
||||
function asynctest(testBlock, args, callback, assertBlock) {
|
||||
async_expected++;
|
||||
testBlock.apply(testBlock, args.concat([function(err){
|
||||
testBlock.apply(testBlock, args.concat(function(err){
|
||||
var ignoreError = false;
|
||||
if (assertBlock) {
|
||||
try {
|
||||
ignoreError = assertBlock.apply(assertBlock,
|
||||
Array.prototype.slice.call(arguments));
|
||||
ignoreError = assertBlock.apply(assertBlock, arguments);
|
||||
}
|
||||
catch (e) {
|
||||
err = e;
|
||||
@ -19,7 +18,7 @@ function asynctest(testBlock, args, callback, assertBlock) {
|
||||
}
|
||||
async_completed++;
|
||||
callback(ignoreError ? null : err);
|
||||
}]));
|
||||
}));
|
||||
}
|
||||
|
||||
function bashRealpath(path, callback) {
|
||||
@ -227,6 +226,18 @@ function test_non_symlinks(callback) {
|
||||
});
|
||||
}
|
||||
|
||||
var upone = path.join(process.cwd(), "..");
|
||||
function test_escape_cwd (cb) {
|
||||
asynctest(fs.realpath, [".."], cb, function(er, uponeActual){
|
||||
assert.equal(upone, uponeActual,
|
||||
"realpath('..') expected: "+upone+" actual:"+uponeActual);
|
||||
})
|
||||
}
|
||||
var uponeActual = fs.realpathSync("..");
|
||||
assert.equal(upone, uponeActual,
|
||||
"realpathSync('..') expected: "+upone+" actual:"+uponeActual);
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var tests = [
|
||||
@ -238,6 +249,7 @@ var tests = [
|
||||
test_relative_input_cwd,
|
||||
test_deep_symlink_mix,
|
||||
test_non_symlinks,
|
||||
test_escape_cwd
|
||||
];
|
||||
var numtests = tests.length;
|
||||
function runNextTest(err) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user