relay_core_lib/utils/
path.rs1use std::path::{Path, PathBuf};
2use std::io;
3
4#[derive(Debug, Clone)]
6pub struct PathSanitizer {
7 root: PathBuf,
8}
9
10impl PathSanitizer {
11 pub fn new(root: PathBuf) -> Self {
12 let canonical_root = root.canonicalize().unwrap_or(root);
15 Self {
16 root: canonical_root
17 }
18 }
19
20 pub fn sanitize(&self, path_str: &str) -> io::Result<PathBuf> {
23 let path = Path::new(path_str);
24
25 let candidate = if path.is_absolute() {
28 path.to_path_buf()
29 } else {
30 self.root.join(path)
31 };
32
33 let canonical = candidate.canonicalize()?;
37
38 if canonical.starts_with(&self.root) {
39 Ok(canonical)
40 } else {
41 Err(io::Error::new(io::ErrorKind::PermissionDenied, format!("Path traversal detected: {:?}", canonical)))
42 }
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use std::fs;
50 use tempfile::TempDir;
51
52 #[test]
53 fn test_sandbox_allows_valid_file() {
54 let temp_dir = TempDir::new().unwrap();
55 let file_path = temp_dir.path().join("test.txt");
56 fs::write(&file_path, "content").unwrap();
57
58 let sanitizer = PathSanitizer::new(temp_dir.path().to_path_buf());
59 let result = sanitizer.sanitize("test.txt");
60
61 assert!(result.is_ok());
62 assert_eq!(result.unwrap(), file_path.canonicalize().unwrap());
63 }
64
65 #[test]
66 fn test_sandbox_denies_traversal() {
67 let temp_dir = TempDir::new().unwrap();
68 let sanitizer = PathSanitizer::new(temp_dir.path().to_path_buf());
69
70 let _result = sanitizer.sanitize("../outside.txt");
73 let outside_dir = TempDir::new().unwrap();
78 let outside_file = outside_dir.path().join("outside.txt");
79 fs::write(&outside_file, "secret").unwrap();
80
81 let result = sanitizer.sanitize(outside_file.to_str().unwrap());
83
84 assert!(result.is_err());
85 }
86}