about summary refs log tree commit diff
path: root/library/std/src/sys/path
diff options
context:
space:
mode:
authorTshepang Mbambo <tshepang@gmail.com>2025-04-19 16:06:07 +0200
committerGitHub <noreply@github.com>2025-04-19 16:06:07 +0200
commitb886a8e654ab660f2f2cd02c68bf170dd4918fd8 (patch)
tree86e6a9ab8518740ca6ce8deae8f409ee9c309f20 /library/std/src/sys/path
parent52616fa902a9a551ba4e574bee41dbf6162746f4 (diff)
parent07b7f00a9e961fdf66ece2ecf70ae1dba505f10e (diff)
Merge pull request #2347 from rust-lang/rustc-pull
Rustc pull update
Diffstat (limited to 'library/std/src/sys/path')
-rw-r--r--library/std/src/sys/path/windows.rs77
-rw-r--r--library/std/src/sys/path/windows/tests.rs12
2 files changed, 89 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
+    }
+}
diff --git a/library/std/src/sys/path/windows/tests.rs b/library/std/src/sys/path/windows/tests.rs
index f2a60e30bc6..9eb79203dca 100644
--- a/library/std/src/sys/path/windows/tests.rs
+++ b/library/std/src/sys/path/windows/tests.rs
@@ -135,3 +135,15 @@ fn broken_unc_path() {
     assert_eq!(components.next(), Some(Component::Normal("foo".as_ref())));
     assert_eq!(components.next(), Some(Component::Normal("bar".as_ref())));
 }
+
+#[test]
+fn test_is_absolute_exact() {
+    use crate::sys::pal::api::wide_str;
+    // These paths can be made verbatim by only changing their prefix.
+    assert!(is_absolute_exact(wide_str!(r"C:\path\to\file")));
+    assert!(is_absolute_exact(wide_str!(r"\\server\share\path\to\file")));
+    // These paths change more substantially
+    assert!(!is_absolute_exact(wide_str!(r"C:\path\to\..\file")));
+    assert!(!is_absolute_exact(wide_str!(r"\\server\share\path\to\..\file")));
+    assert!(!is_absolute_exact(wide_str!(r"C:\path\to\NUL"))); // Converts to \\.\NUL
+}