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. 225
      lib/fs.js
  2. 20
      test/simple/test-fs-realpath.js

225
lib/fs.js

@ -469,132 +469,119 @@ var path = require('path');
var normalize = path.normalize; var normalize = path.normalize;
var normalizeArray = path.normalizeArray; var normalizeArray = path.normalizeArray;
fs.realpathSync = function (path) { // realpath
var seen_links = {}, knownHards = {}, buf, i = 0, part, x, stats; // Not using realpath(2) because it's bad.
if (path.charAt(0) !== '/') { // See: http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
var cwd = process.cwd().split('/'); fs.realpathSync = realpathSync;
path = cwd.concat(path.split('/')); fs.realpath = realpath;
path = normalizeArray(path); function realpathSync (p) {
i = cwd.length; if (p.charAt(0) !== '/') {
buf = [].concat(cwd); p = path.join(process.cwd(), p);
} else { }
path = normalizeArray(path.split('/')); p = p.split('/');
buf = ['']; var buf = [ '' ];
} var seenLinks = {};
for (; i<path.length; i++) { var knownHard = {};
part = path.slice(0, i+1).join('/'); // walk down the path, swapping out linked pathparts for their real
if (part.length !== 0) { // values, and pushing non-link path bits onto the buffer.
if (part in knownHards) { // then return the buffer.
buf.push(path[i]); // NB: path.length changes.
} else { for (var i = 0; i < p.length; i ++) {
stats = fs.lstatSync(part); // skip over empty path parts.
if (stats.isSymbolicLink()) { if (p[i] === '') continue;
x = stats.dev.toString(32)+":"+stats.ino.toString(32); var part = buf.join('/')+'/'+p[i];
if (x in seen_links) if (knownHard[part]) {
throw new Error("cyclic link at "+part); buf.push( p[i] );
seen_links[x] = true; continue;
part = fs.readlinkSync(part); }
if (part.charAt(0) === '/') { var stat = fs.lstatSync(part);
// absolute if (!stat.isSymbolicLink()) {
path = normalizeArray(part.split('/')); // not a symlink. easy.
buf = ['']; knownHard[ part ] = true;
i = 0; buf.push(p[i]);
} else { continue;
// 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;
}
}
} }
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('/'); return buf.join('/');
} }
function realpath (p, cb) {
if (p.charAt(0) !== '/') {
fs.realpath = function (path, callback) { p = path.join(process.cwd(), p);
var seen_links = {}, knownHards = {}, buf = [''], i = 0, part, x; }
if (path.charAt(0) !== '/') { p = p.split('/');
// assumes cwd is canonical var buf = [ '' ];
var cwd = process.cwd().split('/'); var seenLinks = {};
path = cwd.concat(path.split('/')); var knownHard = {};
path = normalizeArray(path); // walk down the path, swapping out linked pathparts for their real
i = cwd.length-1; // values, and pushing non-link path bits onto the buffer.
buf = [].concat(cwd); // then return the buffer.
} else { // NB: path.length changes.
path = normalizeArray(path.split('/')); var i = 0;
} var part;
function done(err) { LOOP();
if (callback) { function LOOP () {
if (!err) callback(err, buf.join('/')); i ++;
else callback(err); 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(); function gotStat (er, stat) {
part = path.slice(0, i+1).join('/'); if (er) return cb(er);
if (part.length === 0) return next(); if (!stat.isSymbolicLink()) {
if (part in knownHards) { // not a symlink. easy.
buf.push(path[i]); knownHard[ part ] = true;
next(); buf.push(p[i]);
} else { return process.nextTick(LOOP);
fs.lstat(part, function(err, stats){ }
if (err) return done(err); var id = stat.dev.toString(32)+':'+stat.ino.toString(32);
if (stats.isSymbolicLink()) { if (seenLinks[id]) return cb(new Error("cyclic link at "+part));
x = stats.dev.toString(32)+":"+stats.ino.toString(32); seenLinks[id] = true;
if (x in seen_links) fs.readlink(part, gotTarget);
return done(new Error("cyclic link at "+part)); }
seen_links[x] = true; function gotTarget (er, target) {
fs.readlink(part, function(err, npart){ if (er) return cb(er);
if (err) return done(err); if (target.charAt(0) === '/') {
part = npart; // absolute. Start over.
if (part.charAt(0) === '/') { buf = [''];
// absolute p = path.normalizeArray(target.split('/'));
path = normalizeArray(part.split('/')); i = 0;
buf = ['']; return process.nextTick(LOOP);
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
} }
// 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(); function exit () {
}; cb(null, buf.join('/') || '/');
}
}
var pool; var pool;
function allocNewPool () { 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) { function asynctest(testBlock, args, callback, assertBlock) {
async_expected++; async_expected++;
testBlock.apply(testBlock, args.concat([function(err){ testBlock.apply(testBlock, args.concat(function(err){
var ignoreError = false; var ignoreError = false;
if (assertBlock) { if (assertBlock) {
try { try {
ignoreError = assertBlock.apply(assertBlock, ignoreError = assertBlock.apply(assertBlock, arguments);
Array.prototype.slice.call(arguments));
} }
catch (e) { catch (e) {
err = e; err = e;
@ -19,7 +18,7 @@ function asynctest(testBlock, args, callback, assertBlock) {
} }
async_completed++; async_completed++;
callback(ignoreError ? null : err); callback(ignoreError ? null : err);
}])); }));
} }
function bashRealpath(path, callback) { 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 = [ var tests = [
@ -238,6 +249,7 @@ var tests = [
test_relative_input_cwd, test_relative_input_cwd,
test_deep_symlink_mix, test_deep_symlink_mix,
test_non_symlinks, test_non_symlinks,
test_escape_cwd
]; ];
var numtests = tests.length; var numtests = tests.length;
function runNextTest(err) { function runNextTest(err) {

Loading…
Cancel
Save