about summary refs log tree commit diff
path: root/library/std/src/sys/path/windows.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/std/src/sys/path/windows.rs')
-rw-r--r--library/std/src/sys/path/windows.rs77
1 files changed, 77 insertions, 0 deletions
diff --git a/library/std/src/sys/path/windows.rs b/library/std/src/sys/path/windows.rs
index 1c534721916..e0e003f6a81 100644
--- a/library/std/src/sys/path/windows.rs
+++ b/library/std/src/sys/path/windows.rs
@@ -10,6 +10,40 @@ mod tests;
 pub const MAIN_SEP_STR: &str = "\\";
 pub const MAIN_SEP: char = '\\';
 
+/// A null terminated wide string.
+#[repr(transparent)]
+pub struct WCStr([u16]);
+
+impl WCStr {
+    /// Convert a slice to a WCStr without checks.
+    ///
+    /// Though it is memory safe, the slice should also not contain interior nulls
+    /// as this may lead to unwanted truncation.
+    ///
+    /// # Safety
+    ///
+    /// The slice must end in a null.
+    pub unsafe fn from_wchars_with_null_unchecked(s: &[u16]) -> &Self {
+        unsafe { &*(s as *const [u16] as *const Self) }
+    }
+
+    pub fn as_ptr(&self) -> *const u16 {
+        self.0.as_ptr()
+    }
+
+    pub fn count_bytes(&self) -> usize {
+        self.0.len()
+    }
+}
+
+#[inline]
+pub fn with_native_path<T>(path: &Path, f: &dyn Fn(&WCStr) -> io::Result<T>) -> io::Result<T> {
+    let path = maybe_verbatim(path)?;
+    // SAFETY: maybe_verbatim returns null-terminated strings
+    let path = unsafe { WCStr::from_wchars_with_null_unchecked(&path) };
+    f(path)
+}
+
 #[inline]
 pub fn is_sep_byte(b: u8) -> bool {
     b == b'/' || b == b'\\'
@@ -350,3 +384,46 @@ pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
 pub(crate) fn is_absolute(path: &Path) -> bool {
     path.has_root() && path.prefix().is_some()
 }
+
+/// Test that the path is absolute, fully qualified and unchanged when processed by the Windows API.
+///
+/// For example:
+///
+/// - `C:\path\to\file` will return true.
+/// - `C:\path\to\nul` returns false because the Windows API will convert it to \\.\NUL
+/// - `C:\path\to\..\file` returns false because it will be resolved to `C:\path\file`.
+///
+/// This is a useful property because it means the path can be converted from and to and verbatim
+/// path just by changing the prefix.
+pub(crate) fn is_absolute_exact(path: &[u16]) -> bool {
+    // This is implemented by checking that passing the path through
+    // GetFullPathNameW does not change the path in any way.
+
+    // Windows paths are limited to i16::MAX length
+    // though the API here accepts a u32 for the length.
+    if path.is_empty() || path.len() > u32::MAX as usize || path.last() != Some(&0) {
+        return false;
+    }
+    // The path returned by `GetFullPathNameW` must be the same length as the
+    // given path, otherwise they're not equal.
+    let buffer_len = path.len();
+    let mut new_path = Vec::with_capacity(buffer_len);
+    let result = unsafe {
+        c::GetFullPathNameW(
+            path.as_ptr(),
+            new_path.capacity() as u32,
+            new_path.as_mut_ptr(),
+            crate::ptr::null_mut(),
+        )
+    };
+    // Note: if non-zero, the returned result is the length of the buffer without the null termination
+    if result == 0 || result as usize != buffer_len - 1 {
+        false
+    } else {
+        // SAFETY: `GetFullPathNameW` initialized `result` bytes and does not exceed `nBufferLength - 1` (capacity).
+        unsafe {
+            new_path.set_len((result as usize) + 1);
+        }
+        path == &new_path
+    }
+}