1use std::io::{Error, ErrorKind, Result};
7use std::path::{Component, Path, PathBuf};
8
9const MAX_SYMLINK_DEPTH: u32 = 255;
12
13fn do_scoped_resolve<R: AsRef<Path>, U: AsRef<Path>>(
14 root: R,
15 unsafe_path: U,
16) -> Result<(PathBuf, PathBuf)> {
17 let root = root.as_ref().canonicalize()?;
18
19 let mut nlinks = 0u32;
20 let mut curr_path = unsafe_path.as_ref().to_path_buf();
21 'restart: loop {
22 let mut subpath = PathBuf::new();
23 let mut iter = curr_path.components();
24
25 'next_comp: while let Some(comp) = iter.next() {
26 match comp {
27 Component::Prefix(_) => {
29 return Err(Error::new(
30 ErrorKind::Other,
31 format!("Invalid path prefix in: {}", unsafe_path.as_ref().display()),
32 ));
33 }
34 Component::RootDir | Component::CurDir => {
37 continue 'next_comp;
38 }
39 Component::ParentDir => {
40 subpath.pop();
41 }
42 Component::Normal(n) => {
43 let path = root.join(&subpath).join(n);
44 if let Ok(v) = path.read_link() {
45 nlinks += 1;
46 if nlinks > MAX_SYMLINK_DEPTH {
47 return Err(Error::new(
48 ErrorKind::Other,
49 format!(
50 "Too many levels of symlinks: {}",
51 unsafe_path.as_ref().display()
52 ),
53 ));
54 }
55 curr_path = if v.is_absolute() {
56 v.join(iter.as_path())
57 } else {
58 subpath.join(v).join(iter.as_path())
59 };
60 continue 'restart;
61 } else {
62 subpath.push(n);
63 }
64 }
65 }
66 }
67
68 return Ok((root, subpath));
69 }
70}
71
72pub fn scoped_resolve<R: AsRef<Path>, U: AsRef<Path>>(root: R, unsafe_path: U) -> Result<PathBuf> {
92 do_scoped_resolve(root, unsafe_path).map(|(_root, path)| path)
93}
94
95pub fn scoped_join<R: AsRef<Path>, U: AsRef<Path>>(root: R, unsafe_path: U) -> Result<PathBuf> {
125 do_scoped_resolve(root, unsafe_path).map(|(root, path)| root.join(path))
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use std::fs::DirBuilder;
132 use std::os::unix::fs;
133 use tempfile::tempdir;
134
135 #[allow(dead_code)]
136 #[derive(Debug)]
137 struct TestData<'a> {
138 name: &'a str,
139 rootfs: &'a Path,
140 unsafe_path: &'a str,
141 result: &'a str,
142 }
143
144 fn exec_tests(tests: &[TestData]) {
145 for (i, t) in tests.iter().enumerate() {
146 let msg = format!("test[{}]: {:?}", i, t);
148 let result = scoped_resolve(t.rootfs, t.unsafe_path).unwrap();
149 let msg = format!("{}, result: {:?}", msg, result);
150
151 assert_eq!(&result, Path::new(t.result), "{}", msg);
153 }
154 }
155
156 #[test]
157 fn test_scoped_resolve() {
158 let rootfs_dir = tempdir().expect("failed to create tmpdir");
160 DirBuilder::new()
161 .create(rootfs_dir.path().join("b"))
162 .unwrap();
163 fs::symlink(rootfs_dir.path().join("b"), rootfs_dir.path().join("a")).unwrap();
164 let rootfs_path = &rootfs_dir.path().join("a");
165
166 let tests = [
167 TestData {
168 name: "normal path",
169 rootfs: rootfs_path,
170 unsafe_path: "a/b/c",
171 result: "a/b/c",
172 },
173 TestData {
174 name: "path with .. at beginning",
175 rootfs: rootfs_path,
176 unsafe_path: "../../../a/b/c",
177 result: "a/b/c",
178 },
179 TestData {
180 name: "path with complex .. pattern",
181 rootfs: rootfs_path,
182 unsafe_path: "../../../a/../../b/../../c",
183 result: "c",
184 },
185 TestData {
186 name: "path with .. in middle",
187 rootfs: rootfs_path,
188 unsafe_path: "/usr/bin/../../bin/ls",
189 result: "bin/ls",
190 },
191 TestData {
192 name: "path with . and ..",
193 rootfs: rootfs_path,
194 unsafe_path: "/usr/./bin/../../bin/./ls",
195 result: "bin/ls",
196 },
197 TestData {
198 name: "path with . at end",
199 rootfs: rootfs_path,
200 unsafe_path: "/usr/./bin/../../bin/./ls/.",
201 result: "bin/ls",
202 },
203 TestData {
204 name: "path try to escape by ..",
205 rootfs: rootfs_path,
206 unsafe_path: "/usr/./bin/../../../../bin/./ls/../ls",
207 result: "bin/ls",
208 },
209 TestData {
210 name: "path with .. at the end",
211 rootfs: rootfs_path,
212 unsafe_path: "/usr/./bin/../../bin/./ls/..",
213 result: "bin",
214 },
215 TestData {
216 name: "path ..",
217 rootfs: rootfs_path,
218 unsafe_path: "..",
219 result: "",
220 },
221 TestData {
222 name: "path .",
223 rootfs: rootfs_path,
224 unsafe_path: ".",
225 result: "",
226 },
227 TestData {
228 name: "path /",
229 rootfs: rootfs_path,
230 unsafe_path: "/",
231 result: "",
232 },
233 TestData {
234 name: "empty path",
235 rootfs: rootfs_path,
236 unsafe_path: "",
237 result: "",
238 },
239 ];
240
241 exec_tests(&tests);
242 }
243
244 #[test]
245 fn test_scoped_resolve_invalid() {
246 scoped_resolve("./root_is_not_absolute_path", ".").unwrap_err();
247 scoped_resolve("C:", ".").unwrap_err();
248 scoped_resolve(r#"\\server\test"#, ".").unwrap_err();
249 scoped_resolve(r#"http://localhost/test"#, ".").unwrap_err();
250 scoped_resolve(r#"您好"#, ".").unwrap_err();
252 }
253
254 #[test]
255 fn test_scoped_resolve_symlink() {
256 let rootfs_dir = tempdir().expect("failed to create tmpdir");
258 let rootfs_path = &rootfs_dir.path();
259 std::fs::create_dir(rootfs_path.join("symlink_dir")).unwrap();
260
261 fs::symlink("../../../", rootfs_path.join("1")).unwrap();
262 let tests = [TestData {
263 name: "relative symlink beyond root",
264 rootfs: rootfs_path,
265 unsafe_path: "1",
266 result: "",
267 }];
268 exec_tests(&tests);
269
270 fs::symlink("/dddd", rootfs_path.join("2")).unwrap();
271 let tests = [TestData {
272 name: "abs symlink pointing to non-exist directory",
273 rootfs: rootfs_path,
274 unsafe_path: "2",
275 result: "dddd",
276 }];
277 exec_tests(&tests);
278
279 fs::symlink("/", rootfs_path.join("3")).unwrap();
280 let tests = [TestData {
281 name: "abs symlink pointing to /",
282 rootfs: rootfs_path,
283 unsafe_path: "3",
284 result: "",
285 }];
286 exec_tests(&tests);
287
288 fs::symlink("usr/bin/../bin/ls", rootfs_path.join("4")).unwrap();
289 let tests = [TestData {
290 name: "symlink with one ..",
291 rootfs: rootfs_path,
292 unsafe_path: "4",
293 result: "usr/bin/ls",
294 }];
295 exec_tests(&tests);
296
297 fs::symlink("usr/bin/../../bin/ls", rootfs_path.join("5")).unwrap();
298 let tests = [TestData {
299 name: "symlink with two ..",
300 rootfs: rootfs_path,
301 unsafe_path: "5",
302 result: "bin/ls",
303 }];
304 exec_tests(&tests);
305
306 fs::symlink(
307 "../usr/bin/../../../bin/ls",
308 rootfs_path.join("symlink_dir/6"),
309 )
310 .unwrap();
311 let tests = [TestData {
312 name: "symlink try to escape",
313 rootfs: rootfs_path,
314 unsafe_path: "symlink_dir/6",
315 result: "bin/ls",
316 }];
317 exec_tests(&tests);
318
319 fs::symlink("/endpoint_b", rootfs_path.join("endpoint_a")).unwrap();
321 fs::symlink("/endpoint_a", rootfs_path.join("endpoint_b")).unwrap();
322 scoped_resolve(rootfs_path, "endpoint_a").unwrap_err();
323 }
324
325 #[test]
326 fn test_scoped_join() {
327 let rootfs_dir = tempdir().expect("failed to create tmpdir");
329 let rootfs_path = &rootfs_dir.path();
330
331 assert_eq!(
332 scoped_join(&rootfs_path, "a").unwrap(),
333 rootfs_path.join("a")
334 );
335 assert_eq!(
336 scoped_join(&rootfs_path, "./a").unwrap(),
337 rootfs_path.join("a")
338 );
339 assert_eq!(
340 scoped_join(&rootfs_path, "././a").unwrap(),
341 rootfs_path.join("a")
342 );
343 assert_eq!(
344 scoped_join(&rootfs_path, "c/d/../../a").unwrap(),
345 rootfs_path.join("a")
346 );
347 assert_eq!(
348 scoped_join(&rootfs_path, "c/d/../../../.././a").unwrap(),
349 rootfs_path.join("a")
350 );
351 assert_eq!(
352 scoped_join(&rootfs_path, "../../a").unwrap(),
353 rootfs_path.join("a")
354 );
355 assert_eq!(
356 scoped_join(&rootfs_path, "./../a").unwrap(),
357 rootfs_path.join("a")
358 );
359 }
360
361 #[test]
362 fn test_scoped_join_symlink() {
363 let rootfs_dir = tempdir().expect("failed to create tmpdir");
365 let rootfs_path = &rootfs_dir.path();
366 DirBuilder::new()
367 .recursive(true)
368 .create(rootfs_dir.path().join("b/c"))
369 .unwrap();
370 fs::symlink("b/c", rootfs_dir.path().join("a")).unwrap();
371
372 let target = rootfs_path.join("b/c");
373 assert_eq!(scoped_join(&rootfs_path, "a").unwrap(), target);
374 assert_eq!(scoped_join(&rootfs_path, "./a").unwrap(), target);
375 assert_eq!(scoped_join(&rootfs_path, "././a").unwrap(), target);
376 assert_eq!(scoped_join(&rootfs_path, "b/c/../../a").unwrap(), target);
377 assert_eq!(
378 scoped_join(&rootfs_path, "b/c/../../../.././a").unwrap(),
379 target
380 );
381 assert_eq!(scoped_join(&rootfs_path, "../../a").unwrap(), target);
382 assert_eq!(scoped_join(&rootfs_path, "./../a").unwrap(), target);
383 assert_eq!(scoped_join(&rootfs_path, "a/../../../a").unwrap(), target);
384 assert_eq!(scoped_join(&rootfs_path, "a/../../../b/c").unwrap(), target);
385 }
386
387 #[test]
388 fn test_scoped_join_symlink_loop() {
389 let rootfs_dir = tempdir().expect("failed to create tmpdir");
391 let rootfs_path = &rootfs_dir.path();
392 fs::symlink("/endpoint_b", rootfs_path.join("endpoint_a")).unwrap();
393 fs::symlink("/endpoint_a", rootfs_path.join("endpoint_b")).unwrap();
394 scoped_join(rootfs_path, "endpoint_a").unwrap_err();
395 }
396
397 #[test]
398 fn test_scoped_join_unicode_character() {
399 let rootfs_dir = tempdir().expect("failed to create tmpdir");
401 let rootfs_path = &rootfs_dir.path().canonicalize().unwrap();
402
403 let path = scoped_join(rootfs_path, "您好").unwrap();
404 assert_eq!(path, rootfs_path.join("您好"));
405
406 let path = scoped_join(rootfs_path, "../../../您好").unwrap();
407 assert_eq!(path, rootfs_path.join("您好"));
408
409 let path = scoped_join(rootfs_path, "。。/您好").unwrap();
410 assert_eq!(path, rootfs_path.join("。。/您好"));
411
412 let path = scoped_join(rootfs_path, "您好/../../test").unwrap();
413 assert_eq!(path, rootfs_path.join("test"));
414 }
415}