From 86f2ba81eb5fe5fec35606dca597ce116ad77927 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 26 Oct 2026 11:01:51 -0700 Subject: [PATCH] Originals: refuse a screenshot target when an original cannot be queried same_file_strict returned false when the metadata of either path could not be read, so a hard link to an original in a folder the process cannot enter was accepted as a screenshot target. It now returns the error unless the path does not exist. --- crates/catalog/src/safe_file.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/crates/catalog/src/safe_file.rs b/crates/catalog/src/safe_file.rs index b0974961f..9b8bf5bcd 100644 --- a/crates/catalog/src/safe_file.rs +++ b/crates/catalog/src/safe_file.rs @@ -274,17 +274,22 @@ pub fn same_file(a: &Path, b: &Path) -> bool { compare_files(a, b, false).unwrap_or(false) } -/// [`same_file`] for a check that must not let an unknown identity pass: `Err` when both paths -/// exist with the same length but the file identity of either cannot be read (on Windows, when -/// either file cannot be opened). A path that does not exist is still `Ok(false)`. +/// [`same_file`] for a check that must not let an unknown identity pass: `Err` when the metadata +/// of either path cannot be read for a reason other than the path not existing (for example a +/// folder it cannot enter), or when both paths exist with the same length but the file identity +/// of either cannot be read (on Windows, when either file cannot be opened). A path that does not +/// exist is still `Ok(false)`. pub fn same_file_strict(a: &Path, b: &Path) -> io::Result { compare_files(a, b, true) } fn compare_files(a: &Path, b: &Path, strict: bool) -> io::Result { - #[cfg(not(windows))] - let _ = strict; - let (Ok(ma), Ok(mb)) = (fs::metadata(a), fs::metadata(b)) else { return Ok(false) }; + let missing = |e: &io::Error| matches!(e.kind(), io::ErrorKind::NotFound | io::ErrorKind::NotADirectory); + let (ma, mb) = match (fs::metadata(a), fs::metadata(b)) { + (Ok(ma), Ok(mb)) => (ma, mb), + (Err(e), _) | (_, Err(e)) if strict && !missing(&e) => return Err(e), + _ => return Ok(false), + }; #[cfg(unix)] { use std::os::unix::fs::MetadataExt; @@ -459,4 +464,20 @@ mod tests { assert!(!same_file(&p, &d.join("missing.jpg"))); let _ = fs::remove_dir_all(&d); } + + #[test] + fn strict_comparison_reports_a_path_it_cannot_query() { + let d = temp("strict-query"); + let p = d.join("x.jpg"); + fs::write(&p, b"x").unwrap(); + // A NUL byte makes the metadata lookup fail with an error other than "not found" on + // every platform, as a folder the process cannot enter does. + let unreadable = Path::new("IMG_1\0.jpg"); + assert!(same_file_strict(unreadable, &p).is_err()); + assert!(same_file_strict(&p, unreadable).is_err()); + assert!(!same_file(unreadable, &p), "the lenient comparison still answers false"); + assert!(!same_file_strict(&d.join("missing.jpg"), &p).unwrap(), "a missing original is another file"); + assert!(!same_file_strict(&p.join("under-a-file.jpg"), &p).unwrap()); + let _ = fs::remove_dir_all(&d); + } }