Skip to main content

rama_utils/include_dir/
dir.rs

1use super::{DirEntry, File};
2use crate::fs::{safe_create_dir_all_in_sync, safe_write_in_sync};
3use std::path::Path;
4
5/// A directory.
6#[derive(Debug, Clone, PartialEq)]
7pub struct Dir<'a> {
8    path: &'a str,
9    entries: &'a [DirEntry<'a>],
10}
11
12impl<'a> Dir<'a> {
13    /// Create a new [`Dir`].
14    #[must_use]
15    pub const fn new(path: &'a str, entries: &'a [DirEntry<'a>]) -> Self {
16        Dir { path, entries }
17    }
18
19    /// The full path for this [`Dir`], relative to the directory passed to
20    /// [`include_dir`](super::include_dir).
21    #[must_use]
22    pub fn path(&self) -> &'a Path {
23        Path::new(self.path)
24    }
25
26    /// The entries within this [`Dir`].
27    #[must_use]
28    pub const fn entries(&self) -> &'a [DirEntry<'a>] {
29        self.entries
30    }
31
32    /// Get a list of the files in this directory.
33    pub fn files(&self) -> impl Iterator<Item = &'a File<'a>> + 'a {
34        self.entries().iter().filter_map(DirEntry::as_file)
35    }
36
37    /// Get a list of the sub-directories inside this directory.
38    pub fn dirs(&self) -> impl Iterator<Item = &'a Dir<'a>> + 'a {
39        self.entries().iter().filter_map(DirEntry::as_dir)
40    }
41
42    /// Recursively search for a [`DirEntry`] with a particular path.
43    pub fn get_entry<S: AsRef<Path>>(&self, path: S) -> Option<&'a DirEntry<'a>> {
44        let path = path.as_ref();
45
46        for entry in self.entries() {
47            if entry.path() == path {
48                return Some(entry);
49            }
50
51            if let DirEntry::Dir(d) = entry
52                && let Some(nested) = d.get_entry(path)
53            {
54                return Some(nested);
55            }
56        }
57
58        None
59    }
60
61    /// Look up a file by name.
62    pub fn get_file<S: AsRef<Path>>(&self, path: S) -> Option<&'a File<'a>> {
63        self.get_entry(path).and_then(DirEntry::as_file)
64    }
65
66    /// Look up a dir by name.
67    pub fn get_dir<S: AsRef<Path>>(&self, path: S) -> Option<&'a Self> {
68        self.get_entry(path).and_then(DirEntry::as_dir)
69    }
70
71    /// Does this directory contain `path`?
72    pub fn contains<S: AsRef<Path>>(&self, path: S) -> bool {
73        self.get_entry(path).is_some()
74    }
75
76    /// Create directories and extract all files to real filesystem.
77    /// Creates parent directories of `path` if they do not already exist.
78    /// Fails if some files already exist.
79    /// In case of error, partially extracted directory may remain on the filesystem.
80    ///
81    /// # Security
82    ///
83    /// This method validates that all entry paths are relative, do not escape
84    /// the extraction directory through path traversal, and do not follow
85    /// symlinks outside the extraction directory.
86    pub fn extract<S: AsRef<Path>>(&self, base_path: S) -> std::io::Result<()> {
87        let base_path = base_path.as_ref();
88        std::fs::create_dir_all(base_path)?;
89        self.extract_entries(base_path)
90    }
91
92    fn extract_entries(&self, base_path: &Path) -> std::io::Result<()> {
93        for entry in self.entries() {
94            match entry {
95                DirEntry::Dir(d) => {
96                    safe_create_dir_all_in_sync(base_path, d.path())?;
97                    d.extract_entries(base_path)?;
98                }
99                DirEntry::File(f) => {
100                    safe_write_in_sync(base_path, f.path(), f.contents())?;
101                }
102            }
103        }
104
105        Ok(())
106    }
107}