vfs_zip/read/
vfs04.rs

1use super::*;
2use ::vfs04::*;
3use std::io::Write;
4
5impl<IO: Read + Seek + Send + 'static> ZipReadOnly<IO> {
6    fn normalize_file<'s>(&self, orig: &'s str) -> VfsResult<&'s str> {
7        if orig.contains('\\') || orig.ends_with('/') {
8            return Err(VfsError::InvalidPath { path: orig.into() }); // Invalid path for file
9        }
10        let path = if orig.starts_with('/') { &orig[1..] } else { orig };
11        if path.split('/').any(|c| c == "" || c == "." || c == "..") {
12            return Err(VfsError::InvalidPath { path: orig.into() });
13        }
14        Ok(path)
15    }
16
17    fn normalize_path_dir<'s>(&self, orig: &'s str) -> VfsResult<(&'s str, bool)> {
18        if orig == "" || orig == "/" {
19            Ok(("", true)) // root dir
20        } else if orig.ends_with('/') {
21            Ok((self.normalize_file(&orig[..orig.len()-1])?, true))
22        } else {
23            Ok((self.normalize_file(orig)?, false))
24        }
25    }
26}
27
28impl<IO: Read + Seek + Send + 'static> FileSystem for ZipReadOnly<IO> {
29    fn read_dir(&self, orig: &str) -> VfsResult<Box<dyn Iterator<Item = String>>> {
30        let path = self.normalize_path_dir(orig)?.0;
31        if let Some(dir) = self.dirs.get(path) {
32            Ok(Box::new(dir.iter().cloned().collect::<Vec<_>>().into_iter())) // Eww
33        } else if let Some(_file) = self.files.get(path) {
34            Err(VfsError::Other { message: format!("\"{}\" is a file, not a directory", orig) })
35        } else {
36            Err(VfsError::FileNotFound { path: orig.into() })
37        }
38    }
39
40    fn open_file(&self, orig: &str) -> VfsResult<Box<dyn SeekAndRead>> {
41        let path = self.normalize_file(orig)?;
42        if let Some(i) = self.files.get(path) {
43            // ZipReadOnly doesn't allow us to access/read multiple ZipFile s at a time.
44            // To play nicely with `vfs`, we sadly need to read the whole thing into memory before returning.
45            let mut buf = Vec::new();
46            self.archive.lock().unwrap().by_index(*i).unwrap().read_to_end(&mut buf)?;
47            Ok(Box::new(std::io::Cursor::new(buf)))
48        } else if let Some(_) = self.dirs.get(path) {
49            Err(VfsError::Other { message: format!("\"{}\" is a directory, not a file", orig) })
50        } else {
51            Err(VfsError::FileNotFound { path: orig.into() })
52        }
53    }
54
55    fn metadata(&self, orig: &str) -> VfsResult<VfsMetadata> {
56        let (path, dir) = self.normalize_path_dir(orig)?;
57        if let Some(i) = self.files.get(path).filter(|_| !dir) {
58            Ok(VfsMetadata { file_type: VfsFileType::File, len: self.archive.lock().unwrap().by_index(*i).map(|f| f.size()).unwrap_or(0) })
59        } else if let Some(_) = self.dirs.get(path) {
60            Ok(VfsMetadata { file_type: VfsFileType::Directory, len: 0 })
61        } else {
62            Err(VfsError::FileNotFound { path: orig.into() })
63        }
64    }
65
66    fn exists(&self, path: &str) -> bool {
67        let (path, dir) = match self.normalize_path_dir(path) {
68            Ok(pd)  => pd,
69            Err(_)  => return false, // XXX
70        };
71        (!dir && self.files.contains_key(path)) || self.dirs.contains_key(path.trim_end_matches('/'))
72    }
73
74    // these all involve writing, which zip::read::ZipArchive doesn't support
75    fn create_dir   (&self, _path: &str)            -> VfsResult<()>                { Err(VfsError::NotSupported) }
76    fn create_file  (&self, _path: &str)            -> VfsResult<Box<dyn Write>>    { Err(VfsError::NotSupported) }
77    fn append_file  (&self, _path: &str)            -> VfsResult<Box<dyn Write>>    { Err(VfsError::NotSupported) }
78    fn remove_file  (&self, _path: &str)            -> VfsResult<()>                { Err(VfsError::NotSupported) }
79    fn remove_dir   (&self, _path: &str)            -> VfsResult<()>                { Err(VfsError::NotSupported) }
80    fn copy_file    (&self, _src: &str, _dst: &str) -> VfsResult<()>                { Err(VfsError::NotSupported) }
81    fn move_file    (&self, _src: &str, _dst: &str) -> VfsResult<()>                { Err(VfsError::NotSupported) }
82    fn move_dir     (&self, _src: &str, _dst: &str) -> VfsResult<()>                { Err(VfsError::NotSupported) }
83}
84
85#[cfg(test)] mod tests {
86    use super::*;
87    use std::fs::File;
88
89    fn is_empty_or_comment(line: &str) -> bool {
90        let line = line.trim_start_matches(char::is_whitespace);
91        line == "" || line.starts_with("#") || line.starts_with("//") || line.starts_with(";")
92    }
93
94    #[test] fn early_vfs_zip() {
95        let zip     = ZipReadOnly::new_strict(File::open("test/data/early-vfs-zip.zip").unwrap()).unwrap();
96        let files   = std::fs::read_to_string("test/data/early-vfs-zip.files.txt").unwrap();
97        let dirs    = std::fs::read_to_string("test/data/early-vfs-zip.dirs.txt").unwrap();
98        let files   = files.split(|ch| "\r\n".contains(ch)).filter(|l| !is_empty_or_comment(l));
99        let dirs    = dirs .split(|ch| "\r\n".contains(ch)).filter(|l| !is_empty_or_comment(l));
100
101        for file in files {
102            for good in &[
103                format!("{}", file),
104                format!("/{}", file),
105            ] {
106                eprintln!("{}", good);
107                zip.read_dir(&good).err().unwrap();
108                zip.open_file(&good).unwrap();
109                zip.metadata(&good).unwrap();
110                assert_eq!(zip.exists(&good), true);
111            }
112
113            for bad in &[
114                format!("//{}", file),
115                format!("\\{}", file),
116                format!("nonexistant/{}", file),
117                format!("{}.nonexistant", file),
118                format!("{}/", file),
119                format!("/{}/", file),
120                format!("./{}/", file),
121            ] {
122                eprintln!("{}", bad);
123                zip.read_dir(&bad).err().unwrap();
124                zip.open_file(&bad).err().unwrap();
125                zip.metadata(&bad).err().unwrap();
126                assert_eq!(zip.exists(&bad), false);
127            }
128        }
129
130        for dir in dirs {
131            for good in &[
132                format!("{}", dir),
133                format!("/{}", dir),
134                format!("{}/", dir),
135                format!("/{}/", dir),
136            ] {
137                eprintln!("{}", good);
138                let _ = zip.read_dir(&good).unwrap().collect::<Vec<String>>();
139                zip.open_file(&good).err().unwrap();
140                zip.metadata(&good).unwrap();
141                assert_eq!(zip.exists(&good), true);
142            }
143
144            for bad in &[
145                format!("nonexistant/{}", dir),
146                format!("{}.nonexistant", dir),
147                format!("/{}/nonexistant", dir),
148                format!("./{}/", dir),
149                format!("//{}", dir),
150                format!("{}//", dir),
151                format!("{}\\", dir),
152                format!("\\{}", dir),
153            ] {
154                eprintln!("{}", bad);
155                zip.read_dir(&bad).err().unwrap();
156                zip.open_file(&bad).err().unwrap();
157                zip.metadata(&bad).err().unwrap();
158                assert_eq!(zip.exists(&bad), false);
159            }
160        }
161    }
162}