virtual_filesystem/physical_fs/
mod.rs

1mod path_resolver;
2
3use crate::file::{DirEntry, File, Metadata, OpenOptions};
4use crate::physical_fs::path_resolver::{
5    PathResolver, SandboxedPathResolver, UnrestrictedPathResolver,
6};
7use crate::util::invalid_path;
8use crate::FileSystem;
9use normalize_path::NormalizePath;
10use std::fs;
11use std::marker::PhantomData;
12use std::path::{Path, PathBuf};
13
14/// The physical filesystem, backed by a root on the drive.
15pub struct PhysicalFSImpl<R: PathResolver> {
16    root: PathBuf,
17    _marker: PhantomData<R>,
18}
19
20/// The physical filesystem, backed by a root on the drive. This filesystem will not protect against
21/// directory traversal and very simply appends the target path to the root.
22pub type PhysicalFS = PhysicalFSImpl<UnrestrictedPathResolver>;
23/// The physical filesystem, backed by a root on the drive. This filesystem will perform basic
24/// protections against directory traversal in the form of returning an error if a user tries to
25/// escape the current directory.
26pub type SandboxedPhysicalFS = PhysicalFSImpl<SandboxedPathResolver>;
27
28impl<R: PathResolver> PhysicalFSImpl<R> {
29    /// Creates a new physical file system at the given root.
30    pub fn new<P: AsRef<Path>>(root: P) -> Self {
31        Self {
32            root: root.as_ref().normalize(),
33            _marker: PhantomData,
34        }
35    }
36}
37
38impl<R: PathResolver> FileSystem for PhysicalFSImpl<R> {
39    fn create_dir(&self, path: &str) -> crate::Result<()> {
40        fs::create_dir(R::resolve_path(&self.root, path)?)
41    }
42
43    fn metadata(&self, path: &str) -> crate::Result<Metadata> {
44        fs::metadata(R::resolve_path(&self.root, path)?).map(Metadata::from)
45    }
46
47    fn open_file_options(&self, path: &str, options: &OpenOptions) -> crate::Result<Box<dyn File>> {
48        fs::OpenOptions::from(options)
49            .open(R::resolve_path(&self.root, path)?)
50            .map::<Box<dyn File>, _>(|file| Box::new(file))
51    }
52
53    fn read_dir(
54        &self,
55        path: &str,
56    ) -> crate::Result<Box<dyn Iterator<Item = crate::Result<DirEntry>>>> {
57        Ok(Box::new(
58            fs::read_dir(R::resolve_path(&self.root, path)?)?.map({
59                let root = self.root.clone();
60                move |entry| {
61                    entry.and_then({
62                        |entry| {
63                            Ok(DirEntry {
64                                // strip the root
65                                path: entry
66                                    .path()
67                                    .strip_prefix(&root)
68                                    .map_err(|_| invalid_path())?
69                                    .into(),
70                                metadata: entry.metadata()?.into(),
71                            })
72                        }
73                    })
74                }
75            }),
76        ))
77    }
78
79    fn remove_dir(&self, path: &str) -> crate::Result<()> {
80        fs::remove_dir(R::resolve_path(&self.root, path)?)
81    }
82
83    fn remove_file(&self, path: &str) -> crate::Result<()> {
84        fs::remove_file(R::resolve_path(&self.root, path)?)
85    }
86}
87
88impl File for fs::File {
89    fn metadata(&self) -> crate::Result<Metadata> {
90        self.metadata().map(Metadata::from)
91    }
92}
93
94#[cfg(test)]
95mod test {
96    use crate::file::FileType;
97    use crate::physical_fs::{PhysicalFS, SandboxedPhysicalFS};
98    use crate::FileSystem;
99    use std::path::Path;
100
101    fn physical_fs<P: AsRef<Path>>(root: P) -> (PhysicalFS, SandboxedPhysicalFS) {
102        (
103            PhysicalFS::new(root.as_ref()),
104            SandboxedPhysicalFS::new(root.as_ref()),
105        )
106    }
107
108    #[test]
109    fn read_dir() {
110        let (unrestricted_fs, sandboxed_fs) = physical_fs("test");
111
112        // basic traversal
113        let dir = sandboxed_fs.read_dir(".").unwrap();
114        assert!(dir.count() > 0);
115        let dir = unrestricted_fs.read_dir(".").unwrap();
116        assert!(dir.count() > 0);
117
118        // project root traversal
119        assert!(sandboxed_fs.read_dir("..").is_err());
120        let dir = unrestricted_fs.read_dir("..").unwrap();
121        assert!(dir.count() > 0);
122
123        // fancy project root traversal
124        assert!(sandboxed_fs.read_dir("test/something/../../..").is_err());
125        let dir = unrestricted_fs.read_dir("test/something/../../..").unwrap();
126        assert!(dir.count() > 0);
127    }
128
129    #[test]
130    fn metadata() {
131        let (unrestricted_fs, sandboxed_fs) = physical_fs("test/folder_a");
132
133        // basic traversal
134        let md = sandboxed_fs.metadata(".").unwrap();
135        assert_eq!(md.file_type, FileType::Directory);
136        assert_eq!(md.len, 0);
137        let md = unrestricted_fs.metadata(".").unwrap();
138        assert_eq!(md.file_type, FileType::Directory);
139        assert_eq!(md.len, 0);
140        let md = sandboxed_fs.metadata("file_a").unwrap();
141        assert_eq!(md.file_type, FileType::File);
142        assert_eq!(md.len, 6);
143        let md = unrestricted_fs.metadata("file_a").unwrap();
144        assert_eq!(md.file_type, FileType::File);
145        assert_eq!(md.len, 6);
146
147        // project root traversal
148        assert!(sandboxed_fs.metadata("../deep_fs.zip").is_err());
149        let md = unrestricted_fs.metadata("../deep_fs.zip").unwrap();
150        assert_eq!(md.file_type, FileType::File);
151        assert_eq!(md.len, 2691);
152
153        // fancy project root traversal
154        assert!(sandboxed_fs.metadata("test/../../deep_fs.zip").is_err());
155        let md = unrestricted_fs.metadata("test/../../deep_fs.zip").unwrap();
156        assert_eq!(md.file_type, FileType::File);
157        assert_eq!(md.len, 2691);
158    }
159
160    #[test]
161    fn open_file() {
162        let (unrestricted_fs, sandboxed_fs) = physical_fs("test/folder_a");
163
164        // basic traversal
165        let mut file = sandboxed_fs.open_file("file_a").unwrap();
166        let md = file.metadata().unwrap();
167        assert_eq!(md.file_type, FileType::File);
168        assert_eq!(md.len, 6);
169        assert_eq!(file.read_into_string().unwrap(), "file a");
170        let mut file = unrestricted_fs.open_file("file_a").unwrap();
171        let md = file.metadata().unwrap();
172        assert_eq!(md.file_type, FileType::File);
173        assert_eq!(md.len, 6);
174        assert_eq!(file.read_into_string().unwrap(), "file a");
175
176        // project root traversal
177        assert!(sandboxed_fs.read_dir("../bad.tar.xz").is_err());
178        let mut file = unrestricted_fs.open_file("../bad.tar.xz").unwrap();
179        let md = file.metadata().unwrap();
180        assert_eq!(md.file_type, FileType::File);
181        assert_eq!(md.len, 4);
182        assert_eq!(file.read_into_string().unwrap(), "abcd");
183
184        // fancy project root traversal
185        assert!(sandboxed_fs.read_dir("test/../../bad.tar.xz").is_err());
186        let mut file = unrestricted_fs.open_file("test/../../bad.tar.xz").unwrap();
187        let md = file.metadata().unwrap();
188        assert_eq!(md.file_type, FileType::File);
189        assert_eq!(md.len, 4);
190        assert_eq!(file.read_into_string().unwrap(), "abcd");
191    }
192
193    #[test]
194    fn exists() {
195        let (unrestricted_fs, sandboxed_fs) = physical_fs("test");
196
197        assert!(unrestricted_fs.exists("").unwrap());
198        assert!(sandboxed_fs.exists("").unwrap());
199        assert!(unrestricted_fs.exists(".").unwrap());
200        assert!(sandboxed_fs.exists(".").unwrap());
201        assert!(unrestricted_fs.exists("///").unwrap());
202        assert!(sandboxed_fs.exists("///").unwrap());
203        assert!(unrestricted_fs.exists("\\\\").unwrap());
204        assert!(sandboxed_fs.exists("\\\\").unwrap());
205        assert!(unrestricted_fs.exists("folder_a").unwrap());
206        assert!(sandboxed_fs.exists("folder_a").unwrap());
207        assert!(!unrestricted_fs.exists("folder_c").unwrap());
208        assert!(!sandboxed_fs.exists("folder_c").unwrap());
209        assert!(unrestricted_fs.exists("bad.tar.xz").unwrap());
210        assert!(sandboxed_fs.exists("bad.tar.xz").unwrap());
211        assert!(unrestricted_fs.exists("../Cargo.toml").unwrap());
212        assert!(sandboxed_fs.exists("../Cargo.toml").is_err());
213        assert!(!unrestricted_fs.exists("../Cargo.toml2").unwrap());
214        // TODO: fix this case
215        // assert!(sandboxed_fs.exists("../Cargo.toml2").is_err());
216        assert!(unrestricted_fs.exists("folder_a/../../Cargo.toml").unwrap());
217        assert!(sandboxed_fs.exists("folder_a/../../Cargo.toml").is_err());
218    }
219}