use crate::PathBoundary;
#[cfg(feature = "virtual-path")]
use std::fs;
use std::io::{Read, Write};
#[cfg(feature = "virtual-path")]
#[test]
fn test_virtual_path_join_and_parent() {
let temp = tempfile::tempdir().unwrap();
let temp_dir: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let jailed = temp_dir.strict_join("foo/bar.txt").unwrap();
let virtual_path = jailed.virtualize();
let joined = virtual_path.virtual_join("baz.txt").unwrap();
assert_eq!(
joined.virtualpath_display().to_string(),
"/foo/bar.txt/baz.txt"
);
let outside = virtual_path.virtual_join("../../../../etc/passwd").unwrap();
assert_eq!(outside.virtualpath_display().to_string(), "/etc/passwd");
let parent = virtual_path.virtualpath_parent().unwrap();
assert!(parent.is_some());
let actual_parent = parent.unwrap();
assert_eq!(actual_parent.virtualpath_display().to_string(), "/foo");
let root_virtual: crate::path::virtual_path::VirtualPath<()> =
crate::path::virtual_path::VirtualPath::with_root(temp_dir.interop_path()).unwrap();
let parent_none = root_virtual.virtualpath_parent().unwrap();
assert!(parent_none.is_none());
}
#[cfg(feature = "virtual-path")]
#[test]
fn test_virtual_path_pathbuf_methods() {
let temp = tempfile::tempdir().unwrap();
let temp_dir: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let restricted = temp_dir.strict_join("foo/bar.txt").unwrap();
let virtual_path = restricted.virtualize();
let with_name = virtual_path
.virtualpath_with_file_name("newname.txt")
.unwrap();
assert_eq!(
with_name.virtualpath_display().to_string(),
"/foo/newname.txt"
);
let root_virtual: crate::path::virtual_path::VirtualPath<()> =
crate::path::virtual_path::VirtualPath::with_root(temp_dir.interop_path()).unwrap();
let escape_attempt = root_virtual
.virtualpath_with_file_name("../../etc/passwd")
.unwrap();
assert_eq!(
escape_attempt.virtualpath_display().to_string(),
"/etc/passwd"
);
let with_ext = virtual_path.virtualpath_with_extension("log").unwrap();
assert_eq!(with_ext.virtualpath_display().to_string(), "/foo/bar.log");
let strict_again = virtual_path.unvirtual();
let inner = strict_again.unstrict();
let expected_path = temp_dir.path().join("foo/bar.txt");
assert_eq!(inner.to_string_lossy(), expected_path.to_string_lossy());
}
#[test]
fn test_strict_path_create_file() {
let temp = tempfile::tempdir().unwrap();
let test_dir: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let strict = test_dir.strict_join("logs/output.txt").unwrap();
strict.create_parent_dir_all().unwrap();
let mut file = strict.create_file().unwrap();
file.write_all(b"hello strict").unwrap();
drop(file);
let mut file = strict.open_file().unwrap();
let mut content = String::new();
file.read_to_string(&mut content).unwrap();
assert_eq!(content, "hello strict");
}
#[cfg(feature = "virtual-path")]
#[test]
fn test_virtual_path_create_file() {
let temp = tempfile::tempdir().unwrap();
let test_dir: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let strict = test_dir.strict_join("reports/summary.txt").unwrap();
strict.create_parent_dir_all().unwrap();
let virtual_path = strict.clone().virtualize();
let mut file = virtual_path.create_file().unwrap();
file.write_all(b"virtual summary").unwrap();
drop(file);
let mut file = virtual_path.open_file().unwrap();
let mut content = String::new();
file.read_to_string(&mut content).unwrap();
assert_eq!(content, "virtual summary");
}
#[cfg(feature = "virtual-path")]
#[test]
#[cfg(unix)] fn test_virtual_path_symlink_edge_cases() {
let temp = tempfile::tempdir().unwrap();
let temp_dir: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let docs_dir = temp.path().join("docs");
let shared_dir = temp.path().join("shared");
let outside_target = temp.path().join("target.txt");
fs::create_dir_all(&docs_dir).unwrap();
fs::create_dir_all(&shared_dir).unwrap();
fs::write(&outside_target, "target content").unwrap();
let link_path = shared_dir.join("link_to_target");
std::os::unix::fs::symlink(&outside_target, &link_path).unwrap();
let start_path = temp_dir.strict_join("docs/start.txt").unwrap();
let virtual_start = start_path.virtualize();
let legitimate_link = virtual_start
.virtual_join("../shared/link_to_target")
.unwrap();
let sys_path = legitimate_link.clone().unvirtual();
eprintln!(
"legitimate_link display = {} (sys = {:?})",
legitimate_link.virtualpath_display(),
sys_path.interop_path()
);
assert_eq!(
legitimate_link.virtualpath_display().to_string(),
"/docs/shared/link_to_target"
);
let traversal_through_link = virtual_start
.virtual_join("../shared/link_to_target/../../../../etc/passwd")
.unwrap();
assert_eq!(
traversal_through_link.virtualpath_display().to_string(),
"/etc/passwd"
);
let outside_dir = temp.path().parent().unwrap().join("outside");
let malicious_file = outside_dir.join("sensitive.txt");
fs::create_dir_all(&outside_dir).unwrap();
fs::write(&malicious_file, "sensitive data").unwrap();
let malicious_link = shared_dir.join("bad_link");
std::os::unix::fs::symlink(&malicious_file, &malicious_link).unwrap();
let safe_access = virtual_start.virtual_join("../shared/bad_link").unwrap();
assert!(safe_access
.virtualpath_display()
.to_string()
.contains("bad_link"));
let complex_traversal = virtual_start
.virtual_join("../../../../../../../shared/link_to_target")
.unwrap();
let complex_virtual_display = complex_traversal.virtualpath_display().to_string();
assert!(
complex_virtual_display == "/shared/link_to_target"
|| complex_virtual_display == "/target.txt"
|| complex_virtual_display.contains("/target.txt"), "unexpected complex traversal display: {complex_virtual_display}"
);
}
#[cfg(feature = "virtual-path")]
#[test]
#[cfg(windows)]
fn test_virtual_path_windows_symlink_edge_cases() {
let temp = tempfile::tempdir().unwrap();
let temp_dir: PathBoundary = PathBoundary::try_new(temp.path()).unwrap();
let docs_dir = temp.path().join("docs");
let shared_dir = temp.path().join("shared");
let target_file = temp.path().join("target.txt");
fs::create_dir_all(&docs_dir).unwrap();
fs::create_dir_all(&shared_dir).unwrap();
fs::write(&target_file, "target content").unwrap();
let start_path = temp_dir.strict_join("docs/start.txt").unwrap();
let virtual_start = start_path.virtualize();
let traversal = virtual_start
.virtual_join("../../../../etc/passwd")
.unwrap();
assert_eq!(traversal.virtualpath_display().to_string(), "/etc/passwd");
let windows_traversal = virtual_start
.virtual_join("..\\..\\..\\..\\Windows\\System32\\config\\SAM")
.unwrap();
assert_eq!(
windows_traversal.virtualpath_display().to_string(),
"/Windows/System32/config/SAM"
);
let unc_like = virtual_start
.virtual_join("../../../shared/target.txt")
.unwrap();
assert_eq!(
unc_like.virtualpath_display().to_string(),
"/shared/target.txt"
);
}