use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SafePath(PathBuf);
impl SafePath {
pub fn new(path: impl AsRef<Path>) -> crate::Result<Self> {
let path = path.as_ref();
if path.is_absolute() {
return Err(crate::CoreError::Path(
"absolute paths are not allowed".into(),
));
}
for component in path.components() {
use std::path::Component;
match component {
Component::ParentDir => {
return Err(crate::CoreError::Path(
"'..' components are not allowed".into(),
));
}
Component::Normal(os_str) => {
let s = os_str.to_string_lossy();
if s.contains('\0') {
return Err(crate::CoreError::Path("null bytes are not allowed".into()));
}
if os_str.len() > 255 {
return Err(crate::CoreError::Path(format!(
"path component exceeds 255 bytes: '{s}'"
)));
}
if s.contains('\u{202A}')
|| s.contains('\u{202B}')
|| s.contains('\u{202C}')
|| s.contains('\u{202D}')
|| s.contains('\u{202E}')
|| s.contains('\u{2066}')
|| s.contains('\u{2067}')
|| s.contains('\u{2068}')
|| s.contains('\u{2069}')
{
return Err(crate::CoreError::Path(
"Unicode bidirectional control characters are not allowed".into(),
));
}
}
_ => {}
}
}
Ok(Self(path.to_path_buf()))
}
#[must_use]
pub fn as_path(&self) -> &Path {
&self.0
}
}
impl AsRef<Path> for SafePath {
fn as_ref(&self) -> &Path {
&self.0
}
}
impl std::fmt::Display for SafePath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.display())
}
}