use std::path::{Component, Path};
use crate::utils::path::error::PathError;
pub fn validate_no_traversal(path: &Path) -> Result<(), PathError> {
for component in path.components() {
if component == Component::ParentDir {
return Err(PathError::PathTraversal(path.display().to_string()));
}
}
Ok(())
}
pub fn validate_relative_only(path: &Path) -> Result<(), PathError> {
if path.is_absolute() {
return Err(PathError::PathTraversal(format!(
"Absolute path not allowed: {}",
path.display()
)));
}
for component in path.components() {
match component {
Component::RootDir | Component::Prefix(_) => {
return Err(PathError::PathTraversal(format!(
"Root or prefix component not allowed: {}",
path.display()
)));
}
_ => {}
}
}
Ok(())
}
pub fn validate_characters(path: &Path) -> Result<(), PathError> {
let path_str = path.to_string_lossy();
if path_str.contains('\0') {
return Err(PathError::InvalidCharacter(path.display().to_string()));
}
Ok(())
}
pub(crate) fn find_existing_ancestor(mut path: &Path) -> Option<std::path::PathBuf> {
const MAX_DEPTH: usize = 100; let mut depth = 0;
loop {
if path.exists() {
return Some(path.to_path_buf());
}
match path.parent() {
Some(parent) if !parent.as_os_str().is_empty() => {
path = parent;
depth += 1;
if depth > MAX_DEPTH {
return None;
}
}
_ => return None,
}
}
}
fn is_within_base_by_prefix(path: &Path, base: &Path) -> bool {
if !path.starts_with(base) {
return false;
}
let path_bytes = path.as_os_str().as_encoded_bytes();
let base_bytes = base.as_os_str().as_encoded_bytes();
if path_bytes.len() == base_bytes.len() {
return true;
}
if path_bytes.len() > base_bytes.len() {
let next_byte = path_bytes[base_bytes.len()];
next_byte == b'/' || next_byte == b'\\'
} else {
false
}
}
pub fn validate_within_base(path: &Path, base: &Path) -> Result<(), PathError> {
validate_no_traversal(path)?;
if !base.exists() {
if path.is_absolute() {
if !is_within_base_by_prefix(path, base) {
return Err(PathError::OutsideBounds);
}
}
return Ok(());
}
let base_canonical = match base.canonicalize() {
Ok(c) => c,
Err(_) => {
if is_within_base_by_prefix(path, base) {
return Ok(());
}
return Err(PathError::OutsideBounds);
}
};
if path.exists() {
if let Ok(path_canonical) = path.canonicalize() {
if !path_canonical.starts_with(&base_canonical) {
return Err(PathError::OutsideBounds);
}
}
return Ok(());
}
let ancestor = match find_existing_ancestor(path) {
Some(a) => a,
None => {
if is_within_base_by_prefix(path, base) {
if let Ok(rel) = path.strip_prefix(base) {
validate_no_traversal(rel)?;
validate_relative_only(rel)?;
}
return Ok(());
}
return Err(PathError::OutsideBounds);
}
};
if let Ok(ancestor_canonical) = ancestor.canonicalize() {
if !ancestor_canonical.starts_with(&base_canonical) {
return Err(PathError::OutsideBounds);
}
}
if let Ok(rel) = path.strip_prefix(base) {
validate_no_traversal(rel)?;
validate_relative_only(rel)?;
}
Ok(())
}