diff --git a/lib/fs.js b/lib/fs.js index ace5c464b73b14..78f931060742ad 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -2730,9 +2730,21 @@ function realpathSync(p, options) { // Walk down the path, swapping out linked path parts for their real // values // NB: p.length changes. - while (pos < p.length) { - // find the next part - const result = nextPart(p, pos); + let unresolvedTail = ''; + while (true) { + if (pos >= p.length) { + if (unresolvedTail === '') { + break; + } + + p = pathModule.resolve(p + unresolvedTail); + unresolvedTail = ''; + current = base = splitRoot(p); + pos = current.length; + continue; + } + + const result = nextPart(p + unresolvedTail, pos); previous = current; if (result === -1) { const last = StringPrototypeSlice(p, pos); @@ -2740,7 +2752,7 @@ function realpathSync(p, options) { base = previous + last; pos = p.length; } else { - current += StringPrototypeSlice(p, pos, result + 1); + current += StringPrototypeSlice(p + unresolvedTail, pos, result + 1); base = previous + StringPrototypeSlice(p, pos, result); pos = result + 1; } @@ -2761,7 +2773,6 @@ function realpathSync(p, options) { } else { // Use stats array directly to avoid creating an fs.Stats instance just // for our internal use. - const stats = binding.lstat(base, true, undefined, true /* throwIfNoEntry */); if (stats === undefined) { return; @@ -2770,6 +2781,11 @@ function realpathSync(p, options) { if (!isFileType(stats, S_IFLNK)) { knownHard.add(base); cache?.set(base, base); + if (unresolvedTail !== '') { + p = pathModule.resolve(p + unresolvedTail); + unresolvedTail = ''; + } + continue; } @@ -2789,8 +2805,14 @@ function realpathSync(p, options) { binding.stat(base, false, undefined, true); linkTarget = binding.readlink(base, undefined); } - resolvedLink = pathModule.resolve(previous, linkTarget); + const nextPathDelimiterIndex = nextPart(linkTarget, 0); + if (nextPathDelimiterIndex >= 1) { + unresolvedTail = StringPrototypeSlice(linkTarget, nextPathDelimiterIndex) + unresolvedTail; + linkTarget = StringPrototypeSlice(linkTarget, 0, nextPathDelimiterIndex); + } + + resolvedLink = pathModule.resolve(previous, linkTarget); cache?.set(base, resolvedLink); if (!isWindows) seenLinks.set(id, linkTarget); } diff --git a/test/parallel/test-fs-realpath-dotdot-symlink.js b/test/parallel/test-fs-realpath-dotdot-symlink.js new file mode 100644 index 00000000000000..bc44dd7846620b --- /dev/null +++ b/test/parallel/test-fs-realpath-dotdot-symlink.js @@ -0,0 +1,30 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); + +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); + +if (common.isWindows) { + common.skip('symlink semantics differ on Windows'); +} + +tmpdir.refresh(); + +const cwd = path.join(tmpdir.path, 'test'); +fs.mkdirSync(path.join(cwd, 'a', 'b', 'c'), { recursive: true }); +fs.mkdirSync(path.join(cwd, 'a', 'b', 'd'), { recursive: true }); + +process.chdir(cwd); + +// ln -s a/b/c c +fs.symlinkSync('a/b/c', 'c', 'dir'); +// ln -s "c/../d" d +fs.symlinkSync('c/../d', 'd', 'dir'); + +const expected = path.resolve(path.join(cwd, 'a', 'b', 'd')); + +// sync +assert.strictEqual(fs.realpathSync('d'), expected);