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}