@@ -3340,6 +3340,25 @@ if (isWindows) {
33403340 } ;
33413341}
33423342
3343+ // realpath(3) applies `..` to the resolved parent, so on POSIX `.` and `..`
3344+ // are kept in the path while walking it. Windows applies `..` lexically, both
3345+ // in the path being opened and in symlink targets, so the path and the link
3346+ // targets are normalized up front there, matching `fs.realpathSync.native()`.
3347+ function toRealpathInput ( p ) {
3348+ if ( isWindows ) return pathModule . resolve ( p ) ;
3349+ return pathModule . isAbsolute ( p ) ? p : `${ pathModule . resolve ( ) } /${ p } ` ;
3350+ }
3351+
3352+ function joinRealpathLink ( previous , linkTarget ) {
3353+ if ( isWindows ) return pathModule . resolve ( previous , linkTarget ) ;
3354+ return pathModule . isAbsolute ( linkTarget ) ? linkTarget : `${ previous } ${ linkTarget } ` ;
3355+ }
3356+
3357+ function joinRealpathRemainder ( resolvedLink , remainder ) {
3358+ if ( isWindows ) return pathModule . resolve ( resolvedLink , remainder ) ;
3359+ return remainder . length === 0 ? resolvedLink : `${ resolvedLink } /${ remainder } ` ;
3360+ }
3361+
33433362/**
33443363 * Returns the resolved pathname.
33453364 * @param {string | Buffer | URL } p
@@ -3358,7 +3377,7 @@ function realpathSync(p, options) {
33583377 p += '' ;
33593378 }
33603379 validatePath ( p ) ;
3361- p = pathModule . resolve ( p ) ;
3380+ p = toRealpathInput ( p ) ;
33623381
33633382 // Check the resolved input path up front so that the lstat walk below cannot
33643383 // be used to probe the existence of paths outside the granted set.
@@ -3372,14 +3391,14 @@ function realpathSync(p, options) {
33723391 }
33733392
33743393 const cache = options [ realpathCacheKey ] ;
3375- const maybeCachedResult = cache ?. get ( p ) ;
3394+ const original = p ;
3395+ const maybeCachedResult = cache ?. get ( original ) ;
33763396 if ( maybeCachedResult ) {
33773397 return maybeCachedResult ;
33783398 }
33793399
33803400 const seenLinks = new SafeMap ( ) ;
33813401 const knownHard = new SafeSet ( ) ;
3382- const original = p ;
33833402 // Whether the symlink this walk resolved last pointed at a pipe or a
33843403 // socket, which is where the walk stops. It cannot be read back from the
33853404 // shared stat buffer, which holds the last stat made anywhere in the
@@ -3415,6 +3434,9 @@ function realpathSync(p, options) {
34153434 while ( pos < p . length ) {
34163435 // find the next part
34173436 const result = nextPart ( p , pos ) ;
3437+ const part = result === - 1 ?
3438+ StringPrototypeSlice ( p , pos ) :
3439+ StringPrototypeSlice ( p , pos , result ) ;
34183440 previous = current ;
34193441 if ( result === - 1 ) {
34203442 const last = StringPrototypeSlice ( p , pos ) ;
@@ -3427,6 +3449,19 @@ function realpathSync(p, options) {
34273449 pos = result + 1 ;
34283450 }
34293451
3452+ if ( part === '' || part === '.' ) {
3453+ current = previous ;
3454+ continue ;
3455+ }
3456+ if ( part === '..' ) {
3457+ base = pathModule . dirname ( previous ) ;
3458+ current = base ;
3459+ if ( current [ current . length - 1 ] !== CHAR_FORWARD_SLASH &&
3460+ current [ current . length - 1 ] !== CHAR_BACKWARD_SLASH ) {
3461+ current += pathModule . sep ;
3462+ }
3463+ continue ;
3464+ }
34303465 // Continue if not a symlink, break if a pipe/socket
34313466 if ( knownHard . has ( base ) || cache ?. get ( base ) === base ) {
34323467 if ( reachedPipeOrSocket ) {
@@ -3472,14 +3507,18 @@ function realpathSync(p, options) {
34723507 isFileType ( targetStats , S_IFSOCK ) ;
34733508 linkTarget = binding . readlink ( base , undefined ) ;
34743509 }
3475- resolvedLink = pathModule . resolve ( previous , linkTarget ) ;
3510+ resolvedLink = joinRealpathLink ( previous , linkTarget ) ;
34763511
3477- cache ?. set ( base , resolvedLink ) ;
3512+ // On POSIX, a target containing `..` resolves relative to the path
3513+ // walked before it, so it cannot be cached by the link alone.
3514+ if ( isWindows || StringPrototypeIndexOf ( linkTarget , '..' ) === - 1 ) {
3515+ cache ?. set ( base , pathModule . resolve ( resolvedLink ) ) ;
3516+ }
34783517 if ( ! isWindows ) seenLinks . set ( id , linkTarget ) ;
34793518 }
34803519
34813520 // Resolve the link, then start over
3482- p = pathModule . resolve ( resolvedLink , StringPrototypeSlice ( p , pos ) ) ;
3521+ p = joinRealpathRemainder ( resolvedLink , StringPrototypeSlice ( p , pos ) ) ;
34833522
34843523 // Skip over roots
34853524 current = base = splitRoot ( p ) ;
@@ -3496,6 +3535,7 @@ function realpathSync(p, options) {
34963535 }
34973536 }
34983537
3538+ p = pathModule . resolve ( p ) ;
34993539 cache ?. set ( original , p ) ;
35003540 return encodeRealpathResult ( p , options ) ;
35013541}
@@ -3548,7 +3588,7 @@ function realpath(p, options, callback) {
35483588 p += '' ;
35493589 }
35503590 validatePath ( p ) ;
3551- p = pathModule . resolve ( p ) ;
3591+ p = toRealpathInput ( p ) ;
35523592
35533593 const seenLinks = new SafeMap ( ) ;
35543594 const knownHard = new SafeSet ( ) ;
@@ -3586,11 +3626,14 @@ function realpath(p, options, callback) {
35863626 function LOOP ( ) {
35873627 // Stop if scanned past end of path
35883628 if ( pos >= p . length ) {
3589- return callback ( null , encodeRealpathResult ( p , options ) ) ;
3629+ return callback ( null , encodeRealpathResult ( pathModule . resolve ( p ) , options ) ) ;
35903630 }
35913631
35923632 // find the next part
35933633 const result = nextPart ( p , pos ) ;
3634+ const part = result === - 1 ?
3635+ StringPrototypeSlice ( p , pos ) :
3636+ StringPrototypeSlice ( p , pos , result ) ;
35943637 previous = current ;
35953638 if ( result === - 1 ) {
35963639 const last = StringPrototypeSlice ( p , pos ) ;
@@ -3603,10 +3646,23 @@ function realpath(p, options, callback) {
36033646 pos = result + 1 ;
36043647 }
36053648
3649+ if ( part === '' || part === '.' ) {
3650+ current = previous ;
3651+ return process . nextTick ( LOOP ) ;
3652+ }
3653+ if ( part === '..' ) {
3654+ base = pathModule . dirname ( previous ) ;
3655+ current = base ;
3656+ if ( current [ current . length - 1 ] !== CHAR_FORWARD_SLASH &&
3657+ current [ current . length - 1 ] !== CHAR_BACKWARD_SLASH ) {
3658+ current += pathModule . sep ;
3659+ }
3660+ return process . nextTick ( LOOP ) ;
3661+ }
36063662 // Continue if not a symlink, break if a pipe/socket
36073663 if ( knownHard . has ( base ) ) {
36083664 if ( reachedPipeOrSocket ) {
3609- return callback ( null , encodeRealpathResult ( p , options ) ) ;
3665+ return callback ( null , encodeRealpathResult ( pathModule . resolve ( p ) , options ) ) ;
36103666 }
36113667 return process . nextTick ( LOOP ) ;
36123668 }
@@ -3650,12 +3706,12 @@ function realpath(p, options, callback) {
36503706 function gotTarget ( err , target ) {
36513707 if ( err ) return callback ( err ) ;
36523708
3653- gotResolvedLink ( pathModule . resolve ( previous , target ) ) ;
3709+ gotResolvedLink ( joinRealpathLink ( previous , target ) ) ;
36543710 }
36553711
36563712 function gotResolvedLink ( resolvedLink ) {
36573713 // Resolve the link, then start over
3658- p = pathModule . resolve ( resolvedLink , StringPrototypeSlice ( p , pos ) ) ;
3714+ p = joinRealpathRemainder ( resolvedLink , StringPrototypeSlice ( p , pos ) ) ;
36593715 current = base = splitRoot ( p ) ;
36603716 pos = current . length ;
36613717
0 commit comments