claude_agent/security/path/
mod.rs1mod resolver;
4
5pub use resolver::SafePath;
6
7use std::ffi::OsString;
8use std::path::{Component, Path, PathBuf};
9
10pub const DEFAULT_MAX_SYMLINK_DEPTH: u8 = 10;
11
12pub fn normalize_path(path: &Path) -> PathBuf {
13 let mut components = Vec::new();
14
15 for component in path.components() {
16 match component {
17 Component::ParentDir => {
18 if !components.is_empty()
19 && !matches!(
20 components.last(),
21 Some(Component::RootDir) | Some(Component::Prefix(_))
22 )
23 {
24 components.pop();
25 }
26 }
27 Component::CurDir => {}
28 c => components.push(c),
29 }
30 }
31
32 if components.is_empty() {
33 PathBuf::from(".")
34 } else {
35 components.iter().collect()
36 }
37}
38
39pub fn extract_relative_components(path: &Path) -> Vec<OsString> {
40 path.components()
41 .filter_map(|c| match c {
42 Component::Normal(s) => Some(s.to_os_string()),
43 _ => None,
44 })
45 .collect()
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn test_normalize_path() {
54 assert_eq!(
55 normalize_path(Path::new("/a/b/../c")),
56 PathBuf::from("/a/c")
57 );
58 assert_eq!(normalize_path(Path::new("/a/./b")), PathBuf::from("/a/b"));
59 assert_eq!(
60 normalize_path(Path::new("/a/b/../../c")),
61 PathBuf::from("/c")
62 );
63 }
64
65 #[test]
66 fn test_extract_relative_components() {
67 let components = extract_relative_components(Path::new("/a/b/c"));
68 assert_eq!(components.len(), 3);
69 assert_eq!(components[0], "a");
70 assert_eq!(components[1], "b");
71 assert_eq!(components[2], "c");
72 }
73}