1use std::path::{Component, Path, PathBuf};
2
3pub(crate) fn normalize(path: &Path) -> Option<PathBuf> {
7 let mut components = path.components().peekable();
8 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek() {
9 let buf = PathBuf::from(c.as_os_str());
10 components.next();
11 buf
12 } else {
13 PathBuf::new()
14 };
15 let mut has_root = false;
16
17 for component in components {
18 match component {
19 Component::Prefix(..) => unreachable!(),
20 Component::RootDir => {
21 ret.push(component.as_os_str());
22 has_root = true;
23 }
24 Component::CurDir => {}
25 Component::ParentDir => {
26 if ret
28 .components()
29 .next_back()
30 .is_some_and(|component| component == Component::ParentDir)
31 {
32 ret.push(component.as_os_str());
33 } else if ret.pop() {
34 } else if has_root {
36 return None;
38 } else {
39 ret.push(component.as_os_str());
41 }
42 }
43 Component::Normal(c) => {
44 ret.push(c);
45 }
46 }
47 }
48
49 Some(ret)
50}
51
52#[cfg(test)]
53mod tests {
54 #[cfg(unix)]
55 use std::path::{Path, PathBuf};
56
57 #[test]
58 #[cfg(unix)]
59 fn test_normalize() {
60 assert_eq!(
62 crate::fs::normalize(Path::new("a/b/c")),
63 Some(PathBuf::from("a/b/c"))
64 );
65
66 assert_eq!(
68 crate::fs::normalize(Path::new("a/b/../c")),
69 Some(PathBuf::from("a/c"))
70 );
71
72 assert_eq!(
74 crate::fs::normalize(Path::new("./a/b")),
75 Some(PathBuf::from("a/b"))
76 );
77
78 assert_eq!(
80 crate::fs::normalize(Path::new("outside")),
81 Some(PathBuf::from("outside"))
82 );
83
84 assert_eq!(
86 crate::fs::normalize(Path::new("../../../../")),
87 Some(PathBuf::from("../../../../"),)
88 );
89
90 assert_eq!(
92 crate::fs::normalize(Path::new("a/b/../../c")),
93 Some(PathBuf::from("c"))
94 );
95
96 assert_eq!(crate::fs::normalize(Path::new("/a/../..")), None);
98
99 assert_eq!(
101 crate::fs::normalize(Path::new("/./a/../b")),
102 Some(PathBuf::from("/b"))
103 );
104
105 assert_eq!(
107 crate::fs::normalize(Path::new("a/b/c/")),
108 Some(PathBuf::from("a/b/c"))
109 );
110
111 assert_eq!(
113 crate::fs::normalize(Path::new("a/b/.")),
114 Some(PathBuf::from("a/b"))
115 );
116
117 assert_eq!(
119 crate::fs::normalize(Path::new("a/b/..")),
120 Some(PathBuf::from("a"))
121 );
122
123 assert_eq!(
125 crate::fs::normalize(Path::new("../x/y")),
126 Some(PathBuf::from("../x/y"))
127 );
128
129 assert_eq!(
131 crate::fs::normalize(Path::new("../../a/b/../c")),
132 Some(PathBuf::from("../../a/c"))
133 );
134
135 #[cfg(windows)]
137 assert_eq!(
138 crate::fs::normalize(Path::new(r"C:\a\..\b")),
139 Some(PathBuf::from(r"C:\b"))
140 );
141
142 #[cfg(windows)]
145 assert_eq!(
146 crate::fs::normalize(Path::new(r"C:..\a")),
147 Some(PathBuf::from(r"C:..\a"))
148 );
149
150 assert_eq!(
152 crate::fs::normalize(Path::new("/")),
153 Some(PathBuf::from("/"))
154 );
155
156 assert_eq!(
158 crate::fs::normalize(Path::new("..")),
159 Some(PathBuf::from(".."))
160 );
161 }
162}