|
| 1 | +use std::path::PathBuf; |
| 2 | + |
| 3 | +use crate::ResolveError; |
| 4 | + |
| 5 | +/// When applicable, converts a [DOS device path](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#dos-device-paths) |
| 6 | +/// to a normal path (usually, "Traditional DOS paths" or "UNC path") that can be consumed by the `import`/`require` syntax of Node.js. |
| 7 | +/// Returns `None` if the path cannot be represented as a normal path. |
| 8 | +pub fn try_strip_windows_prefix(path: PathBuf) -> Result<PathBuf, ResolveError> { |
| 9 | + // See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file |
| 10 | + let path_bytes = path.as_os_str().as_encoded_bytes(); |
| 11 | + |
| 12 | + let path = if let Some(p) = |
| 13 | + path_bytes.strip_prefix(br"\\?\UNC\").or_else(|| path_bytes.strip_prefix(br"\\.\UNC\")) |
| 14 | + { |
| 15 | + // UNC paths |
| 16 | + // SAFETY: |
| 17 | + unsafe { |
| 18 | + PathBuf::from(std::ffi::OsStr::from_encoded_bytes_unchecked(&[br"\\", p].concat())) |
| 19 | + } |
| 20 | + } else if let Some(p) = |
| 21 | + path_bytes.strip_prefix(br"\\?\").or_else(|| path_bytes.strip_prefix(br"\\.\")) |
| 22 | + { |
| 23 | + // Assuming traditional DOS path "\\?\C:\" |
| 24 | + if p[1] != b':' { |
| 25 | + // E.g., |
| 26 | + // \\?\Volume{b75e2c83-0000-0000-0000-602f00000000} |
| 27 | + // \\?\BootPartition\ |
| 28 | + // It seems nodejs does not support DOS device paths with Volume GUIDs. |
| 29 | + // This can happen if the path points to a Mounted Volume without a drive letter. |
| 30 | + return Err(ResolveError::PathNotSupported(path)); |
| 31 | + } |
| 32 | + // SAFETY: |
| 33 | + unsafe { PathBuf::from(std::ffi::OsStr::from_encoded_bytes_unchecked(p)) } |
| 34 | + } else { |
| 35 | + path |
| 36 | + }; |
| 37 | + |
| 38 | + Ok(path) |
| 39 | +} |
| 40 | + |
| 41 | +#[test] |
| 42 | +fn test_try_strip_windows_prefix() { |
| 43 | + let pass = [ |
| 44 | + (r"\\?\C:\Users\user\Documents\file1.txt", r"C:\Users\user\Documents\file1.txt"), |
| 45 | + (r"\\.\C:\Users\user\Documents\file2.txt", r"C:\Users\user\Documents\file2.txt"), |
| 46 | + (r"\\?\UNC\server\share\file3.txt", r"\\server\share\file3.txt"), |
| 47 | + ]; |
| 48 | + |
| 49 | + for (path, expected) in pass { |
| 50 | + assert_eq!(try_strip_windows_prefix(PathBuf::from(path)), Ok(PathBuf::from(expected))); |
| 51 | + } |
| 52 | + |
| 53 | + let fail = [ |
| 54 | + r"\\?\Volume{c8ec34d8-3ba6-45c3-9b9d-3e4148e12d00}\file4.txt", |
| 55 | + r"\\?\BootPartition\file4.txt", |
| 56 | + ]; |
| 57 | + |
| 58 | + for path in fail { |
| 59 | + assert_eq!( |
| 60 | + try_strip_windows_prefix(PathBuf::from(path)), |
| 61 | + Err(crate::ResolveError::PathNotSupported(PathBuf::from(path))) |
| 62 | + ); |
| 63 | + } |
| 64 | +} |
0 commit comments