use crate::OsStrExt;
use std::path::{
Component, Path,
Prefix::{UNC, Verbatim, VerbatimUNC},
StripPrefixError,
};
use windows::Win32::Foundation::MAX_PATH;
pub(crate) trait PathExt {
fn has_invalid_chars(&self) -> bool;
fn is_longer_than_wide(&self, max: u32) -> bool;
fn is_longer_than_win_max_path(&self) -> bool;
fn to_wide_vec_with_nul(&self) -> Vec<u16>;
fn strip_prefix_fix(&self, base: impl AsRef<Path>) -> Result<&Path, StripPrefixError>;
fn trim_leading_separator(&self) -> &Path;
fn trim_trailing_separator(&self) -> &Path;
}
impl PathExt for Path {
fn has_invalid_chars(&self) -> bool {
let mut components = self.components();
match components.next() {
None => false,
Some(Component::Prefix(prefix)) => match prefix.kind() {
Verbatim(str) => {
str.has_invalid_path_chars()
|| components.as_path().as_os_str().has_invalid_path_chars()
}
UNC(server, share) | VerbatimUNC(server, share) => {
server.has_invalid_path_chars()
|| share.has_invalid_path_chars()
|| components.as_path().as_os_str().has_invalid_path_chars()
}
_ => components.as_path().as_os_str().has_invalid_path_chars(),
},
_ => self.as_os_str().has_invalid_path_chars(),
}
}
fn is_longer_than_wide(&self, max: u32) -> bool {
self.as_os_str().is_longer_than_wide(max)
}
fn is_longer_than_win_max_path(&self) -> bool {
self.is_longer_than_wide(MAX_PATH)
}
fn to_wide_vec_with_nul(&self) -> Vec<u16> {
self.as_os_str().to_wide_vec_with_nul()
}
fn strip_prefix_fix(&self, base: impl AsRef<Path>) -> Result<&Path, StripPrefixError> {
let result = self.strip_prefix(base);
if let Ok(result) = result {
return Ok(result.trim_leading_separator());
}
result
}
fn trim_leading_separator(&self) -> &Path {
let mut components = self.components();
if let Some(first) = components.next()
&& first == Component::RootDir
{
return components.as_path();
}
self
}
fn trim_trailing_separator(&self) -> &Path {
let mut components = self.components();
if let Some(last) = components.next_back()
&& last == Component::RootDir
{
return components.as_path();
}
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn has_invalid_chars() {
assert!(Path::new(":").has_invalid_chars());
assert!(Path::new(r"a\:").has_invalid_chars());
assert!(Path::new(r"a\>").has_invalid_chars());
assert!(Path::new(r"a\dir:dir").has_invalid_chars());
assert!(Path::new(r"dir:\a").has_invalid_chars());
assert!(Path::new(r"C:\dir:").has_invalid_chars());
assert!(Path::new(r"\\server:\share\dir").has_invalid_chars());
assert!(Path::new(r"\\server\share:\dir").has_invalid_chars());
assert!(Path::new(r"\\server\share\dir:").has_invalid_chars());
assert!(Path::new(r"\\?\UNC\server:\share\dir").has_invalid_chars());
assert!(Path::new(r"\\?\UNC\server\share:\dir").has_invalid_chars());
assert!(Path::new(r"\\?\UNC\server\share\dir:").has_invalid_chars());
assert!(Path::new(r"\\?\UNC\foo:").has_invalid_chars());
assert!(Path::new(r"\\?\UNC\foo>").has_invalid_chars());
assert!(Path::new(r"\\?\C:\foo:").has_invalid_chars());
assert!(Path::new(r"\\?\:\foo").has_invalid_chars());
assert!(Path::new(r"\\?\>\foo").has_invalid_chars());
assert!(!Path::new("").has_invalid_chars());
assert!(!Path::new("a").has_invalid_chars());
assert!(!Path::new(r"a\b").has_invalid_chars());
assert!(!Path::new(r"\a\b").has_invalid_chars());
assert!(!Path::new(r"C:\").has_invalid_chars());
assert!(!Path::new(r"C:\dir").has_invalid_chars());
assert!(!Path::new(r"\\server").has_invalid_chars());
assert!(!Path::new(r"\\server\share").has_invalid_chars());
assert!(!Path::new(r"\\server\share\dir").has_invalid_chars());
assert!(!Path::new(r"\\?\").has_invalid_chars());
assert!(!Path::new(r"\\?\foo").has_invalid_chars());
assert!(!Path::new(r"\\?\foo\bar").has_invalid_chars());
assert!(!Path::new(r"\\?\UNC\server\share\dir").has_invalid_chars());
assert!(!Path::new(r"\\?\UNC\foo").has_invalid_chars());
assert!(!Path::new(r"\\?\C:\").has_invalid_chars());
assert!(!Path::new(r"\\?\C:\dir").has_invalid_chars());
assert!(!Path::new(r"\\.\COM42").has_invalid_chars());
}
#[test]
fn is_longer_than_wide() {
assert!(!Path::new("").is_longer_than_wide(10));
assert!(!Path::new(&"1".repeat(9)).is_longer_than_wide(10));
assert!(!Path::new(&"1".repeat(10)).is_longer_than_wide(10));
assert!(Path::new(&"1".repeat(11)).is_longer_than_wide(10));
assert!(!Path::new(&"\u{3042}".repeat(9)).is_longer_than_wide(10));
assert!(!Path::new(&"\u{3042}".repeat(10)).is_longer_than_wide(10));
assert!(Path::new(&"\u{3042}".repeat(11)).is_longer_than_wide(10));
}
#[test]
fn strip_prefix_fix() {
let path = Path::new(r"\\?\UNC\server\share\dir");
let base = Path::new(r"\\?\UNC\server\share");
assert_eq!(path.strip_prefix(base), Ok(Path::new(r"\dir")));
assert_eq!(path.strip_prefix_fix(base), Ok(Path::new(r"dir")));
}
#[test]
fn trim_leading_separator() {
assert_eq!(Path::new("/").trim_leading_separator(), Path::new(""));
assert_eq!(Path::new("").trim_leading_separator(), Path::new(""));
assert_eq!(Path::new("/a").trim_leading_separator(), Path::new("a"));
assert_eq!(Path::new("a").trim_leading_separator(), Path::new("a"));
assert_eq!(Path::new("/a/b").trim_leading_separator(), Path::new("a/b"));
assert_eq!(Path::new("a/b").trim_leading_separator(), Path::new("a/b"));
}
#[test]
fn trim_trailing_separator() {
assert_eq!(Path::new("/").trim_trailing_separator(), Path::new(""));
assert_eq!(Path::new("").trim_trailing_separator(), Path::new(""));
assert_eq!(Path::new("a/").trim_trailing_separator(), Path::new("a"));
assert_eq!(Path::new("a").trim_trailing_separator(), Path::new("a"));
assert_eq!(Path::new("a//").trim_trailing_separator(), Path::new("a"));
assert_eq!(
Path::new("a/b/").trim_trailing_separator(),
Path::new("a/b")
);
assert_eq!(Path::new("a/b").trim_trailing_separator(), Path::new("a/b"));
}
}