use std::path::Path;
use crate::{Error, Result};
use super::PathSafety;
pub(crate) fn validate_path(
entry_idx: usize,
entry_path: &str,
dest: &Path,
policy: &PathSafety,
) -> Result<std::path::PathBuf> {
match policy {
PathSafety::Disabled => Ok(dest.join(entry_path)),
PathSafety::Relaxed | PathSafety::Strict => {
let full_path = dest.join(entry_path);
let canonical_dest = dest.canonicalize().unwrap_or_else(|_| dest.to_path_buf());
if *policy == PathSafety::Strict {
for component in std::path::Path::new(entry_path).components() {
if let std::path::Component::Normal(name) = component {
if let Some(name_str) = name.to_str() {
if name_str.starts_with('/') {
return Err(Error::PathTraversal {
entry_index: entry_idx,
path: entry_path.to_string(),
});
}
}
}
}
}
if !full_path.starts_with(&canonical_dest) && !full_path.starts_with(dest) {
return Err(Error::PathTraversal {
entry_index: entry_idx,
path: entry_path.to_string(),
});
}
Ok(full_path)
}
}
}
pub(crate) fn validate_symlink_target(
entry_idx: usize,
entry_path: &str,
target: &str,
) -> Result<()> {
if target.starts_with('/') || target.starts_with('\\') {
return Err(Error::SymlinkTargetEscape {
entry_index: entry_idx,
path: entry_path.to_string(),
target: target.to_string(),
});
}
if target.len() >= 2 && target.chars().nth(1) == Some(':') {
return Err(Error::SymlinkTargetEscape {
entry_index: entry_idx,
path: entry_path.to_string(),
target: target.to_string(),
});
}
let entry_parent = Path::new(entry_path).parent().unwrap_or(Path::new(""));
let initial_depth = entry_parent
.components()
.filter(|c| matches!(c, std::path::Component::Normal(_)))
.count() as i32;
let mut depth = initial_depth;
for component in Path::new(target).components() {
match component {
std::path::Component::ParentDir => {
depth -= 1;
if depth < 0 {
return Err(Error::SymlinkTargetEscape {
entry_index: entry_idx,
path: entry_path.to_string(),
target: target.to_string(),
});
}
}
std::path::Component::Normal(_) => {
depth += 1;
}
_ => {}
}
}
Ok(())
}
#[cfg(unix)]
pub(crate) fn create_symlink(link_path: &Path, target: &str) -> Result<u64> {
std::os::unix::fs::symlink(target, link_path).map_err(Error::Io)?;
Ok(0)
}
#[cfg(windows)]
pub(crate) fn create_symlink(link_path: &Path, target: &str) -> Result<u64> {
let target_path = link_path.parent().map(|p| p.join(target));
if let Some(ref tp) = target_path {
if tp.is_dir() {
std::os::windows::fs::symlink_dir(target, link_path).map_err(Error::Io)?;
return Ok(0);
}
}
std::os::windows::fs::symlink_file(target, link_path).map_err(Error::Io)?;
Ok(0)
}
#[cfg(not(any(unix, windows)))]
pub(crate) fn create_symlink(_link_path: &Path, _target: &str) -> Result<u64> {
Err(Error::UnsupportedFeature {
feature: "symbolic links on this platform",
})
}