use std::path::{Component, Path, PathBuf};
use anyhow::{Context, Result};
pub fn normalize_under_root(root: &Path, candidate: &Path) -> Result<PathBuf> {
if candidate.is_absolute() {
anyhow::bail!("absolute paths are not allowed: {}", candidate.display());
}
let canon_root = root
.canonicalize()
.with_context(|| format!("failed to canonicalize root {}", root.display()))?;
let mut normalized = canon_root.clone();
let base_depth = canon_root.components().count();
for comp in candidate.components() {
match comp {
Component::CurDir => {}
Component::Normal(part) => normalized.push(part),
Component::ParentDir => {
if normalized.components().count() <= base_depth {
anyhow::bail!(
"path escapes root ({}): {}",
canon_root.display(),
candidate.display()
);
}
normalized.pop();
}
Component::Prefix(_) | Component::RootDir => {
anyhow::bail!("invalid path component in {}", candidate.display());
}
}
}
if !normalized.starts_with(&canon_root) {
anyhow::bail!(
"path escapes root ({}): {}",
canon_root.display(),
candidate.display()
);
}
Ok(normalized)
}