This commit is contained in:
Brandon Thomas 2026-10-13 02:04:06 -04:00
commit 8a640dc70b
1 changed files with 30 additions and 1 deletions

View File

@ -57,6 +57,8 @@ pub fn files_in(root: &Path, skip: Option<&Path>, limits: Limits, mut keep: impl
if let Ok(c) = std::fs::canonicalize(root) {
linked.insert(c);
}
// Include aliases to the library in the exclusion, not just its lexical path.
let skip_canonical = skip.and_then(|p| std::fs::canonicalize(p).ok());
// (path, depth of the folder it is in, is a folder); popped in path order
let mut stack: Vec<(PathBuf, usize, bool)> = vec![(root.to_path_buf(), 0, true)];
while let Some((path, depth, is_dir)) = stack.pop() {
@ -66,7 +68,9 @@ pub fn files_in(root: &Path, skip: Option<&Path>, limits: Limits, mut keep: impl
}
continue;
}
if skip.is_some_and(|s| path == s) {
if skip.is_some_and(|s| path == s)
|| skip_canonical.as_ref().is_some_and(|s| std::fs::canonicalize(&path).is_ok_and(|p| p.starts_with(s)))
{
continue;
}
if depth >= limits.max_depth {
@ -165,6 +169,31 @@ mod tests {
let _ = std::fs::remove_dir_all(&d);
}
/// Folder imports must not revisit the user's own library through symlinks
/// or through an alias to the library's parent.
#[cfg(unix)]
#[test]
fn skipped_library_stays_skipped_through_symlinks_and_ancestors() {
let d = tmp("skip-alias");
let root = d.join("incoming");
let library = d.join("library");
std::fs::create_dir_all(&root).unwrap();
std::fs::create_dir_all(library.join("Originals")).unwrap();
std::fs::write(root.join("ordinary.jpg"), b"photo").unwrap();
std::fs::write(library.join("Originals").join("library-only.jpg"), b"original").unwrap();
std::os::unix::fs::symlink(&library, root.join("library-link")).unwrap();
std::os::unix::fs::symlink(&d, root.join("parent-link")).unwrap();
let photos = files_in(&root, Some(&library), Limits::default(), |p| p.extension().is_some_and(|e| e == "jpg"));
assert!(photos.files.contains(&root.join("ordinary.jpg")));
assert!(!photos.files.iter().any(|p| p.ends_with("library-only.jpg")), "{photos:?}");
// Scanning a subfolder already inside the excluded library is also forbidden.
let inside = files_in(&library.join("Originals"), Some(&library), Limits::default(), |_| true);
assert!(inside.files.is_empty(), "{inside:?}");
let _ = std::fs::remove_dir_all(&d);
}
/// A link back up the tree (a loop) is followed at most once: the walk ends.
#[cfg(unix)]
#[test]