gix_status/
stack.rs

1use crate::SymlinkCheck;
2use bstr::BStr;
3use gix_fs::stack::ToNormalPathComponents;
4use gix_fs::Stack;
5use std::borrow::Cow;
6use std::path::{Path, PathBuf};
7
8impl SymlinkCheck {
9    /// Create a new stack that starts operating at `root`.
10    pub fn new(root: PathBuf) -> Self {
11        Self {
12            inner: gix_fs::Stack::new(root),
13        }
14    }
15
16    /// Return a valid filesystem path located in our root by appending `relative_path`, which is guaranteed to
17    /// not pass through a symbolic link. That way the caller can be sure to not be misled by an attacker that
18    /// tries to make us reach outside of the repository.
19    ///
20    /// Note that the file pointed to by `relative_path` may still be a symbolic link, or not exist at all,
21    /// and that an error may also be produced if directories on the path leading to the leaf
22    /// component of `relative_path` are missing.
23    ///
24    /// ### Note
25    ///
26    /// On windows, no verification is performed, instead only the combined path is provided as usual.
27    pub fn verified_path(&mut self, relative_path: impl ToNormalPathComponents) -> std::io::Result<&Path> {
28        self.inner.make_relative_path_current(relative_path, &mut Delegate)?;
29        Ok(self.inner.current())
30    }
31
32    /// Like [`Self::verified_path()`], but do not fail if there is no directory entry at `relative_path` or on the way
33    /// to `relative_path`. Instead.
34    /// For convenience, this incarnation is tuned to be easy to use with Git paths, i.e. slash-separated `BString` path.
35    pub fn verified_path_allow_nonexisting(&mut self, relative_path: &BStr) -> std::io::Result<Cow<'_, Path>> {
36        let rela_path = gix_path::try_from_bstr(relative_path).map_err(std::io::Error::other)?;
37        if let Err(err) = self.verified_path(rela_path.as_ref()) {
38            if err.kind() == std::io::ErrorKind::NotFound {
39                Ok(Cow::Owned(self.inner.root().join(rela_path)))
40            } else {
41                Err(err)
42            }
43        } else {
44            Ok(Cow::Borrowed(self.inner.current()))
45        }
46    }
47}
48
49struct Delegate;
50
51impl gix_fs::stack::Delegate for Delegate {
52    fn push_directory(&mut self, _stack: &Stack) -> std::io::Result<()> {
53        Ok(())
54    }
55
56    #[cfg_attr(windows, allow(unused_variables))]
57    fn push(&mut self, is_last_component: bool, stack: &Stack) -> std::io::Result<()> {
58        #[cfg(windows)]
59        {
60            Ok(())
61        }
62        #[cfg(not(windows))]
63        {
64            if is_last_component {
65                return Ok(());
66            }
67
68            if stack.current().symlink_metadata()?.is_symlink() {
69                return Err(std::io::Error::new(
70                    std::io::ErrorKind::Other,
71                    "Cannot step through symlink to perform an lstat",
72                ));
73            }
74            Ok(())
75        }
76    }
77
78    fn pop_directory(&mut self) {}
79}