From ba0c32e2e1aa873728a54df12a3785091f475143 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 8 Sep 2010 17:25:59 -0700 Subject: [PATCH] Fix issue #262. Allow fs.realpath to traverse above the current working directory. --- lib/fs.js | 225 +++++++++++++++----------------- test/simple/test-fs-realpath.js | 20 ++- 2 files changed, 122 insertions(+), 123 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index f164a454fc..5cdbb1cd1c 100644 --- a/lib/fs.js +++ b/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 0) buf.splice(y, delta); - } else { - i--; - } - } - } else { - buf.push(path[i]); - knownHards[buf.join('/')] = true; - } - } +// 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; + 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 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); } - } - 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 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 + 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; + 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(); -}; + function exit () { + cb(null, buf.join('/') || '/'); + } +} var pool; function allocNewPool () { diff --git a/test/simple/test-fs-realpath.js b/test/simple/test-fs-realpath.js index 7ff4ea5300..c845e8cbe2 100644 --- a/test/simple/test-fs-realpath.js +++ b/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) {