use std::path::{Path, PathBuf};
pub fn image_root(base_dir: &Path) -> PathBuf {
let start = base_dir
.canonicalize()
.unwrap_or_else(|_| base_dir.to_path_buf());
let home = std::env::var_os("HOME")
.map(PathBuf::from)
.and_then(|h| h.canonicalize().ok());
for ancestor in start.ancestors() {
if home.as_deref() == Some(ancestor) {
break;
}
if ancestor.parent().is_none() {
break;
}
if is_project_root(ancestor) {
return ancestor.to_path_buf();
}
}
start
}
fn is_project_root(dir: &Path) -> bool {
const MARKERS: &[&str] = &[".git", ".hg", ".svn", ".jj"];
MARKERS.iter().any(|m| dir.join(m).exists())
}
pub fn is_within_image_root(candidate: &Path, base_dir: &Path) -> bool {
let root = image_root(base_dir);
match candidate.canonicalize() {
Ok(canonical) => canonical.starts_with(&root),
Err(_) => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn without_a_project_marker_the_root_is_the_document_directory() {
let tmp = tempfile::tempdir().unwrap();
let docs = tmp.path().join("docs");
std::fs::create_dir_all(&docs).unwrap();
assert_eq!(image_root(&docs), docs.canonicalize().unwrap());
}
#[test]
fn a_git_directory_widens_the_root_to_the_project() {
let tmp = tempfile::tempdir().unwrap();
let proj = tmp.path().join("proj");
std::fs::create_dir_all(proj.join(".git")).unwrap();
std::fs::create_dir_all(proj.join("docs")).unwrap();
assert_eq!(image_root(&proj.join("docs")), proj.canonicalize().unwrap());
}
#[test]
fn a_parent_directory_image_is_allowed_inside_a_project() {
let tmp = tempfile::tempdir().unwrap();
let proj = tmp.path().join("proj");
std::fs::create_dir_all(proj.join(".git")).unwrap();
std::fs::create_dir_all(proj.join("docs")).unwrap();
std::fs::create_dir_all(proj.join("images")).unwrap();
let logo = proj.join("images/logo.png");
std::fs::write(&logo, b"\x89PNG\r\n\x1a\n").unwrap();
assert!(is_within_image_root(&logo, &proj.join("docs")));
}
#[test]
fn an_image_outside_the_project_is_refused() {
let tmp = tempfile::tempdir().unwrap();
let proj = tmp.path().join("proj");
std::fs::create_dir_all(proj.join(".git")).unwrap();
std::fs::create_dir_all(proj.join("docs")).unwrap();
let outside = tmp.path().join("secret.png");
std::fs::write(&outside, b"\x89PNG\r\n\x1a\n").unwrap();
assert!(!is_within_image_root(&outside, &proj.join("docs")));
}
#[test]
fn a_missing_file_is_refused() {
let tmp = tempfile::tempdir().unwrap();
assert!(!is_within_image_root(
&tmp.path().join("nope.png"),
tmp.path()
));
}
}