use std::borrow::Cow;
use std::env;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::io;
use std::ops::Not;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::ffi::OsStringExt;
use std::path::Component;
use std::path::Path;
use std::path::Prefix;
use std::path::PrefixComponent;
use std::ptr;
use windows_sys::Win32::Storage::FileSystem::GetFullPathNameW;
use windows_sys::Win32::Storage::FileSystem::GetLongPathNameW;
use windows_sys::Win32::Storage::FileSystem::GetShortPathNameW;
use crate::BasePath;
use crate::BasePathBuf;
#[cfg(feature = "localization")]
pub(super) mod localize;
macro_rules! static_assert {
( $condition:expr ) => {
const _: () = assert!($condition, "static assertion failed");
};
}
fn is_separator(byte: &u8) -> bool {
[b'/', b'\\'].contains(byte)
}
pub(crate) fn is_base(path: &Path) -> bool {
matches!(path.components().next(), Some(Component::Prefix(_)))
}
pub(crate) fn to_base(path: &Path) -> io::Result<BasePathBuf> {
let base = env::current_dir()?;
debug_assert!(is_base(&base));
let mut base = BasePathBuf(base);
base.push(path);
Ok(base)
}
#[inline(always)]
const fn u32_to_usize(n: u32) -> usize {
static_assert!(size_of::<u32>() <= size_of::<usize>());
n as usize
}
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 = call_fn(buffer.as_mut_ptr(), capacity);
if capacity == 0 {
break Err(io::Error::last_os_error());
}
let length = u32_to_usize(capacity);
let Some(mut additional_capacity) =
length.checked_sub(buffer.capacity())
else {
unsafe {
buffer.set_len(length);
}
return Ok(buffer);
};
assert_ne!(0, additional_capacity);
let extra_capacity = 2.min(capacity.not());
capacity += extra_capacity;
additional_capacity += u32_to_usize(extra_capacity);
buffer.reserve(additional_capacity);
}
}
fn winapi_path(
path: &Path,
call_fn: fn(*const u16, *mut u16, u32) -> u32,
) -> io::Result<Cow<'_, Path>> {
if path.as_os_str().as_encoded_bytes().contains(&0) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"strings passed to WinAPI cannot contains NULs",
));
}
match path.components().next() {
Some(Component::Prefix(prefix)) if prefix.kind().is_verbatim() => {
return Ok(Cow::Borrowed(path));
}
Some(Component::RootDir)
if path
.as_os_str()
.as_encoded_bytes()
.get(1)
.is_some_and(is_separator) =>
{
return Err(io::Error::new(
io::ErrorKind::NotFound,
"partial UNC prefixes are invalid",
));
}
_ => {}
}
let mut path: Vec<_> = path.as_os_str().encode_wide().collect();
debug_assert!(!path.contains(&0));
path.push(0);
path = winapi_buffered(|buffer, capacity| {
call_fn(path.as_ptr(), buffer, capacity)
})?;
Ok(Cow::Owned(OsString::from_wide(&path).into()))
}
pub(crate) fn normalize_virtually(path: &Path) -> io::Result<BasePathBuf> {
winapi_path(path, |path, buffer, capacity| unsafe {
GetFullPathNameW(path, capacity, buffer, ptr::null_mut())
})
.map(|x| BasePathBuf(x.into_owned()))
}
pub(crate) fn normalize(path: &Path) -> io::Result<BasePathBuf> {
let _ = path.metadata()?;
normalize_virtually(path)
}
pub(crate) fn expand(path: &Path) -> io::Result<Cow<'_, Path>> {
winapi_path(path, |path, buffer, capacity| unsafe {
GetLongPathNameW(path, buffer, capacity)
})
}
pub(crate) fn shorten(path: &Path) -> io::Result<Cow<'_, Path>> {
winapi_path(path, |path, buffer, capacity| unsafe {
GetShortPathNameW(path, buffer, capacity)
})
}
fn get_prefix(base: &BasePath) -> PrefixComponent<'_> {
if let Some(Component::Prefix(prefix)) = base.components().next() {
prefix
} else {
panic!(
"base path is missing a prefix: \"{}\"",
base.as_path().display(),
);
}
}
fn convert_separators(path: &Path) -> Cow<'_, OsStr> {
let path_bytes = path.as_os_str().as_encoded_bytes();
let mut parts = path_bytes.split(|&x| x == b'/');
let part = parts.next().expect("split iterator is empty");
if part.len() == path_bytes.len() {
debug_assert_eq!(path_bytes, part);
debug_assert_eq!(None, parts.next());
return Cow::Borrowed(path.as_os_str());
}
let mut path_bytes = Vec::with_capacity(path_bytes.len());
path_bytes.extend(part);
for part in parts {
path_bytes.push(b'\\');
path_bytes.extend(part);
}
Cow::Owned(unsafe { OsString::from_encoded_bytes_unchecked(path_bytes) })
}
fn push_separator(base: &mut BasePathBuf) {
base.0.push("");
}
pub(crate) fn push(base: &mut BasePathBuf, path: &Path) {
let mut components = path.components();
let mut next_component = components.next();
match next_component {
Some(Component::Prefix(prefix)) => {
let mut absolute = prefix.kind().is_verbatim();
if !absolute {
next_component = components.next();
absolute = !matches!(prefix.kind(), Prefix::Disk(_))
|| prefix.kind() != get_prefix(base).kind()
|| next_component == Some(Component::RootDir);
}
if absolute {
*base = BasePathBuf(path.to_owned());
return;
}
}
Some(Component::RootDir) => {
let mut buffer = get_prefix(base).as_os_str().to_owned();
buffer.push(convert_separators(path));
*base = BasePathBuf(buffer.into());
return;
}
_ => {
while let Some(component) = next_component {
match component {
Component::CurDir => {}
Component::ParentDir if base.pop().is_ok() => {}
_ => break,
}
next_component = components.next();
}
}
}
if let Some(component) = next_component {
push_separator(base);
base.0.as_mut_os_string().push(component);
let components = components.as_path();
if !components.as_os_str().is_empty() {
push_separator(base);
base.0
.as_mut_os_string()
.push(convert_separators(components));
}
}
let path_bytes = path.as_os_str().as_encoded_bytes();
if path_bytes.last().is_some_and(is_separator)
|| matches!(path_bytes, [x, b'.'] if is_separator(x))
{
push_separator(base);
}
}