strict_path/validator/
path_history.rs

1// Content copied from original src/validator/stated_path.rs
2use crate::{Result, StrictPathError};
3use soft_canonicalize::{anchored_canonicalize, soft_canonicalize};
4use std::ops::Deref;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone)]
8pub struct Raw;
9#[derive(Debug, Clone)]
10pub struct Canonicalized;
11#[derive(Debug, Clone)]
12pub struct BoundaryChecked;
13#[derive(Debug, Clone)]
14pub struct Exists;
15#[derive(Debug, Clone)]
16pub struct Virtualized;
17
18#[derive(Debug, Clone)]
19pub struct PathHistory<History> {
20    inner: std::path::PathBuf,
21    _marker: std::marker::PhantomData<History>,
22}
23
24impl<H> AsRef<Path> for PathHistory<H> {
25    #[inline]
26    fn as_ref(&self) -> &Path {
27        &self.inner
28    }
29}
30
31impl<H> Deref for PathHistory<H> {
32    type Target = Path;
33    #[inline]
34    fn deref(&self) -> &Self::Target {
35        &self.inner
36    }
37}
38
39impl PathHistory<Raw> {
40    #[inline]
41    pub fn new<P: Into<std::path::PathBuf>>(path: P) -> Self {
42        PathHistory {
43            inner: path.into(),
44            _marker: std::marker::PhantomData,
45        }
46    }
47}
48
49impl<H> PathHistory<H> {
50    #[inline]
51    pub fn into_inner(self) -> std::path::PathBuf {
52        self.inner
53    }
54
55    /// Virtualizes this path by preparing a path boundary-anchored system path for validation.
56    ///
57    /// Semantics:
58    /// - Clamps traversal (.., .) in virtual space so results never walk above the virtual root.
59    /// - Absolute inputs are treated as requests relative to the virtual root (drop only the root/prefix).
60    /// - Does not resolve symlinks; that is handled by canonicalization in `PathBoundary::restricted_join`.
61    /// - Returns a path under the path boundary as root to be canonicalized and boundary-checked.
62    pub fn virtualize_to_restriction<Marker>(
63        self,
64        restriction: &crate::PathBoundary<Marker>,
65    ) -> PathHistory<(H, Virtualized)> {
66        // Build a clamped relative path by processing components and preventing
67        // traversal above the virtual root.
68        use std::path::Component;
69        let mut parts: Vec<std::ffi::OsString> = Vec::new();
70        for comp in self.inner.components() {
71            match comp {
72                Component::Normal(name) => parts.push(name.to_os_string()),
73                Component::CurDir => {}
74                Component::ParentDir => {
75                    if parts.pop().is_none() {
76                        // At virtual root; ignore extra ".."
77                    }
78                }
79                Component::RootDir | Component::Prefix(_) => {
80                    // Treat as virtual root reset; clear accumulated parts
81                    parts.clear();
82                }
83            }
84        }
85        let mut rel = PathBuf::new();
86        for p in parts {
87            rel.push(p);
88        }
89
90        PathHistory {
91            inner: restriction.path().join(rel),
92            _marker: std::marker::PhantomData,
93        }
94    }
95
96    pub fn canonicalize(self) -> Result<PathHistory<(H, Canonicalized)>> {
97        let canon = soft_canonicalize(&self.inner)
98            .map_err(|e| StrictPathError::path_resolution_error(self.inner.clone(), e))?;
99        Ok(PathHistory {
100            inner: canon,
101            _marker: std::marker::PhantomData,
102        })
103    }
104
105    /// Canonicalize relative to a path boundary root using anchored semantics (virtual clamp + resolution).
106    /// Returns a Canonicalized state; boundary checking is still required.
107    pub fn canonicalize_anchored<Marker>(
108        self,
109        anchor: &crate::PathBoundary<Marker>,
110    ) -> Result<PathHistory<(H, Canonicalized)>> {
111        let canon = anchored_canonicalize(anchor.path(), &self.inner)
112            .map_err(|e| StrictPathError::path_resolution_error(self.inner.clone(), e))?;
113        Ok(PathHistory {
114            inner: canon,
115            _marker: std::marker::PhantomData,
116        })
117    }
118
119    pub fn verify_exists(self) -> Option<PathHistory<(H, Exists)>> {
120        self.inner.exists().then_some(PathHistory {
121            inner: self.inner,
122            _marker: std::marker::PhantomData,
123        })
124    }
125}
126
127impl<H> PathHistory<(H, Canonicalized)> {
128    #[inline]
129    pub fn boundary_check(
130        self,
131        restriction: &PathHistory<((Raw, Canonicalized), Exists)>,
132    ) -> Result<PathHistory<((H, Canonicalized), BoundaryChecked)>> {
133        if !self.starts_with(restriction) {
134            return Err(StrictPathError::path_escapes_boundary(
135                self.into_inner(),
136                restriction.to_path_buf(),
137            ));
138        }
139        Ok(PathHistory {
140            inner: self.inner,
141            _marker: std::marker::PhantomData,
142        })
143    }
144}
145
146// No separate anchored type-state after canonicalization; use Canonicalized