@@ -155,30 +155,52 @@ impl FileSystemOs {
155
155
/// See [std::fs::read_link]
156
156
#[ inline]
157
157
pub fn read_link ( path : & Path ) -> io:: Result < PathBuf > {
158
- let path = fs:: read_link ( path) ?;
158
+ let target = fs:: read_link ( path) ?;
159
159
cfg_if ! {
160
160
if #[ cfg( windows) ] {
161
- Ok ( Self :: strip_windows_prefix( path) )
161
+ Ok ( match Self :: try_strip_windows_prefix( & target) {
162
+ Some ( path) => path,
163
+ // We won't follow the link if we cannot represent its target properly.
164
+ None => target,
165
+ } )
162
166
} else {
163
- Ok ( path )
167
+ Ok ( target . to_path_buf ( ) )
164
168
}
165
169
}
166
170
}
167
171
168
- pub fn strip_windows_prefix < P : AsRef < Path > > ( path : P ) -> PathBuf {
169
- const UNC_PATH_PREFIX : & [ u8 ] = b"\\ \\ ?\\ UNC\\ " ;
170
- const LONG_PATH_PREFIX : & [ u8 ] = b"\\ \\ ?\\ " ;
172
+ /// When applicable, converts a [DOS device path](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#dos-device-paths)
173
+ /// to a normal path (usually, "Traditional DOS paths" or "UNC path") that can be consumed by the `import`/`require` syntax of Node.js.
174
+ /// Returns `None` if the path cannot be represented as a normal path.
175
+ pub fn try_strip_windows_prefix < P : AsRef < Path > > ( path : P ) -> Option < PathBuf > {
176
+ // See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
171
177
let path_bytes = path. as_ref ( ) . as_os_str ( ) . as_encoded_bytes ( ) ;
172
- path_bytes
173
- . strip_prefix ( UNC_PATH_PREFIX )
174
- . or_else ( || path_bytes. strip_prefix ( LONG_PATH_PREFIX ) )
175
- . map_or_else (
176
- || path. as_ref ( ) . to_path_buf ( ) ,
177
- |p| {
178
- // SAFETY: `as_encoded_bytes` ensures `p` is valid path bytes
179
- unsafe { PathBuf :: from ( std:: ffi:: OsStr :: from_encoded_bytes_unchecked ( p) ) }
180
- } ,
181
- )
178
+
179
+ let path = if let Some ( p) =
180
+ path_bytes. strip_prefix ( br"\\?\UNC\" ) . or ( path_bytes. strip_prefix ( br"\\.\UNC\" ) )
181
+ {
182
+ // UNC paths
183
+ unsafe {
184
+ PathBuf :: from ( std:: ffi:: OsStr :: from_encoded_bytes_unchecked ( & [ br"\\" , p] . concat ( ) ) )
185
+ }
186
+ } else if let Some ( p) =
187
+ path_bytes. strip_prefix ( br"\\?\" ) . or ( path_bytes. strip_prefix ( br"\\.\" ) )
188
+ {
189
+ // Assuming traditional DOS path "\\?\C:\"
190
+ if p[ 1 ] != b':' {
191
+ // E.g.,
192
+ // \\?\Volume{b75e2c83-0000-0000-0000-602f00000000}
193
+ // \\?\BootPartition\
194
+ // It seems nodejs does not support DOS device paths with Volume GUIDs.
195
+ // This can happen if the path points to a Mounted Volume without a drive letter.
196
+ return None ;
197
+ }
198
+ unsafe { PathBuf :: from ( std:: ffi:: OsStr :: from_encoded_bytes_unchecked ( p) ) }
199
+ } else {
200
+ path. as_ref ( ) . to_path_buf ( )
201
+ } ;
202
+
203
+ Some ( path)
182
204
}
183
205
}
184
206
@@ -245,3 +267,37 @@ fn metadata() {
245
267
) ;
246
268
let _ = meta;
247
269
}
270
+
271
+ #[ test]
272
+ fn test_strip_windows_prefix ( ) {
273
+ assert_eq ! (
274
+ FileSystemOs :: try_strip_windows_prefix( PathBuf :: from(
275
+ r"\\?\C:\Users\user\Documents\file.txt"
276
+ ) ) ,
277
+ Some ( PathBuf :: from( r"C:\Users\user\Documents\file.txt" ) )
278
+ ) ;
279
+
280
+ assert_eq ! (
281
+ FileSystemOs :: try_strip_windows_prefix( PathBuf :: from(
282
+ r"\\.\C:\Users\user\Documents\file.txt"
283
+ ) ) ,
284
+ Some ( PathBuf :: from( r"C:\Users\user\Documents\file.txt" ) )
285
+ ) ;
286
+
287
+ assert_eq ! (
288
+ FileSystemOs :: try_strip_windows_prefix( PathBuf :: from( r"\\?\UNC\server\share\file.txt" ) ) ,
289
+ Some ( PathBuf :: from( r"\\server\share\file.txt" ) )
290
+ ) ;
291
+
292
+ assert_eq ! (
293
+ FileSystemOs :: try_strip_windows_prefix( PathBuf :: from(
294
+ r"\\?\Volume{c8ec34d8-3ba6-45c3-9b9d-3e4148e12d00}\file.txt"
295
+ ) ) ,
296
+ None
297
+ ) ;
298
+
299
+ assert_eq ! (
300
+ FileSystemOs :: try_strip_windows_prefix( PathBuf :: from( r"\\?\BootPartition\file.txt" ) ) ,
301
+ None
302
+ ) ;
303
+ }
0 commit comments