use crate::error::Result;
use crate::paths::normalize_roots;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
pub(crate) fn subtract_denied_roots(
mut allowed: Vec<PathBuf>,
denied: &[PathBuf],
) -> Result<Vec<PathBuf>> {
normalize_roots(&mut allowed);
let mut denied = denied.to_vec();
normalize_roots(&mut denied);
let mut roots = Vec::new();
for root in allowed {
roots.extend(scan_allowed_root(&root, &denied, true)?);
}
normalize_roots(&mut roots);
Ok(roots)
}
fn scan_allowed_root(
root: &Path,
denied: &[PathBuf],
is_explicit_root: bool,
) -> Result<Vec<PathBuf>> {
if denied
.iter()
.any(|denied_root| root == denied_root || root.starts_with(denied_root))
{
return Ok(Vec::new());
}
let has_denied_descendant = denied
.iter()
.any(|denied_root| denied_root.starts_with(root));
let metadata = match fs::symlink_metadata(root) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => {
return Ok(vec![root.to_path_buf()]);
}
Err(source) => return Err(source.into()),
};
let file_type = metadata.file_type();
if file_type.is_symlink() && !is_explicit_root {
return Ok(Vec::new());
}
if !has_denied_descendant {
return Ok(vec![root.to_path_buf()]);
}
if !file_type.is_dir() {
return Ok(vec![root.to_path_buf()]);
}
let mut roots = Vec::new();
let entries = fs::read_dir(root)?;
for entry in entries {
let entry = entry?;
let child = entry.path();
roots.extend(scan_allowed_root(&child, denied, false)?);
}
Ok(roots)
}