@@ -3339,6 +3339,25 @@ if (isWindows) {
33393339 } ;
33403340}
33413341
3342+ // realpath(3) applies `..` to the resolved parent, so on POSIX `.` and `..`
3343+ // are kept in the path while walking it. Windows applies `..` lexically, both
3344+ // in the path being opened and in symlink targets, so the path and the link
3345+ // targets are normalized up front there, matching `fs.realpathSync.native()`.
3346+ function toRealpathInput ( p ) {
3347+ if ( isWindows ) return pathModule . resolve ( p ) ;
3348+ return pathModule . isAbsolute ( p ) ? p : `${ pathModule . resolve ( ) } /${ p } ` ;
3349+ }
3350+
3351+ function joinRealpathLink ( previous , linkTarget ) {
3352+ if ( isWindows ) return pathModule . resolve ( previous , linkTarget ) ;
3353+ return pathModule . isAbsolute ( linkTarget ) ? linkTarget : `${ previous } ${ linkTarget } ` ;
3354+ }
3355+
3356+ function joinRealpathRemainder ( resolvedLink , remainder ) {
3357+ if ( isWindows ) return pathModule . resolve ( resolvedLink , remainder ) ;
3358+ return remainder . length === 0 ? resolvedLink : `${ resolvedLink } /${ remainder } ` ;
3359+ }
3360+
33423361/**
33433362 * Returns the resolved pathname.
33443363 * @param {string | Buffer | URL } p
@@ -3357,7 +3376,7 @@ function realpathSync(p, options) {
33573376 p += '' ;
33583377 }
33593378 validatePath ( p ) ;
3360- p = pathModule . resolve ( p ) ;
3379+ p = toRealpathInput ( p ) ;
33613380
33623381 // Check the resolved input path up front so that the lstat walk below cannot
33633382 // be used to probe the existence of paths outside the granted set.
@@ -3371,14 +3390,14 @@ function realpathSync(p, options) {
33713390 }
33723391
33733392 const cache = options [ realpathCacheKey ] ;
3374- const maybeCachedResult = cache ?. get ( p ) ;
3393+ const original = p ;
3394+ const maybeCachedResult = cache ?. get ( original ) ;
33753395 if ( maybeCachedResult ) {
33763396 return maybeCachedResult ;
33773397 }
33783398
33793399 const seenLinks = new SafeMap ( ) ;
33803400 const knownHard = new SafeSet ( ) ;
3381- const original = p ;
33823401 // Whether the symlink this walk resolved last pointed at a pipe or a
33833402 // socket, which is where the walk stops. It cannot be read back from the
33843403 // shared stat buffer, which holds the last stat made anywhere in the
@@ -3414,6 +3433,9 @@ function realpathSync(p, options) {
34143433 while ( pos < p . length ) {
34153434 // find the next part
34163435 const result = nextPart ( p , pos ) ;
3436+ const part = result === - 1 ?
3437+ StringPrototypeSlice ( p , pos ) :
3438+ StringPrototypeSlice ( p , pos , result ) ;
34173439 previous = current ;
34183440 if ( result === - 1 ) {
34193441 const last = StringPrototypeSlice ( p , pos ) ;
@@ -3426,6 +3448,19 @@ function realpathSync(p, options) {
34263448 pos = result + 1 ;
34273449 }
34283450
3451+ if ( part === '' || part === '.' ) {
3452+ current = previous ;
3453+ continue ;
3454+ }
3455+ if ( part === '..' ) {
3456+ base = pathModule . dirname ( previous ) ;
3457+ current = base ;
3458+ if ( current [ current . length - 1 ] !== CHAR_FORWARD_SLASH &&
3459+ current [ current . length - 1 ] !== CHAR_BACKWARD_SLASH ) {
3460+ current += pathModule . sep ;
3461+ }
3462+ continue ;
3463+ }
34293464 // Continue if not a symlink, break if a pipe/socket
34303465 if ( knownHard . has ( base ) || cache ?. get ( base ) === base ) {
34313466 if ( reachedPipeOrSocket ) {
@@ -3471,14 +3506,18 @@ function realpathSync(p, options) {
34713506 isFileType ( targetStats , S_IFSOCK ) ;
34723507 linkTarget = binding . readlink ( base , undefined ) ;
34733508 }
3474- resolvedLink = pathModule . resolve ( previous , linkTarget ) ;
3509+ resolvedLink = joinRealpathLink ( previous , linkTarget ) ;
34753510
3476- cache ?. set ( base , resolvedLink ) ;
3511+ // On POSIX, a target containing `..` resolves relative to the path
3512+ // walked before it, so it cannot be cached by the link alone.
3513+ if ( isWindows || StringPrototypeIndexOf ( linkTarget , '..' ) === - 1 ) {
3514+ cache ?. set ( base , pathModule . resolve ( resolvedLink ) ) ;
3515+ }
34773516 if ( ! isWindows ) seenLinks . set ( id , linkTarget ) ;
34783517 }
34793518
34803519 // Resolve the link, then start over
3481- p = pathModule . resolve ( resolvedLink , StringPrototypeSlice ( p , pos ) ) ;
3520+ p = joinRealpathRemainder ( resolvedLink , StringPrototypeSlice ( p , pos ) ) ;
34823521
34833522 // Skip over roots
34843523 current = base = splitRoot ( p ) ;
@@ -3495,6 +3534,7 @@ function realpathSync(p, options) {
34953534 }
34963535 }
34973536
3537+ p = pathModule . resolve ( p ) ;
34983538 cache ?. set ( original , p ) ;
34993539 return encodeRealpathResult ( p , options ) ;
35003540}
@@ -3547,7 +3587,7 @@ function realpath(p, options, callback) {
35473587 p += '' ;
35483588 }
35493589 validatePath ( p ) ;
3550- p = pathModule . resolve ( p ) ;
3590+ p = toRealpathInput ( p ) ;
35513591
35523592 const seenLinks = new SafeMap ( ) ;
35533593 const knownHard = new SafeSet ( ) ;
@@ -3585,11 +3625,14 @@ function realpath(p, options, callback) {
35853625 function LOOP ( ) {
35863626 // Stop if scanned past end of path
35873627 if ( pos >= p . length ) {
3588- return callback ( null , encodeRealpathResult ( p , options ) ) ;
3628+ return callback ( null , encodeRealpathResult ( pathModule . resolve ( p ) , options ) ) ;
35893629 }
35903630
35913631 // find the next part
35923632 const result = nextPart ( p , pos ) ;
3633+ const part = result === - 1 ?
3634+ StringPrototypeSlice ( p , pos ) :
3635+ StringPrototypeSlice ( p , pos , result ) ;
35933636 previous = current ;
35943637 if ( result === - 1 ) {
35953638 const last = StringPrototypeSlice ( p , pos ) ;
@@ -3602,10 +3645,23 @@ function realpath(p, options, callback) {
36023645 pos = result + 1 ;
36033646 }
36043647
3648+ if ( part === '' || part === '.' ) {
3649+ current = previous ;
3650+ return process . nextTick ( LOOP ) ;
3651+ }
3652+ if ( part === '..' ) {
3653+ base = pathModule . dirname ( previous ) ;
3654+ current = base ;
3655+ if ( current [ current . length - 1 ] !== CHAR_FORWARD_SLASH &&
3656+ current [ current . length - 1 ] !== CHAR_BACKWARD_SLASH ) {
3657+ current += pathModule . sep ;
3658+ }
3659+ return process . nextTick ( LOOP ) ;
3660+ }
36053661 // Continue if not a symlink, break if a pipe/socket
36063662 if ( knownHard . has ( base ) ) {
36073663 if ( reachedPipeOrSocket ) {
3608- return callback ( null , encodeRealpathResult ( p , options ) ) ;
3664+ return callback ( null , encodeRealpathResult ( pathModule . resolve ( p ) , options ) ) ;
36093665 }
36103666 return process . nextTick ( LOOP ) ;
36113667 }
@@ -3649,12 +3705,12 @@ function realpath(p, options, callback) {
36493705 function gotTarget ( err , target ) {
36503706 if ( err ) return callback ( err ) ;
36513707
3652- gotResolvedLink ( pathModule . resolve ( previous , target ) ) ;
3708+ gotResolvedLink ( joinRealpathLink ( previous , target ) ) ;
36533709 }
36543710
36553711 function gotResolvedLink ( resolvedLink ) {
36563712 // Resolve the link, then start over
3657- p = pathModule . resolve ( resolvedLink , StringPrototypeSlice ( p , pos ) ) ;
3713+ p = joinRealpathRemainder ( resolvedLink , StringPrototypeSlice ( p , pos ) ) ;
36583714 current = base = splitRoot ( p ) ;
36593715 pos = current . length ;
36603716
0 commit comments