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
//! Output path sanitization.
//!
//! Prevents directory traversal in scanner output by stripping or encoding
//! path separators and parent-directory sequences from server-controlled
//! strings before they are embedded in [`Finding`] fields.
/// Sanitize a string so it is safe to use as a filesystem path component.
///
/// Replaces `..`, `.`, `/`, `\`, and null bytes with safe alternatives.
/// Percent-encodes remaining problematic characters.
pub fn sanitize(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'/' | '\\' | '\0' => out.push('_'),
_ => out.push(ch),
}
}
// Collapse multiple dots that could form traversal sequences
out.replace("..", "__")
}
/// Sanitize a URL path for embedding in finding titles / details.
pub fn sanitize_url_path(path: &str) -> String {
sanitize(path)
}
/// Sanitize an arbitrary body excerpt to prevent traversal in output.
pub fn sanitize_excerpt(excerpt: &str, max_len: usize) -> String {
let truncated: String = excerpt.chars().take(max_len).collect();
sanitize(&truncated)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_replaces_path_separators() {
assert_eq!(sanitize("foo/bar"), "foo_bar");
assert_eq!(sanitize("foo\\bar"), "foo_bar");
}
#[test]
fn sanitize_collapses_dotdot() {
// Each `..` becomes `__`, each `/` becomes `_`. Three traversal
// chunks separated by single `/` collapse to nine underscores.
assert_eq!(sanitize("../../../etc/passwd"), "_________etc_passwd");
}
#[test]
fn sanitize_url_path_is_safe() {
assert_eq!(sanitize_url_path("/api/v1/admin"), "_api_v1_admin");
// ..\..\windows\system32 — `\` and `..` both collapse to `_`,
// producing six underscores followed by `windows_system32`.
assert_eq!(sanitize_url_path("..\\..\\windows\\system32"), "______windows_system32");
}
#[test]
fn sanitize_excerpt_respects_max_len() {
let long = "a".repeat(1000);
let sanitized = sanitize_excerpt(&long, 200);
assert_eq!(sanitized.len(), 200);
}
}