Skip to content

Commit e2b2166

Browse files
committed
fs: defer path resolution in realpath functions
In `fs.realpath` and `fs.realpathSync`, path resolution is now performed after symlink traversal instead of before, ensuring correct behavior when dealing with parent directory references (`..`) in paths containing symlinks. Fixes: #60779
1 parent 15bdf38 commit e2b2166

File tree

2 files changed

+44
-2
lines changed

2 files changed

+44
-2
lines changed

lib/fs.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2681,7 +2681,6 @@ function realpathSync(p, options) {
26812681
p += '';
26822682
}
26832683
validatePath(p);
2684-
p = pathModule.resolve(p);
26852684

26862685
const cache = options[realpathCacheKey];
26872686
const maybeCachedResult = cache?.get(p);
@@ -2800,6 +2799,8 @@ function realpathSync(p, options) {
28002799
}
28012800
}
28022801

2802+
p = pathModule.resolve(p);
2803+
28032804
cache?.set(original, p);
28042805
return encodeRealpathResult(p, options);
28052806
}
@@ -2842,7 +2843,6 @@ function realpath(p, options, callback) {
28422843
p += '';
28432844
}
28442845
validatePath(p);
2845-
p = pathModule.resolve(p);
28462846

28472847
const seenLinks = new SafeMap();
28482848
const knownHard = new SafeSet();
@@ -2875,6 +2875,7 @@ function realpath(p, options, callback) {
28752875
function LOOP() {
28762876
// Stop if scanned past end of path
28772877
if (pos >= p.length) {
2878+
p = pathModule.resolve(p);
28782879
return callback(null, encodeRealpathResult(p, options));
28792880
}
28802881

@@ -2896,6 +2897,7 @@ function realpath(p, options, callback) {
28962897
if (knownHard.has(base)) {
28972898
if (isFileType(statValues, S_IFIFO) ||
28982899
isFileType(statValues, S_IFSOCK)) {
2900+
p = pathModule.resolve(p);
28992901
return callback(null, encodeRealpathResult(p, options));
29002902
}
29012903
return process.nextTick(LOOP);

test/parallel/test-fs-realpath.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ function asynctest(testBlock, args, callback, assertBlock) {
8585

8686
// sub-tests:
8787
function test_simple_error_callback(realpath, realpathSync, cb) {
88+
console.log('test_simple_error_callback');
8889
realpath('/this/path/does/not/exist', common.mustCall(function(err, s) {
8990
assert(err);
9091
assert(!s);
@@ -93,6 +94,7 @@ function test_simple_error_callback(realpath, realpathSync, cb) {
9394
}
9495

9596
function test_simple_error_cb_with_null_options(realpath, realpathSync, cb) {
97+
console.log('test_simple_error_cb_with_null_options');
9698
realpath('/this/path/does/not/exist', null, common.mustCall(function(err, s) {
9799
assert(err);
98100
assert(!s);
@@ -382,6 +384,43 @@ function test_non_symlinks(realpath, realpathSync, callback) {
382384
});
383385
}
384386

387+
function test_parent_after_symlink(realpath, realpathSync, callback) {
388+
console.log('test_parent_after_symlink');
389+
if (skipSymlinks) {
390+
common.printSkipMessage('symlink test (no privs)');
391+
return callback();
392+
}
393+
394+
const folder = tmp('folder');
395+
const nested = path.join(folder, 'nested');
396+
const symlinkDir = tmp('symlink-dir');
397+
398+
try {
399+
fs.mkdirSync(folder);
400+
fs.mkdirSync(nested);
401+
} catch {
402+
// Continue regardless of error.
403+
}
404+
405+
try { fs.unlinkSync(symlinkDir); } catch {
406+
// Continue regardless of error.
407+
}
408+
409+
fs.symlinkSync(nested, symlinkDir, 'dir');
410+
unlink.push(symlinkDir);
411+
unlink.push(nested);
412+
unlink.push(folder);
413+
414+
const a = realpathSync(`${symlinkDir}/..`);
415+
const b = realpathSync(folder);
416+
417+
assertEqualPath(a, b);
418+
419+
asynctest(realpath, [`${symlinkDir}/..`], callback, (err, res) => {
420+
assertEqualPath(res, b);
421+
});
422+
}
423+
385424
const upone = path.join(process.cwd(), '..');
386425
function test_escape_cwd(realpath, realpathSync, cb) {
387426
console.log('test_escape_cwd');
@@ -579,6 +618,7 @@ const tests = [
579618
test_up_multiple_with_null_options,
580619
test_root,
581620
test_root_with_null_options,
621+
test_parent_after_symlink,
582622
];
583623
const numtests = tests.length;
584624
let testsRun = 0;

0 commit comments

Comments
 (0)