use crate::path_guard::{GuardedSource, PathGuard};
use crate::sensitive::sensitive_warning;
use crate::tests::common::{register_dir_source, register_dir_source_with};
use orbok_core::{HiddenFilePolicy, OrbokError, SymlinkPolicy};
use orbok_db::Catalog;
use std::fs;
use std::path::Path;
fn guard_for(catalog: &Catalog, root: &Path) -> PathGuard {
let record = register_dir_source(catalog, root);
PathGuard::new(vec![GuardedSource::from_record(&record)])
}
#[test]
fn rejects_path_outside_sources() {
let dir = tempfile::tempdir().unwrap();
let outside = tempfile::tempdir().unwrap();
fs::write(outside.path().join("secret.txt"), "secret").unwrap();
let catalog = Catalog::open_in_memory().unwrap();
let guard = guard_for(&catalog, dir.path());
let err = guard.validate(&outside.path().join("secret.txt")).unwrap_err();
assert!(matches!(err, OrbokError::PathOutsideSources));
}
#[test]
fn rejects_dot_dot_traversal() {
let parent = tempfile::tempdir().unwrap();
let root = parent.path().join("source");
fs::create_dir(&root).unwrap();
fs::write(parent.path().join("outside.txt"), "x").unwrap();
let catalog = Catalog::open_in_memory().unwrap();
let guard = guard_for(&catalog, &root);
let sneaky = root.join("..").join("outside.txt");
let err = guard.validate(&sneaky).unwrap_err();
assert!(matches!(err, OrbokError::PathOutsideSources));
}
#[cfg(unix)]
#[test]
fn rejects_symlink_escape() {
let root = tempfile::tempdir().unwrap();
let outside = tempfile::tempdir().unwrap();
fs::write(outside.path().join("escape.txt"), "x").unwrap();
std::os::unix::fs::symlink(
outside.path().join("escape.txt"),
root.path().join("link.txt"),
)
.unwrap();
let catalog = Catalog::open_in_memory().unwrap();
let guard = guard_for(&catalog, root.path());
let err = guard.validate(&root.path().join("link.txt")).unwrap_err();
assert!(matches!(err, OrbokError::PathOutsideSources));
}
#[cfg(unix)]
#[test]
fn ignore_policy_blocks_internal_symlink() {
let root = tempfile::tempdir().unwrap();
fs::write(root.path().join("real.txt"), "x").unwrap();
std::os::unix::fs::symlink(root.path().join("real.txt"), root.path().join("alias.txt"))
.unwrap();
let catalog = Catalog::open_in_memory().unwrap();
let guard = guard_for(&catalog, root.path());
let err = guard.validate(&root.path().join("alias.txt")).unwrap_err();
assert!(matches!(err, OrbokError::PolicyBlocked("symlink_policy_blocked")));
assert!(guard.validate(&root.path().join("real.txt")).is_ok());
}
#[cfg(unix)]
#[test]
fn follow_within_source_admits_internal_rejects_external() {
let root = tempfile::tempdir().unwrap();
let outside = tempfile::tempdir().unwrap();
fs::write(root.path().join("real.txt"), "x").unwrap();
fs::write(outside.path().join("evil.txt"), "x").unwrap();
std::os::unix::fs::symlink(root.path().join("real.txt"), root.path().join("ok.txt")).unwrap();
std::os::unix::fs::symlink(outside.path().join("evil.txt"), root.path().join("bad.txt"))
.unwrap();
let catalog = Catalog::open_in_memory().unwrap();
let record = register_dir_source_with(
&catalog,
root.path(),
HiddenFilePolicy::Exclude,
SymlinkPolicy::FollowWithinSource,
);
let guard = PathGuard::new(vec![GuardedSource::from_record(&record)]);
assert!(guard.validate(&root.path().join("ok.txt")).is_ok());
assert!(matches!(
guard.validate(&root.path().join("bad.txt")).unwrap_err(),
OrbokError::PathOutsideSources
));
}
#[test]
fn hidden_file_excluded_by_default() {
let root = tempfile::tempdir().unwrap();
fs::write(root.path().join(".env"), "SECRET=1").unwrap();
let catalog = Catalog::open_in_memory().unwrap();
let guard = guard_for(&catalog, root.path());
let err = guard.validate(&root.path().join(".env")).unwrap_err();
assert!(matches!(err, OrbokError::PolicyBlocked("hidden_file_excluded")));
}
#[test]
fn oversized_file_blocked() {
let root = tempfile::tempdir().unwrap();
fs::write(root.path().join("big.txt"), vec![b'a'; 64]).unwrap();
let catalog = Catalog::open_in_memory().unwrap();
let mut record = register_dir_source(&catalog, root.path());
record.max_file_size_bytes = Some(16);
let guard = PathGuard::new(vec![GuardedSource::from_record(&record)]);
let err = guard.validate(&root.path().join("big.txt")).unwrap_err();
assert!(matches!(err, OrbokError::PolicyBlocked("file_too_large")));
}
#[test]
fn sensitive_paths_warn() {
assert_eq!(
sensitive_warning(Path::new("/home/user/.ssh")),
Some("credential_directory")
);
assert_eq!(
sensitive_warning(Path::new("/home/user/.config")),
Some("hidden_configuration_directory")
);
assert!(sensitive_warning(Path::new("/home/user/Documents")).is_none());
#[cfg(unix)]
assert_eq!(sensitive_warning(Path::new("/etc/passwd")), Some("system_directory"));
}