exarch_core/security/
path.rs

1//! Path traversal validation.
2
3use std::path::Path;
4
5use crate::Result;
6use crate::SecurityConfig;
7use crate::types::DestDir;
8use crate::types::SafePath;
9
10/// Validates that a path does not contain traversal attempts.
11///
12/// This function delegates to `SafePath::validate()` which performs
13/// comprehensive validation including:
14/// - Null byte detection
15/// - Absolute path rejection (unless allowed)
16/// - Parent directory traversal (`..`) detection
17/// - Path depth limiting
18/// - Banned component checking
19/// - Path normalization
20/// - Destination boundary verification
21///
22/// # Performance
23///
24/// For non-existing paths: ~300-500 ns (no I/O syscalls)
25/// For existing paths: ~5-50 μs (involves `canonicalize()` syscalls)
26///
27/// # Errors
28///
29/// Returns an error if the path contains:
30/// - `ExtractionError::PathTraversal` for `..` or absolute paths
31/// - `ExtractionError::SecurityViolation` for banned components or excessive
32///   depth
33///
34/// # Examples
35///
36/// ```no_run
37/// use exarch_core::SecurityConfig;
38/// use exarch_core::security::validate_path;
39/// use exarch_core::types::DestDir;
40/// use std::path::Path;
41/// use std::path::PathBuf;
42///
43/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
44/// let dest = DestDir::new(PathBuf::from("/tmp"))?;
45/// let config = SecurityConfig::default();
46///
47/// // Valid path
48/// let path = Path::new("foo/bar.txt");
49/// let safe_path = validate_path(path, &dest, &config)?;
50///
51/// // Path traversal is rejected
52/// let path = Path::new("../etc/passwd");
53/// assert!(validate_path(path, &dest, &config).is_err());
54/// # Ok(())
55/// # }
56/// ```
57pub fn validate_path(path: &Path, dest: &DestDir, config: &SecurityConfig) -> Result<SafePath> {
58    SafePath::validate(path, dest, config)
59}
60
61#[cfg(test)]
62#[allow(clippy::unwrap_used, clippy::expect_used)]
63mod tests {
64    use super::*;
65    use std::path::PathBuf;
66    use tempfile::TempDir;
67
68    fn create_test_dest() -> (TempDir, DestDir) {
69        let temp = TempDir::new().expect("failed to create temp dir");
70        let dest = DestDir::new(temp.path().to_path_buf()).expect("failed to create dest");
71        (temp, dest)
72    }
73
74    #[test]
75    fn test_validate_path_valid() {
76        let (_temp, dest) = create_test_dest();
77        let config = SecurityConfig::default();
78        let path = PathBuf::from("foo/bar.txt");
79        assert!(validate_path(&path, &dest, &config).is_ok());
80    }
81
82    #[test]
83    fn test_validate_path_traversal() {
84        let (_temp, dest) = create_test_dest();
85        let config = SecurityConfig::default();
86        let path = PathBuf::from("../etc/passwd");
87        assert!(validate_path(&path, &dest, &config).is_err());
88    }
89
90    #[test]
91    fn test_validate_path_absolute() {
92        let (_temp, dest) = create_test_dest();
93        let config = SecurityConfig::default();
94        let path = PathBuf::from("/etc/passwd");
95        assert!(validate_path(&path, &dest, &config).is_err());
96    }
97
98    #[test]
99    fn test_validate_path_nested() {
100        let (_temp, dest) = create_test_dest();
101        let config = SecurityConfig::default();
102        let path = PathBuf::from("foo/bar/baz/file.txt");
103        assert!(validate_path(&path, &dest, &config).is_ok());
104    }
105
106    #[test]
107    fn test_validate_path_current_dir() {
108        let (_temp, dest) = create_test_dest();
109        let config = SecurityConfig::default();
110        let path = PathBuf::from("./foo/bar.txt");
111        let result = validate_path(&path, &dest, &config);
112        assert!(result.is_ok());
113    }
114}