1use crate::file::{DirEntry, File, FileType, Metadata, OpenOptions};
2use crate::util::{make_relative, not_found, not_supported, parent_iter};
3use crate::{util, FileSystem};
4use itertools::Itertools;
5use parking_lot::Mutex;
6use std::collections::{HashMap, HashSet};
7use std::fmt::Debug;
8use std::io;
9use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom, Write};
10use std::path::{Path, PathBuf};
11use zip::read::ZipFile;
12use zip::result::{ZipError, ZipResult};
13use zip::ZipArchive;
14
15#[derive(Debug)]
17pub struct ZipFS<R: Read + Seek> {
18 zip_file: Mutex<ZipArchive<R>>,
19 directories: HashSet<PathBuf>,
20 normalized_lower_to_path: HashMap<PathBuf, PathBuf>,
21}
22
23impl<R: Read + Seek> ZipFS<R> {
24 pub fn new(zip_file: R) -> ZipResult<Self> {
26 let zip_file = ZipArchive::new(zip_file)?;
27
28 let mut directories = HashSet::from_iter([Path::new("").to_owned()]);
30 let mut normalized_lower_to_path = HashMap::new();
31 for file_name in zip_file.file_names() {
32 for parent in parent_iter(Path::new(&file_name.to_lowercase())) {
33 directories.insert(parent.to_owned());
34 }
35
36 let normalized = Self::normalize_path(file_name);
37 let lower = PathBuf::from(
38 normalized
39 .to_str()
40 .ok_or_else(not_supported)?
41 .to_lowercase(),
42 );
43
44 normalized_lower_to_path.insert(lower, normalized);
45 }
46
47 Ok(Self {
48 zip_file: Mutex::new(zip_file),
49 directories,
50 normalized_lower_to_path,
51 })
52 }
53
54 fn convert_error<T>(maybe_error: ZipResult<T>) -> crate::Result<T> {
55 maybe_error.map_err(|err| match err {
56 ZipError::FileNotFound => {
57 io::Error::new(ErrorKind::NotFound, "File not found in zip archive")
58 }
59 ZipError::Io(io_error) => io_error,
60 ZipError::InvalidArchive(error_str) => {
61 io::Error::new(ErrorKind::InvalidData, error_str)
62 }
63 ZipError::UnsupportedArchive(error_str) => {
64 io::Error::new(ErrorKind::Unsupported, error_str)
65 }
66 })
67 }
68
69 fn get_cased_path(&self, normalized_path: &Path) -> Option<&PathBuf> {
71 let lowercase_path = PathBuf::from(normalized_path.to_str()?.to_lowercase());
73 self.normalized_lower_to_path.get(&lowercase_path)
74 }
75
76 fn normalize_path<P: AsRef<Path>>(path: P) -> PathBuf {
77 make_relative(util::normalize_path(path))
79 }
80
81 fn with_file<RV, F: FnOnce(ZipFile) -> RV>(
82 &self,
83 normalized_path: &Path,
84 f: F,
85 ) -> crate::Result<RV> {
86 let cased_path = self.get_cased_path(normalized_path).ok_or_else(not_found)?;
88
89 let mut zip_file = self.zip_file.lock();
90
91 let entry =
92 Self::convert_error(zip_file.by_name(cased_path.to_str().ok_or_else(not_supported)?))?;
93 Ok(f(entry))
94 }
95}
96
97impl<R: Read + Seek> FileSystem for ZipFS<R> {
98 fn create_dir(&self, _path: &str) -> crate::Result<()> {
99 Err(not_supported())
100 }
101
102 fn metadata(&self, path: &str) -> crate::Result<Metadata> {
103 let normalized_path = Self::normalize_path(path);
104
105 let lowercase_path = PathBuf::from(
107 normalized_path
108 .as_path()
109 .to_str()
110 .ok_or_else(not_supported)?
111 .to_lowercase(),
112 );
113 if self.directories.get(&lowercase_path).is_some() {
114 return Ok(Metadata {
115 file_type: FileType::Directory,
116 len: 0,
117 });
118 }
119
120 self.with_file(normalized_path.as_path(), |file| Metadata {
122 file_type: FileType::File,
123 len: file.size(),
124 })
125 }
126
127 fn open_file_options(&self, path: &str, options: &OpenOptions) -> crate::Result<Box<dyn File>> {
128 if !options.read || options.write {
130 return Err(not_supported());
131 }
132
133 self.with_file::<crate::Result<Box<dyn File>>, _>(
135 &Self::normalize_path(path),
136 |mut entry| {
137 let mut contents = Vec::with_capacity(entry.size() as usize);
138 entry.read_to_end(&mut contents)?;
139 Ok(Box::new(ZipFileContents {
140 inner: Cursor::new(contents),
141 }))
142 },
143 )?
144 }
145
146 fn read_dir(
147 &self,
148 path: &str,
149 ) -> crate::Result<Box<dyn Iterator<Item = crate::Result<DirEntry>>>> {
150 let directory = Self::normalize_path(path);
151
152 if !self.directories.contains(&directory) {
154 return Err(not_found());
155 }
156
157 let mut zip_file = self.zip_file.lock();
158 let mut files = HashMap::new();
159 for file in zip_file
160 .file_names()
161 .map(|file_name| file_name.to_owned())
162 .collect_vec()
163 {
164 let normalized_file = Self::normalize_path(&file);
165
166 let mut add_parent = |normalized_path: &Path, metadata| {
167 if normalized_path.parent()? == directory {
168 files.insert(PathBuf::from(normalized_path.file_name()?), metadata);
169 }
170
171 Some(())
172 };
173
174 add_parent(
176 &normalized_file,
177 Metadata::file(zip_file.by_name(&file)?.size()),
178 );
179
180 if let Some(file_parent) = normalized_file.parent() {
182 add_parent(file_parent, Metadata::directory());
183 }
184 }
185
186 Ok(Box::new(
187 files
188 .into_iter()
189 .map(|(path, metadata)| Ok(DirEntry { path, metadata })),
190 ))
191 }
192
193 fn remove_dir(&self, _path: &str) -> crate::Result<()> {
194 Err(not_supported())
195 }
196
197 fn remove_file(&self, _path: &str) -> crate::Result<()> {
198 Err(not_supported())
199 }
200}
201
202struct ZipFileContents {
203 inner: Cursor<Vec<u8>>,
204}
205
206impl Read for ZipFileContents {
207 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
208 self.inner.read(buf)
209 }
210}
211
212impl Seek for ZipFileContents {
213 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
214 self.inner.seek(pos)
215 }
216}
217
218impl Write for ZipFileContents {
219 fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
220 Err(not_supported())
221 }
222
223 fn flush(&mut self) -> io::Result<()> {
224 Err(not_supported())
225 }
226}
227
228impl File for ZipFileContents {
229 fn metadata(&self) -> crate::Result<Metadata> {
230 Ok(Metadata::file(self.inner.get_ref().len() as u64))
231 }
232}
233
234#[cfg(test)]
235mod test {
236 use crate::file::{FileType, Metadata};
237 use crate::zip_fs::ZipFS;
238 use crate::FileSystem;
239 use std::collections::BTreeMap;
240 use std::fs::File;
241
242 fn read_directory(fs: &ZipFS<File>, path: &str) -> crate::Result<BTreeMap<String, Metadata>> {
243 Ok(fs
244 .read_dir(path)?
245 .map(|entry| {
246 let entry = entry.unwrap();
247 (entry.path.to_str().unwrap().to_owned(), entry.metadata)
248 })
249 .collect::<BTreeMap<_, _>>())
250 }
251
252 fn zip_fs() -> ZipFS<File> {
253 ZipFS::new(File::open("test/deep_fs.zip").unwrap()).unwrap()
254 }
255
256 #[test]
257 fn read_dir() {
258 let fs = zip_fs();
259
260 let root = read_directory(&fs, "").unwrap();
261 itertools::assert_equal(root.keys(), vec!["file", "folder"]);
262 itertools::assert_equal(
263 root.values().map(|md| md.file_type),
264 vec![FileType::File, FileType::Directory],
265 );
266 itertools::assert_equal(root.values().map(|md| md.len), vec![2571, 0]);
267
268 let another_root = read_directory(&fs, ".").unwrap();
269 assert_eq!(root, another_root);
270
271 let another_root = read_directory(&fs, "///").unwrap();
272 assert_eq!(root, another_root);
273
274 let another_root = read_directory(&fs, "\\").unwrap();
275 assert_eq!(root, another_root);
276
277 let another_root = read_directory(&fs, "///test/../").unwrap();
278 assert_eq!(root, another_root);
279
280 let deeper_root = read_directory(&fs, "folder/and/it").unwrap();
281 itertools::assert_equal(deeper_root.keys(), vec!["desc", "goes"]);
282
283 assert!(read_directory(&fs, "file").is_err());
284 assert!(read_directory(&fs, "not_a_real_path").is_err());
285 }
286
287 #[test]
288 fn open_file() {
289 let fs = zip_fs();
290
291 let mut file = fs.open_file("file").unwrap();
292 let md = file.metadata().unwrap();
293 assert_eq!(md.file_type, FileType::File);
294 assert_eq!(md.len, 2571);
295
296 let file = file.read_into_string().unwrap();
297 assert!(file.starts_with("Lorem ipsum dolor"));
298
299 let indirect_file = fs
300 .open_file("///something/..\\file")
301 .unwrap()
302 .read_into_string()
303 .unwrap();
304 assert_eq!(indirect_file, file);
305
306 let nested_file = fs
307 .open_file("folder/and/it/goes/deeper/desc")
308 .unwrap()
309 .read_into_string()
310 .unwrap();
311 assert_eq!(nested_file, "deeper\n")
312 }
313
314 #[test]
315 fn metadata() {
316 let fs = zip_fs();
317
318 let md = fs.metadata("file").unwrap();
319 assert_eq!(md.file_type, FileType::File);
320 assert_eq!(md.len, 2571);
321
322 let md = fs.metadata("folder").unwrap();
323 assert_eq!(md.file_type, FileType::Directory);
324 assert_eq!(md.len, 0);
325
326 let md = fs.metadata("folder/and/it/goes/desc").unwrap();
327 assert_eq!(md.file_type, FileType::File);
328 assert_eq!(md.len, 5);
329 }
330
331 #[test]
332 fn exists() {
333 let fs = zip_fs();
334
335 assert!(fs.exists("/").unwrap());
336 assert!(fs.exists("").unwrap());
337 assert!(fs.exists("file").unwrap());
338 assert!(fs.exists("FiLe").unwrap());
339 assert!(!fs.exists("no_file").unwrap());
340 assert!(fs.exists("folder").unwrap());
341 assert!(fs.exists("folDeR").unwrap());
342 assert!(fs.exists("folder/and/it").unwrap());
343 assert!(fs.exists("folder/anD/iT").unwrap());
344 assert!(fs.exists("folder/and/it/desc").unwrap());
345 assert!(!fs.exists("folder/and/it/does/not").unwrap());
346 assert!(fs.exists("///test/something_else/../../file").unwrap());
347 assert!(fs.exists("///test/something_elsE/../../file").unwrap());
348 }
349}