use crate::PathBoundary;
use std::fs;
#[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!(
format!("{}", joined.virtualpath_display()),
"/foo/bar.txt/baz.txt"
);
let outside = virtual_path.virtual_join("../../../../etc/passwd").unwrap();
assert_eq!(format!("{}", outside.virtualpath_display()), "/etc/passwd");
let parent = virtual_path.virtualpath_parent().unwrap();
assert!(parent.is_some());
let actual_parent = parent.unwrap();
assert_eq!(format!("{}", actual_parent.virtualpath_display()), "/foo");
let root_virtual: crate::path::virtual_path::VirtualPath<()> =
crate::path::virtual_path::VirtualPath::with_root(temp_dir.as_ref()).unwrap();
let parent_none = root_virtual.virtualpath_parent().unwrap();
assert!(parent_none.is_none());
}
#[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!(
format!("{}", with_name.virtualpath_display()),
"/foo/newname.txt"
);
let root_virtual: crate::path::virtual_path::VirtualPath<()> =
crate::path::virtual_path::VirtualPath::with_root(temp_dir.as_ref()).unwrap();
let escape_attempt = root_virtual
.virtualpath_with_file_name("../../etc/passwd")
.unwrap();
assert_eq!(
format!("{}", escape_attempt.virtualpath_display()),
"/etc/passwd"
);
let with_ext = virtual_path.virtualpath_with_extension("log").unwrap();
assert_eq!(
format!("{}", with_ext.virtualpath_display()),
"/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]
#[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!(
format!("{}", legitimate_link.virtualpath_display()),
"/docs/shared/link_to_target"
);
let traversal_through_link = virtual_start
.virtual_join("../shared/link_to_target/../../../../etc/passwd")
.unwrap();
assert_eq!(
format!("{}", traversal_through_link.virtualpath_display()),
"/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_disp = format!("{}", complex_traversal.virtualpath_display());
assert!(
complex_disp == "/shared/link_to_target" || complex_disp == "/target.txt",
"unexpected complex traversal display: {complex_disp}"
);
}
#[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!(
format!("{}", traversal.virtualpath_display()),
"/etc/passwd"
);
let windows_traversal = virtual_start
.virtual_join("..\\..\\..\\..\\Windows\\System32\\config\\SAM")
.unwrap();
assert_eq!(
format!("{}", windows_traversal.virtualpath_display()),
"/Windows/System32/config/SAM"
);
let unc_like = virtual_start
.virtual_join("../../../shared/target.txt")
.unwrap();
assert_eq!(
format!("{}", unc_like.virtualpath_display()),
"/shared/target.txt"
);
}