1use std::ffi::{OsStr, OsString};
7use std::path::{Component, MAIN_SEPARATOR_STR, Path, PathBuf};
8
9pub fn normalize(path: &Path) -> PathBuf {
12 let mut res = PathBuf::with_capacity(path.as_os_str().as_encoded_bytes().len());
13 let mut root_len = 0;
14
15 for component in path.components() {
16 match component {
17 Component::Prefix(p) => res.push(p.as_os_str()),
18 Component::RootDir => {
19 res.push(OsStr::new(MAIN_SEPARATOR_STR));
20 root_len = res.as_os_str().as_encoded_bytes().len();
21 }
22 Component::CurDir => {}
23 Component::ParentDir => {
24 if let Some(len) = res
26 .parent()
27 .map(|p| p.as_os_str().as_encoded_bytes().len())
28 && len >= root_len
30 {
31 let mut bytes = res.into_os_string().into_encoded_bytes();
36 bytes.truncate(len);
37 res = PathBuf::from(unsafe { OsString::from_encoded_bytes_unchecked(bytes) });
38 }
39 }
40 Component::Normal(p) => res.push(p),
41 }
42 }
43
44 res
45}
46
47#[cfg(test)]
48mod tests {
49 use std::ffi::OsString;
50 use std::path::Path;
51
52 use super::*;
53
54 fn norm(s: &str) -> OsString {
55 normalize(Path::new(s)).into_os_string()
56 }
57
58 #[cfg(unix)]
59 #[test]
60 fn test_unix() {
61 assert_eq!(norm("/a/b/c"), "/a/b/c");
62 assert_eq!(norm("/a/b/c/"), "/a/b/c");
63 assert_eq!(norm("/a/./b"), "/a/b");
64 assert_eq!(norm("/a/b/../c"), "/a/c");
65 assert_eq!(norm("/../../a"), "/a");
66 assert_eq!(norm("/../"), "/");
67 assert_eq!(norm("/a//b/c"), "/a/b/c");
68 assert_eq!(norm("/a/b/c/../../../../d"), "/d");
69 assert_eq!(norm("//"), "/");
70 }
71
72 #[cfg(windows)]
73 #[test]
74 fn test_windows() {
75 assert_eq!(norm(r"C:\a\b\c"), r"C:\a\b\c");
76 assert_eq!(norm(r"C:\a\b\c\"), r"C:\a\b\c");
77 assert_eq!(norm(r"C:\a\.\b"), r"C:\a\b");
78 assert_eq!(norm(r"C:\a\b\..\c"), r"C:\a\c");
79 assert_eq!(norm(r"C:\..\..\a"), r"C:\a");
80 assert_eq!(norm(r"C:\..\"), r"C:\");
81 assert_eq!(norm(r"C:\a\\b\c"), r"C:\a\b\c");
82 assert_eq!(norm(r"C:/a\b/c"), r"C:\a\b\c");
83 assert_eq!(norm(r"C:\a\b\c\..\..\..\..\d"), r"C:\d");
84 assert_eq!(norm(r"\\server\share\path"), r"\\server\share\path");
85 }
86}