1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
use std::path::{Component, Path, PathBuf};
/// Guarantee that the path is relative and cannot traverse back to parent directories
/// and optionally prevent traversing hidden directories.
///
/// See the unit tests tests::test_sanitize_path* for examples
pub fn sanitize_path(path: &Path, traverse_hidden: bool) -> Option<PathBuf> {
let mut buf = PathBuf::new();
for comp in path.components() {
match comp {
Component::Normal(name) => buf.push(name),
Component::ParentDir => {
buf.pop();
}
_ => (),
}
}
// Double-check that all components are Normal and check for hidden dirs
for comp in buf.components() {
match comp {
Component::Normal(_) if traverse_hidden => (),
Component::Normal(name) if !name.to_str()?.starts_with('.') => (),
_ => return None,
}
}
Some(buf)
}
/// Returns if a path goes through a symbolic link
pub fn contains_symlink(path: &PathBuf) -> bool {
let mut joined_path = PathBuf::new();
for path_slice in path {
joined_path = joined_path.join(path_slice);
if !joined_path.exists() {
// On Windows, `\\?\` won't exist even though it's the root
// So, we can't just return here
// But we don't need to check if it's a symlink since it won't be
continue;
}
if joined_path
.symlink_metadata()
.map(|m| m.file_type().is_symlink())
.unwrap_or(false)
{
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use rstest::rstest;
#[rstest]
#[case("/foo", "foo")]
#[case("////foo", "foo")]
#[case("C:/foo", if cfg!(windows) { "foo" } else { "C:/foo" })]
#[case("../foo", "foo")]
#[case("../foo/../bar/abc", "bar/abc")]
fn test_sanitize_path(#[case] input: &str, #[case] output: &str) {
assert_eq!(
sanitize_path(Path::new(input), true).unwrap(),
Path::new(output)
);
assert_eq!(
sanitize_path(Path::new(input), false).unwrap(),
Path::new(output)
);
}
#[rstest]
#[case(".foo")]
#[case("/.foo")]
#[case("foo/.bar/foo")]
fn test_sanitize_path_no_hidden_files(#[case] input: &str) {
assert_eq!(sanitize_path(Path::new(input), false), None);
}
}
|