use std::borrow::Cow;
use std::ffi::OsStr;
use std::path::Path;
pub(super) fn is_skipped_dir(entry: &walkdir::DirEntry, skip_dirs: &[&str]) -> bool {
if !entry.file_type().is_dir() {
return false;
}
let name = entry.file_name().to_string_lossy();
skip_dirs.iter().any(|skip| name.eq_ignore_ascii_case(skip))
}
pub(super) fn lossy_filename_with_warning<'a>(
filename: &'a OsStr,
full_path: &Path,
) -> Cow<'a, str> {
match filename.to_str() {
Some(s) => Cow::Borrowed(s),
None => {
tracing::warn!(
"non-UTF-8 filename in {}; matched with lossy conversion (possible evasion attempt in untrusted package)",
full_path.display(),
);
filename.to_string_lossy()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn is_skipped_dir_matches_skip_list_entries_case_insensitively() {
let dir = tempdir().expect("tempdir");
for name in ["Node_Modules", "VENV", ".GIT", "Target", "DiSt"] {
let path = dir.path().join(name);
fs::create_dir_all(&path).expect("create dir");
}
let skip_dirs = &["node_modules", "venv", ".git", "target", "dist"];
let walker = walkdir::WalkDir::new(dir.path()).min_depth(1).max_depth(1);
for entry in walker.into_iter().filter_map(Result::ok) {
assert!(
is_skipped_dir(&entry, skip_dirs),
"{} must be pruned (case-insensitive); skip_dirs={skip_dirs:?}",
entry.file_name().to_string_lossy()
);
}
}
#[test]
fn is_skipped_dir_does_not_prune_unrelated_directories() {
let dir = tempdir().expect("tempdir");
for name in ["src", "scripts", "tests", "docs", "node_modules_backup"] {
fs::create_dir_all(dir.path().join(name)).expect("create dir");
}
let skip_dirs = &["node_modules", "venv", ".git", "target", "dist"];
let walker = walkdir::WalkDir::new(dir.path()).min_depth(1).max_depth(1);
for entry in walker.into_iter().filter_map(Result::ok) {
assert!(
!is_skipped_dir(&entry, skip_dirs),
"{} must NOT be pruned; skip_dirs={skip_dirs:?}",
entry.file_name().to_string_lossy()
);
}
}
}