microsandbox_core/utils/
path.rs

1//! Utility functions for working with paths.
2
3use microsandbox_utils::SupportedPathType;
4
5use crate::{MicrosandboxError, MicrosandboxResult};
6
7//--------------------------------------------------------------------------------------------------
8// Functions
9//--------------------------------------------------------------------------------------------------
10
11/// Checks if two paths conflict (one is a parent/child of the other or they are the same)
12pub fn paths_overlap(path1: &str, path2: &str) -> bool {
13    let path1 = if path1.ends_with('/') {
14        path1.to_string()
15    } else {
16        format!("{}/", path1)
17    };
18
19    let path2 = if path2.ends_with('/') {
20        path2.to_string()
21    } else {
22        format!("{}/", path2)
23    };
24
25    path1.starts_with(&path2) || path2.starts_with(&path1)
26}
27
28/// Helper function to normalize and validate volume paths
29pub fn normalize_volume_path(base_path: &str, requested_path: &str) -> MicrosandboxResult<String> {
30    // First normalize both paths
31    let normalized_base =
32        microsandbox_utils::normalize_path(base_path, SupportedPathType::Absolute)?;
33
34    // If requested path is absolute, verify it's under base_path
35    if requested_path.starts_with('/') {
36        let normalized_requested =
37            microsandbox_utils::normalize_path(requested_path, SupportedPathType::Absolute)?;
38        // Check if normalized_requested starts with normalized_base
39        if !normalized_requested.starts_with(&normalized_base) {
40            return Err(MicrosandboxError::PathValidation(format!(
41                "Absolute path '{}' must be under base path '{}'",
42                normalized_requested, normalized_base
43            )));
44        }
45        Ok(normalized_requested)
46    } else {
47        // For relative paths, first normalize the requested path to catch any ../ attempts
48        let normalized_requested =
49            microsandbox_utils::normalize_path(requested_path, SupportedPathType::Relative)?;
50
51        // Then join with base and normalize again
52        let full_path = format!("{}/{}", normalized_base, normalized_requested);
53        microsandbox_utils::normalize_path(&full_path, SupportedPathType::Absolute)
54            .map_err(Into::into)
55    }
56}
57
58//--------------------------------------------------------------------------------------------------
59// Tests
60//--------------------------------------------------------------------------------------------------
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_paths_overlap() {
68        // Test cases that should conflict
69        assert!(paths_overlap("/data", "/data"));
70        assert!(paths_overlap("/data", "/data/app"));
71        assert!(paths_overlap("/data/app", "/data"));
72        assert!(paths_overlap("/data/app/logs", "/data/app"));
73
74        // Test cases that should not conflict
75        assert!(!paths_overlap("/data", "/database"));
76        assert!(!paths_overlap("/var/log", "/var/lib"));
77        assert!(!paths_overlap("/data/app1", "/data/app2"));
78        assert!(!paths_overlap("/data/app/logs", "/data/web/logs"));
79    }
80}