From cd75e8855923d162acbde3b632ee43ec655b9a94 Mon Sep 17 00:00:00 2001 From: dylni <46035563+dylni@users.noreply.github.com> Date: Sat, 27 Jul 2024 09:37:38 -0400 Subject: [PATCH] Expand shorthand paths (#19) --- src/lib.rs | 15 +++++- src/windows/normalize.rs | 109 +++++++++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 41 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 47bf209..c226a63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,7 +217,7 @@ pub trait PathExt: private::Sealed { /// /// Currently, this method calls: /// - [`fs::canonicalize`] on Unix. - /// - [`GetFullPathNameW`] on Windows. + /// - [`GetFullPathNameW`] and [`GetLongPathNameW`] on Windows. /// /// However, the implementation is subject to change. This section is only /// informative. @@ -251,6 +251,7 @@ pub trait PathExt: private::Sealed { /// /// [`fs::canonicalize`]: ::std::fs::canonicalize /// [`GetFullPathNameW`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew + /// [`GetLongPathNameW`]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlongpathnamew /// [`normalize_virtually`]: Self::normalize_virtually /// [rust-lang/rust#42869]: https://github.com/rust-lang/rust/issues/42869 /// [rust-lang/rust#49342]: https://github.com/rust-lang/rust/issues/49342 @@ -259,7 +260,16 @@ pub trait PathExt: private::Sealed { /// [verbatim]: ::std::path::Prefix::is_verbatim fn normalize(&self) -> io::Result; - /// Equivalent to [`normalize`] but does not access the file system. + /// Normalizes `self` similarly to [`normalize`] but does not perform any + /// operations that require accessing the filesystem. + /// + /// # Implementation + /// + /// Currently, this method is equivalent to [`normalize`], except that it + /// does not call [`GetLongPathNameW`] on Windows. + /// + /// However, the implementation is subject to change. This section is only + /// informative. /// /// # Errors /// @@ -283,6 +293,7 @@ pub trait PathExt: private::Sealed { /// # Ok::<_, io::Error>(()) /// ``` /// + /// [`GetLongPathNameW`]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlongpathnamew /// [`normalize`]: Self::normalize #[cfg(any(doc, windows))] #[cfg_attr(normpath_docs_rs, doc(cfg(windows)))] diff --git a/src/windows/normalize.rs b/src/windows/normalize.rs index 98e81dc..1936e0e 100644 --- a/src/windows/normalize.rs +++ b/src/windows/normalize.rs @@ -12,6 +12,7 @@ use std::path::PrefixComponent; use std::ptr; use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW; +use windows_sys::Win32::Storage::FileSystem::GetLongPathNameW; use crate::BasePath; use crate::BasePathBuf; @@ -81,46 +82,14 @@ fn normalize_verbatim(path: &Path) -> Cow<'_, BasePath> { } } -pub(crate) fn normalize_virtually( - initial_path: &Path, -) -> io::Result { - // [GetFullPathNameW] always converts separators. - let path = convert_separators(initial_path, None); - - let mut wide_path: Vec<_> = path.as_os_str().encode_wide().collect(); - if wide_path.contains(&0) { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "strings passed to WinAPI cannot contains NULs", - )); - } - wide_path.push(0); - - match path.components().next() { - // Verbatim paths should not be modified. - Some(Component::Prefix(prefix)) if prefix.kind().is_verbatim() => { - return Ok(normalize_verbatim(initial_path).into_owned()); - } - Some(Component::RootDir) if wide_path[1] == SEPARATOR.into() => { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "partial UNC prefixes are invalid", - )); - } - _ => {} - } - +fn winapi_buffered(mut call_fn: F) -> io::Result> +where + F: FnMut(*mut u16, u32) -> u32, +{ let mut buffer = Vec::new(); let mut capacity = 0; loop { - capacity = unsafe { - GetFullPathNameW( - wide_path.as_ptr(), - capacity, - buffer.as_mut_ptr(), - ptr::null_mut(), - ) - }; + capacity = call_fn(buffer.as_mut_ptr(), capacity); if capacity == 0 { break Err(io::Error::last_os_error()); } @@ -160,12 +129,74 @@ pub(crate) fn normalize_virtually( unsafe { buffer.set_len(length); } - break Ok(BasePathBuf(OsString::from_wide(&buffer).into())); + return Ok(buffer); } } +fn normalize_with( + initial_path: &Path, + existing: bool, +) -> io::Result { + // [GetFullPathNameW] always converts separators. + let path = convert_separators(initial_path, None); + + let mut wide_path: Vec<_> = path.as_os_str().encode_wide().collect(); + if wide_path.contains(&0) { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "strings passed to WinAPI cannot contains NULs", + )); + } + wide_path.push(0); + + match path.components().next() { + // Verbatim paths should not be modified. + Some(Component::Prefix(prefix)) if prefix.kind().is_verbatim() => { + return Ok(normalize_verbatim(initial_path).into_owned()); + } + Some(Component::RootDir) if wide_path[1] == SEPARATOR.into() => { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "partial UNC prefixes are invalid", + )); + } + _ => {} + } + + wide_path = winapi_buffered(|buffer, capacity| { + unsafe { + GetFullPathNameW( + wide_path.as_ptr(), + capacity, + buffer, + ptr::null_mut(), + ) + } + })?; + + if existing { + if wide_path.contains(&0) { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "strings passed to WinAPI cannot contains NULs", + )); + } + wide_path.push(0); + + wide_path = winapi_buffered(|buffer, capacity| unsafe { + GetLongPathNameW(wide_path.as_ptr(), buffer, capacity) + })?; + } + + Ok(BasePathBuf(OsString::from_wide(&wide_path).into())) +} + +pub(crate) fn normalize_virtually(path: &Path) -> io::Result { + normalize_with(path, false) +} + pub(crate) fn normalize(path: &Path) -> io::Result { - path.metadata().and_then(|_| normalize_virtually(path)) + path.metadata().and_then(|_| normalize_with(path, true)) } fn get_prefix(base: &BasePath) -> PrefixComponent<'_> {