Skip to content

Commit

Permalink
Expand shorthand paths (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
dylni committed Jul 27, 2024
1 parent fa5e355 commit cd75e88
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 41 deletions.
15 changes: 13 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -259,7 +260,16 @@ pub trait PathExt: private::Sealed {
/// [verbatim]: ::std::path::Prefix::is_verbatim
fn normalize(&self) -> io::Result<BasePathBuf>;

/// 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
///
Expand All @@ -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)))]
Expand Down
109 changes: 70 additions & 39 deletions src/windows/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -81,46 +82,14 @@ fn normalize_verbatim(path: &Path) -> Cow<'_, BasePath> {
}
}

pub(crate) fn normalize_virtually(
initial_path: &Path,
) -> io::Result<BasePathBuf> {
// [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<F>(mut call_fn: F) -> io::Result<Vec<u16>>
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());
}
Expand Down Expand Up @@ -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<BasePathBuf> {
// [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<BasePathBuf> {
normalize_with(path, false)
}

pub(crate) fn normalize(path: &Path) -> io::Result<BasePathBuf> {
path.metadata().and_then(|_| normalize_virtually(path))
path.metadata().and_then(|_| normalize_with(path, true))
}

fn get_prefix(base: &BasePath) -> PrefixComponent<'_> {
Expand Down

0 comments on commit cd75e88

Please sign in to comment.