use std::path::{Path, PathBuf};
use tracing::warn;
pub const PROTECTED_HOME_SUBDIRS: &[&str] = &[
"Downloads",
"Desktop",
"Documents",
"Library",
"Movies",
"Music",
"Pictures",
"Public",
];
pub fn is_home_or_protected_dir(path: &Path, home: Option<&Path>) -> bool {
if path == Path::new("/") {
return true;
}
let Some(home) = home else {
return false;
};
if path == home {
return true;
}
PROTECTED_HOME_SUBDIRS
.iter()
.any(|name| path == home.join(name))
}
pub fn safe_session_cwd(cwd: Option<PathBuf>) -> PathBuf {
let home = dirs::home_dir();
match cwd {
Some(p) if !is_home_or_protected_dir(&p, home.as_deref()) => p,
other => {
let fallback = std::env::temp_dir();
match other {
Some(p) => warn!(
requested = %p.display(),
fallback = %fallback.display(),
"managed session cwd resolves to $HOME or a TCC-protected folder; \
using a scratch dir to avoid recursive home traversal / macOS \
folder-access prompts"
),
None => warn!(
fallback = %fallback.display(),
"no managed session cwd supplied; using a scratch dir instead of $HOME"
),
}
fallback
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_home_or_protected_dir_rejects_home_and_tcc_dirs() {
let home = Path::new("/Users/tester");
assert!(is_home_or_protected_dir(Path::new("/"), Some(home)));
assert!(is_home_or_protected_dir(home, Some(home)));
for d in PROTECTED_HOME_SUBDIRS {
assert!(
is_home_or_protected_dir(&home.join(d), Some(home)),
"bare ~/{d} must be rejected"
);
}
assert!(is_home_or_protected_dir(
Path::new("/Users/tester/Downloads/"),
Some(home)
));
}
#[test]
fn is_home_or_protected_dir_allows_normal_project() {
let home = Path::new("/Users/tester");
assert!(!is_home_or_protected_dir(
Path::new("/Users/tester/trusty-mpm-projects/owner/repo/.worktrees/x"),
Some(home)
));
assert!(!is_home_or_protected_dir(
&home.join("Downloads").join("my-project"),
Some(home)
));
assert!(!is_home_or_protected_dir(
Path::new("/Users/tester/Downloads"),
None
));
}
#[test]
fn safe_session_cwd_keeps_explicit_project_dir() {
let p = PathBuf::from("/Users/tester/trusty-mpm-projects/o/r/.worktrees/s");
assert_eq!(safe_session_cwd(Some(p.clone())), p);
}
#[test]
fn safe_session_cwd_replaces_home_and_missing() {
let scratch = std::env::temp_dir();
assert_eq!(safe_session_cwd(None), scratch);
if let Some(home) = dirs::home_dir() {
assert_eq!(safe_session_cwd(Some(home.clone())), scratch);
assert_eq!(safe_session_cwd(Some(home.join("Downloads"))), scratch);
}
assert_eq!(safe_session_cwd(Some(PathBuf::from("/"))), scratch);
}
}