#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
use std::borrow::Cow;
use std::path::{Component, Path, PathBuf};
mod native;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NativePathKey(Vec<native::NativeComponentKey>);
impl NativePathKey {
pub fn new(path: &Path) -> Self {
let path = normalize_lexical_path(path);
Self(
path.components()
.filter(|component| *component != Component::CurDir)
.map(native::component_key)
.collect(),
)
}
}
pub fn contains_parent_traversal(path: &Path) -> bool {
path.components()
.any(|component| component == Component::ParentDir)
}
pub fn normalize_lexical_path(path: &Path) -> Cow<'_, Path> {
#[cfg(windows)]
let path = native::normalize_windows_device_path(path);
#[cfg(not(windows))]
let path = Cow::Borrowed(path);
let normalized = path
.components()
.filter(|component| *component != Component::CurDir)
.collect::<PathBuf>();
if normalized.as_os_str() == path.as_os_str() {
path
} else {
Cow::Owned(normalized)
}
}
pub fn paths_equal(left: &Path, right: &Path) -> bool {
#[cfg(windows)]
{
NativePathKey::new(left) == NativePathKey::new(right)
}
#[cfg(not(windows))]
let mut left = left
.components()
.filter(|component| *component != Component::CurDir);
#[cfg(not(windows))]
let mut right = right
.components()
.filter(|component| *component != Component::CurDir);
#[cfg(not(windows))]
loop {
match (left.next(), right.next()) {
(None, None) => return true,
(Some(left), Some(right)) if components_equal(left, right) => {}
_ => return false,
}
}
}
pub fn is_within(path: &Path, root: &Path) -> bool {
if contains_parent_traversal(path) || contains_parent_traversal(root) {
return false;
}
#[cfg(windows)]
{
let path = NativePathKey::new(path);
let root = NativePathKey::new(root);
if root.0.is_empty() {
return !matches!(
path.0.first(),
Some(native::NativeComponentKey::Prefix(_) | native::NativeComponentKey::RootDir)
);
}
path.0.starts_with(&root.0)
}
#[cfg(not(windows))]
let mut path = path
.components()
.filter(|component| *component != Component::CurDir);
#[cfg(not(windows))]
let mut root = root
.components()
.filter(|component| *component != Component::CurDir);
#[cfg(not(windows))]
if root.clone().next().is_none() {
return path.clone().next() != Some(Component::RootDir);
}
#[cfg(not(windows))]
loop {
match (root.next(), path.next()) {
(None, _) => return true,
(Some(root), Some(path)) if components_equal(path, root) => {}
(Some(_), _) => return false,
}
}
}
pub fn contains_component_path(path: &Path, needle: &Path) -> bool {
#[cfg(windows)]
{
let path = NativePathKey::new(path);
let needle = NativePathKey::new(needle);
!needle.0.is_empty()
&& needle.0.len() <= path.0.len()
&& path
.0
.windows(needle.0.len())
.any(|window| window == needle.0)
}
#[cfg(not(windows))]
let path_components: Vec<_> = path
.components()
.filter(|component| *component != Component::CurDir)
.collect();
#[cfg(not(windows))]
let needle_components: Vec<_> = needle
.components()
.filter(|component| *component != Component::CurDir)
.collect();
#[cfg(not(windows))]
if needle_components.is_empty() || needle_components.len() > path_components.len() {
return false;
}
#[cfg(not(windows))]
path_components
.windows(needle_components.len())
.any(|window| {
window
.iter()
.zip(&needle_components)
.all(|(left, right)| components_equal(*left, *right))
})
}
pub fn components_equal(left: Component<'_>, right: Component<'_>) -> bool {
native::components_equal(left, right)
}
pub fn strings_equal(left: &str, right: &str) -> bool {
#[cfg(windows)]
{
case_fold(left) == case_fold(right)
}
#[cfg(not(windows))]
{
left == right
}
}
pub fn case_fold(value: &str) -> String {
#[cfg(windows)]
{
value.to_lowercase()
}
#[cfg(not(windows))]
{
value.to_owned()
}
}