sandbox_runtime/utils/
path.rs1use std::path::{Path, PathBuf};
4
5pub fn normalize_path_for_sandbox(path: &str) -> String {
10 let expanded = expand_home(path);
11
12 match std::fs::canonicalize(&expanded) {
14 Ok(canonical) => canonical.display().to_string(),
15 Err(_) => expanded,
16 }
17}
18
19pub fn expand_home(path: &str) -> String {
21 if path.starts_with("~/") {
22 if let Some(home) = dirs::home_dir() {
23 return format!("{}{}", home.display(), &path[1..]);
24 }
25 } else if path == "~" {
26 if let Some(home) = dirs::home_dir() {
27 return home.display().to_string();
28 }
29 }
30 path.to_string()
31}
32
33pub fn normalize_case_for_comparison(path: &str) -> String {
35 #[cfg(target_os = "macos")]
36 {
37 path.to_lowercase()
38 }
39 #[cfg(not(target_os = "macos"))]
40 {
41 path.to_string()
42 }
43}
44
45pub fn contains_glob_chars(path: &str) -> bool {
47 path.contains('*') || path.contains('?') || path.contains('[') || path.contains('{')
48}
49
50pub fn remove_trailing_glob_suffix(path: &str) -> String {
52 let mut result = path.to_string();
53
54 while result.ends_with("/**") {
56 result = result[..result.len() - 3].to_string();
57 }
58
59 while result.ends_with("/*") {
61 result = result[..result.len() - 2].to_string();
62 }
63
64 result
65}
66
67pub fn is_symlink_outside_boundary(original: &Path, resolved: &Path) -> bool {
70 if resolved == Path::new("/") {
72 return true;
73 }
74
75 if original.starts_with(resolved) && original != resolved {
77 return true;
78 }
79
80 false
81}
82
83pub fn get_parent_path(path: &Path) -> Option<&Path> {
85 let parent = path.parent()?;
86 if parent.as_os_str().is_empty() {
87 None
88 } else {
89 Some(parent)
90 }
91}
92
93pub fn join_paths<P: AsRef<Path>>(base: &Path, path: P) -> PathBuf {
95 let path = path.as_ref();
96 if path.is_absolute() {
97 path.to_path_buf()
98 } else {
99 base.join(path)
100 }
101}
102
103pub fn is_symlink(path: &Path) -> bool {
105 path.symlink_metadata()
106 .map(|m| m.file_type().is_symlink())
107 .unwrap_or(false)
108}
109
110pub fn resolve_symlink(path: &Path) -> std::io::Result<PathBuf> {
112 std::fs::read_link(path)
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_expand_home() {
121 let home = dirs::home_dir().unwrap();
122
123 assert_eq!(expand_home("~"), home.display().to_string());
124 assert_eq!(
125 expand_home("~/Documents"),
126 format!("{}/Documents", home.display())
127 );
128 assert_eq!(expand_home("/absolute/path"), "/absolute/path");
129 assert_eq!(expand_home("relative/path"), "relative/path");
130 }
131
132 #[test]
133 fn test_contains_glob_chars() {
134 assert!(contains_glob_chars("*.txt"));
135 assert!(contains_glob_chars("src/**/*.rs"));
136 assert!(contains_glob_chars("file?.txt"));
137 assert!(contains_glob_chars("file[0-9].txt"));
138 assert!(contains_glob_chars("file{a,b}.txt"));
139 assert!(!contains_glob_chars("/plain/path"));
140 }
141
142 #[test]
143 fn test_remove_trailing_glob_suffix() {
144 assert_eq!(remove_trailing_glob_suffix("/path/**"), "/path");
145 assert_eq!(remove_trailing_glob_suffix("/path/*"), "/path");
146 assert_eq!(remove_trailing_glob_suffix("/path/**/**"), "/path");
147 assert_eq!(remove_trailing_glob_suffix("/path"), "/path");
148 }
149}