use std::path::{Path, PathBuf};
pub(crate) fn copy_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let meta = entry.metadata()?;
let dest_path = dst.join(entry.file_name());
if meta.is_dir() {
copy_dir(&entry.path(), &dest_path)?;
} else {
std::fs::copy(entry.path(), dest_path)?;
}
}
Ok(())
}
#[derive(Debug)]
pub(crate) struct ContainedPath(PathBuf);
impl ContainedPath {
pub(crate) fn child(base: &Path, name: &str) -> Result<Self, String> {
for comp in Path::new(name).components() {
if !matches!(comp, std::path::Component::Normal(_)) {
return Err(format!(
"Invalid path component in '{name}': path traversal detected"
));
}
}
let path = base.join(name);
if path.exists() {
let canonical = path
.canonicalize()
.map_err(|e| format!("Path resolution failed: {e}"))?;
let base_canonical = base
.canonicalize()
.map_err(|e| format!("Base path resolution failed: {e}"))?;
if !canonical.starts_with(&base_canonical) {
return Err(format!("Path '{name}' escapes base directory"));
}
}
Ok(Self(path))
}
}
impl std::ops::Deref for ContainedPath {
type Target = Path;
fn deref(&self) -> &Path {
&self.0
}
}
impl AsRef<Path> for ContainedPath {
fn as_ref(&self) -> &Path {
self
}
}