radix_router/path.rs
1/// CleanPath is the URL version of path.Clean, it returns a canonical URL path
2/// for p, eliminating . and .. elements.
3///
4/// The following rules are applied iteratively until no further processing can
5/// be done:
6/// 1. Replace multiple slashes with a single slash.
7/// 2. Eliminate each . path name element (the current directory).
8/// 3. Eliminate each inner .. path name element (the parent directory)
9/// along with the non-.. element that precedes it.
10/// 4. Eliminate .. elements that begin a rooted path:
11/// that is, replace "/.." by "/" at the beginning of a path.
12///
13/// If the result of this process is an empty string, "/" is returned
14pub fn clean_path(p: &str) -> String {
15 // Turn empty string into "/"
16 if p == "" {
17 return "/".to_string();
18 }
19
20 let n = p.len();
21 let mut buf: Vec<u8> = Vec::new();
22
23 // Invariants:
24 // reading from path; r is index of next byte to process.
25 // writing to buf; w is index of next byte to write.
26
27 // path must start with '/'
28
29 let mut r = 1;
30 let mut w = 1;
31
32 if !p.starts_with("/") {
33 r = 0;
34 buf.resize(n + 1, 0);
35 buf[0] = b'/';
36 }
37
38 let mut trailing = n > 1 && p.ends_with("/");
39 let p = p.as_bytes();
40
41 // A bit more clunky without a 'lazybuf' like the path package, but the loop
42 // gets completely inlined (bufApp). So in contrast to the path package this
43 // loop has no expensive function calls (except 1x make)
44
45 while r < n {
46 match p[r] {
47 b'/' => r += 1, // empty path element, trailing slash is added after the end
48 b'.' => {
49 if r + 1 == n {
50 trailing = true;
51 r += 1;
52 } else if p[r + 1] == b'/' {
53 // . element
54 r += 2;
55 } else if p[r + 1] == b'.' && (r + 2 == n || p[r + 2] == b'/') {
56 // .. element: remove to last /
57 r += 3;
58
59 if w > 1 {
60 // can backtrack
61 w -= 1;
62
63 if buf.is_empty() {
64 while w > 1 && p[w] != b'/' {
65 w -= 1;
66 }
67 } else {
68 while w > 1 && buf[w] != b'/' {
69 w -= 1;
70 }
71 }
72 }
73 }
74 }
75 _ => {
76 // real path element.
77 // add slash if needed
78 if w > 1 {
79 buf_app(&mut buf, p, w, b'/');
80 w += 1;
81 }
82
83 // copy element
84 while r < n && p[r] != b'/' {
85 buf_app(&mut buf, p, w, p[r]);
86 w += 1;
87 r += 1;
88 }
89 }
90 }
91 }
92
93 // re-append trailing slash
94 if trailing && w > 1 {
95 buf_app(&mut buf, p, w, b'/');
96 w += 1;
97 }
98
99 if buf.is_empty() {
100 return String::from_utf8(p[..w].to_vec()).unwrap();
101 }
102 String::from_utf8(buf[..w].to_vec()).unwrap()
103}
104
105/// internal helper to lazily create a buffer if necessary
106fn buf_app(buf: &mut Vec<u8>, s: &[u8], w: usize, c: u8) {
107 if buf.is_empty() {
108 if s[w] == c {
109 return;
110 }
111 buf.resize(s.len(), 0);
112 buf[..w].copy_from_slice(&s[..w]);
113 }
114 buf[w] = c;
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 // path, result
122 fn clean_tests() -> Vec<(&'static str, &'static str)> {
123 vec![
124 // Already clean
125 ("/", "/"),
126 ("/abc", "/abc"),
127 ("/a/b/c", "/a/b/c"),
128 ("/abc/", "/abc/"),
129 ("/a/b/c/", "/a/b/c/"),
130 // missing root
131 ("", "/"),
132 ("a/", "/a/"),
133 ("abc", "/abc"),
134 ("abc/def", "/abc/def"),
135 ("a/b/c", "/a/b/c"),
136 // Remove doubled slash
137 ("//", "/"),
138 ("/abc//", "/abc/"),
139 ("/abc/def//", "/abc/def/"),
140 ("/a/b/c//", "/a/b/c/"),
141 ("/abc//def//ghi", "/abc/def/ghi"),
142 ("//abc", "/abc"),
143 ("///abc", "/abc"),
144 ("//abc//", "/abc/"),
145 // Remove . elements
146 (".", "/"),
147 ("./", "/"),
148 ("/abc/./def", "/abc/def"),
149 ("/./abc/def", "/abc/def"),
150 ("/abc/.", "/abc/"),
151 // Remove .. elements
152 ("..", "/"),
153 ("../", "/"),
154 ("../../", "/"),
155 ("../..", "/"),
156 ("../../abc", "/abc"),
157 ("/abc/def/ghi/../jkl", "/abc/def/jkl"),
158 ("/abc/def/../ghi/../jkl", "/abc/jkl"),
159 ("/abc/def/..", "/abc"),
160 ("/abc/def/../..", "/"),
161 ("/abc/def/../../..", "/"),
162 ("/abc/def/../../..", "/"),
163 ("/abc/def/../../../ghi/jkl/../../../mno", "/mno"),
164 // Combinations
165 ("abc/./../def", "/def"),
166 ("abc//./../def", "/def"),
167 ("abc/../../././../def", "/def"),
168 ]
169 }
170
171 #[test]
172 fn test_path_clean() {
173 let tests = clean_tests();
174 for test in tests {
175 let s = clean_path(test.0);
176 assert_eq!(test.1, s);
177
178 let s = clean_path(test.1);
179 assert_eq!(test.1, s);
180 }
181 }
182
183 // #[test]
184 // fn test_path_clean_mallocs() {
185
186 // }
187}