Browse Source

Fix issue #262. Allow fs.realpath to traverse above the current working directory.

v0.7.4-release
isaacs 15 years ago
committed by Ryan Dahl
parent
commit
ba0c32e2e1
  1. 217
      lib/fs.js
  2. 20
      test/simple/test-fs-realpath.js

217
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 = [''];
}
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('/'));
// 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);
}
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;
} 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;
}
}
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 done(err) {
if (callback) {
if (!err) callback(err, buf.join('/'));
else callback(err);
}
}
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('/'));
function realpath (p, cb) {
if (p.charAt(0) !== '/') {
p = path.join(process.cwd(), p);
}
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 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);
}
function gotTarget (er, target) {
if (er) return cb(er);
if (target.charAt(0) === '/') {
// absolute. Start over.
buf = [''];
p = path.normalizeArray(target.split('/'));
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
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);
}
next();
}); // binding.readlink
}
else {
buf.push(path[i]);
knownHards[buf.join('/')] = true;
next();
}
}); // binding.lstat
}
function exit () {
cb(null, buf.join('/') || '/');
}
next();
};
}
var pool;
function allocNewPool () {

20
test/simple/test-fs-realpath.js

@ -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…
Cancel
Save