@@ -132,6 +132,14 @@ const {
132132const {
133133 CHAR_FORWARD_SLASH ,
134134 CHAR_BACKWARD_SLASH ,
135+ CHAR_COLON ,
136+ CHAR_QUESTION_MARK ,
137+ CHAR_UPPERCASE_A ,
138+ CHAR_UPPERCASE_C ,
139+ CHAR_UPPERCASE_Z ,
140+ CHAR_LOWERCASE_A ,
141+ CHAR_LOWERCASE_N ,
142+ CHAR_LOWERCASE_Z ,
135143} = require ( 'internal/constants' ) ;
136144const {
137145 isInt32,
@@ -2628,6 +2636,43 @@ function unwatchFile(filename, listener) {
26282636}
26292637
26302638
2639+ // Strips the Windows extended-length path prefix (\\?\) from a resolved path.
2640+ // Extended-length paths (\\?\C:\... or \\?\UNC\...) are a Win32 API mechanism
2641+ // to bypass MAX_PATH limits. Node.js should handle them transparently by
2642+ // converting to standard paths for internal processing. The \\?\ prefix is
2643+ // re-added when needed via path.toNamespacedPath() before system calls.
2644+ // See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
2645+ function stripExtendedPathPrefix ( p ) {
2646+ // Check for \\?\ prefix
2647+ if ( p . length >= 4 &&
2648+ StringPrototypeCharCodeAt ( p , 0 ) === CHAR_BACKWARD_SLASH &&
2649+ StringPrototypeCharCodeAt ( p , 1 ) === CHAR_BACKWARD_SLASH &&
2650+ StringPrototypeCharCodeAt ( p , 2 ) === CHAR_QUESTION_MARK &&
2651+ StringPrototypeCharCodeAt ( p , 3 ) === CHAR_BACKWARD_SLASH ) {
2652+ // \\?\C:\ -> C:\ (extended drive path)
2653+ if ( p . length >= 6 ) {
2654+ const drive = StringPrototypeCharCodeAt ( p , 4 ) ;
2655+ if ( ( ( drive >= CHAR_UPPERCASE_A && drive <= CHAR_UPPERCASE_Z ) ||
2656+ ( drive >= CHAR_LOWERCASE_A && drive <= CHAR_LOWERCASE_Z ) ) &&
2657+ StringPrototypeCharCodeAt ( p , 5 ) === CHAR_COLON ) {
2658+ return StringPrototypeSlice ( p , 4 ) ;
2659+ }
2660+ }
2661+ // \\?\UNC\server\share -> \\server\share (extended UNC path)
2662+ if ( p . length >= 8 &&
2663+ ( StringPrototypeCharCodeAt ( p , 4 ) === 85 /* U */ ||
2664+ StringPrototypeCharCodeAt ( p , 4 ) === 117 /* u */ ) &&
2665+ ( StringPrototypeCharCodeAt ( p , 5 ) === 78 /* N */ ||
2666+ StringPrototypeCharCodeAt ( p , 5 ) === CHAR_LOWERCASE_N ) &&
2667+ ( StringPrototypeCharCodeAt ( p , 6 ) === CHAR_UPPERCASE_C ||
2668+ StringPrototypeCharCodeAt ( p , 6 ) === 99 /* c */ ) &&
2669+ StringPrototypeCharCodeAt ( p , 7 ) === CHAR_BACKWARD_SLASH ) {
2670+ return '\\\\' + StringPrototypeSlice ( p , 8 ) ;
2671+ }
2672+ }
2673+ return p ;
2674+ }
2675+
26312676let splitRoot ;
26322677if ( isWindows ) {
26332678 // Regex to find the device root on Windows (e.g. 'c:\\'), including trailing
@@ -2690,6 +2735,12 @@ function realpathSync(p, options) {
26902735 validatePath ( p ) ;
26912736 p = pathModule . resolve ( p ) ;
26922737
2738+ // On Windows, strip the extended-length path prefix (\\?\) so that the
2739+ // path walking logic below works with standard drive-letter or UNC roots.
2740+ if ( isWindows ) {
2741+ p = stripExtendedPathPrefix ( p ) ;
2742+ }
2743+
26932744 const cache = options [ realpathCacheKey ] ;
26942745 const maybeCachedResult = cache ?. get ( p ) ;
26952746 if ( maybeCachedResult ) {
@@ -2793,6 +2844,11 @@ function realpathSync(p, options) {
27932844 // Resolve the link, then start over
27942845 p = pathModule . resolve ( resolvedLink , StringPrototypeSlice ( p , pos ) ) ;
27952846
2847+ // Strip extended path prefix again in case pathModule.resolve re-added it
2848+ if ( isWindows ) {
2849+ p = stripExtendedPathPrefix ( p ) ;
2850+ }
2851+
27962852 // Skip over roots
27972853 current = base = splitRoot ( p ) ;
27982854 pos = current . length ;
@@ -2851,6 +2907,12 @@ function realpath(p, options, callback) {
28512907 validatePath ( p ) ;
28522908 p = pathModule . resolve ( p ) ;
28532909
2910+ // On Windows, strip the extended-length path prefix (\\?\) so that the
2911+ // path walking logic below works with standard drive-letter or UNC roots.
2912+ if ( isWindows ) {
2913+ p = stripExtendedPathPrefix ( p ) ;
2914+ }
2915+
28542916 const seenLinks = new SafeMap ( ) ;
28552917 const knownHard = new SafeSet ( ) ;
28562918
@@ -2951,6 +3013,12 @@ function realpath(p, options, callback) {
29513013 function gotResolvedLink ( resolvedLink ) {
29523014 // Resolve the link, then start over
29533015 p = pathModule . resolve ( resolvedLink , StringPrototypeSlice ( p , pos ) ) ;
3016+
3017+ // Strip extended path prefix again in case pathModule.resolve re-added it
3018+ if ( isWindows ) {
3019+ p = stripExtendedPathPrefix ( p ) ;
3020+ }
3021+
29543022 current = base = splitRoot ( p ) ;
29553023 pos = current . length ;
29563024
0 commit comments