use std::ffi::OsString;
use std::io;
use std::iter::Iterator;
use std::mem::MaybeUninit;
#[cfg(not(doc))]
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::path::{Path, PathBuf};
use std::ptr;
use std::string::String;
use std::vec::Vec;
use super::kind::{ParsedUtf8Path, Win32Absolute, Win32Relative, WinPathKind};
pub trait WinPathExt: Sealed {
fn win_absolute(&self) -> io::Result<PathBuf>;
#[doc(hidden)]
fn win_clean(&self) -> io::Result<PathBuf>;
fn to_winuser_path(&self) -> io::Result<PathBuf>;
}
impl WinPathExt for Path {
fn win_absolute(&self) -> io::Result<PathBuf> {
if self.as_os_str().is_empty() {
return Ok(PathBuf::new());
}
if let Some(std::path::Component::Prefix(prefix)) = self.components().next() {
if prefix.kind().is_verbatim() {
return Ok(self.into());
}
}
let path = to_wide(self)?;
absolute_inner(&path, |path| OsString::from_wide(path).into())
}
#[doc(hidden)]
fn win_clean(&self) -> io::Result<PathBuf> {
let path = match self.to_str() {
Some(path) => path,
None => return Ok(self.into()),
};
let parsed = ParsedUtf8Path::from_utf8(path);
if parsed.kind() == WinPathKind::Verbatim {
return Ok(path.into());
}
let (prefix, path) = parsed.parts();
let path = String::from_iter([r"\\.\", path]);
let path = to_wide(Path::new(&path))?;
absolute_inner(&path, |path| {
let mut os_path = OsString::from(prefix);
os_path.push(&OsString::from_wide(&path[r"\\.\".len()..]));
os_path.into()
})
}
fn to_winuser_path(&self) -> io::Result<PathBuf> {
let path = match self.to_str() {
Some(path) => path,
None => return Ok(self.into()),
};
let (prefix, subpath) = match Win32Absolute::from_verbatim_str(path) {
Ok(result) => result,
Err(_) => return Ok(path.into()),
};
let prefix = match prefix {
Win32Absolute::Drive(_) => return Ok(subpath.into()),
Win32Absolute::Device => r"\\.\",
Win32Absolute::Unc => {
if subpath.is_empty() {
r"\\"
} else {
r"\"
}
}
};
let mut win32 = String::with_capacity(prefix.len() + subpath.len());
win32.push_str(prefix);
win32.push_str(subpath);
let win32 = Path::new(&win32);
if win32 == win32.win_absolute().unwrap_or_default() {
Ok(win32.into())
} else {
Ok(path.into())
}
}
}
pub fn resolve_prefix(prefix: Win32Relative) -> io::Result<PathBuf> {
match prefix {
Win32Relative::CurrentDirectory => Path::new(r".\").win_absolute(),
Win32Relative::Root => Path::new(r"\").win_absolute(),
Win32Relative::DriveRelative(drive) => {
let path = [drive, b':' as u16, 0];
absolute_inner(&path, |path| OsString::from_wide(path).into())
}
}
}
fn absolute_inner<F>(path: &[u16], f: F) -> io::Result<PathBuf>
where
F: FnOnce(&[u16]) -> PathBuf,
{
debug_assert!(!path.starts_with(&[b'\\' as u16, b'\\' as u16, b'?' as u16, b'\\' as u16]));
assert_eq!(path.last(), Some(&0));
unsafe {
const MAX_PATH: u16 = i16::MAX as u16;
let mut buffer: [MaybeUninit<u16>; MAX_PATH as usize] = MaybeUninit::uninit().assume_init();
let capacity = MAX_PATH as u32;
let len = c::GetFullPathNameW(
path.as_ptr(),
capacity,
buffer.as_mut_ptr().cast(),
ptr::null_mut(),
);
if len == 0 {
Err(io::Error::last_os_error())
} else {
let path = &*((&buffer[..len as usize]) as *const _ as *const [u16]);
Ok(f(path))
}
}
}
fn to_wide(path: &Path) -> io::Result<Vec<u16>> {
let mut contains_null = false;
let path: Vec<u16> = path
.as_os_str()
.encode_wide()
.inspect(|&w| {
if w == 0 {
contains_null = true
}
})
.chain([0])
.collect();
if !contains_null {
Ok(path)
} else {
Err(io::Error::new(io::ErrorKind::Other, "paths must not contain nulls"))
}
}
#[allow(nonstandard_style, clippy::style)]
mod c {
type DWORD = u32;
type LPCWSTR = *const u16;
type LPWSTR = *mut u16;
#[link(name = "kernel32")]
extern "system" {
pub fn GetFullPathNameW(
lpFileName: LPCWSTR,
nBufferLength: DWORD,
lpBuffer: LPWSTR,
lpFilePart: *mut LPWSTR,
) -> DWORD;
}
}
mod private {
pub trait Sealed {}
impl Sealed for std::path::Path {}
}
use private::Sealed;