1use std::cell::RefCell;
13use std::fmt::{self, Debug};
14use std::fs;
15use std::io::{self, Read, Seek, Write};
16use std::path::{self, Path, PathBuf};
17
18use zip;
19
20use crate::{Error, Result};
21
22fn convenient_path_to_str(path: &path::Path) -> Result<&str> {
24 path.to_str().ok_or_else(|| {
25 let errmessage = format!("Invalid path format for resource: {:?}", path);
26 Error::VfsError(errmessage)
27 })
28}
29
30pub trait VFile: Read + Write + Seek + Debug {}
33
34impl<T> VFile for T where T: Read + Write + Seek + Debug {}
35
36#[must_use]
42#[derive(Debug, Default, Copy, Clone, PartialEq)]
43pub struct OpenOptions {
44 read: bool,
45 write: bool,
46 create: bool,
47 append: bool,
48 truncate: bool,
49}
50
51impl OpenOptions {
52 pub fn new() -> OpenOptions {
54 Default::default()
55 }
56
57 pub fn read(mut self, read: bool) -> OpenOptions {
59 self.read = read;
60 self
61 }
62
63 pub fn write(mut self, write: bool) -> OpenOptions {
65 self.write = write;
66 self
67 }
68
69 pub fn create(mut self, create: bool) -> OpenOptions {
71 self.create = create;
72 self
73 }
74
75 pub fn append(mut self, append: bool) -> OpenOptions {
77 self.append = append;
78 self
79 }
80
81 pub fn truncate(mut self, truncate: bool) -> OpenOptions {
83 self.truncate = truncate;
84 self
85 }
86
87 fn to_fs_openoptions(self) -> fs::OpenOptions {
88 let mut opt = fs::OpenOptions::new();
89 let _ = opt
90 .read(self.read)
91 .write(self.write)
92 .create(self.create)
93 .append(self.append)
94 .truncate(self.truncate)
95 .create(self.create);
96 opt
97 }
98}
99
100pub trait VFS: Debug {
103 fn open_options(&self, path: &Path, open_options: OpenOptions) -> Result<Box<dyn VFile>>;
105 fn open(&self, path: &Path) -> Result<Box<dyn VFile>> {
107 self.open_options(path, OpenOptions::new().read(true))
108 }
109 fn create(&self, path: &Path) -> Result<Box<dyn VFile>> {
111 self.open_options(
112 path,
113 OpenOptions::new().write(true).create(true).truncate(true),
114 )
115 }
116 fn append(&self, path: &Path) -> Result<Box<dyn VFile>> {
118 self.open_options(
119 path,
120 OpenOptions::new().write(true).create(true).append(true),
121 )
122 }
123 fn mkdir(&self, path: &Path) -> Result;
125
126 fn rm(&self, path: &Path) -> Result;
128
129 fn rmrf(&self, path: &Path) -> Result;
131
132 fn exists(&self, path: &Path) -> bool;
134
135 fn metadata(&self, path: &Path) -> Result<Box<dyn VMetadata>>;
137
138 fn read_dir(&self, path: &Path) -> Result<Box<dyn Iterator<Item = Result<PathBuf>>>>;
140
141 fn to_path_buf(&self) -> Option<PathBuf>;
143}
144
145pub trait VMetadata {
147 fn is_dir(&self) -> bool;
151 fn is_file(&self) -> bool;
153 fn len(&self) -> u64;
156}
157
158#[derive(Clone)]
164pub struct PhysicalFS {
165 root: PathBuf,
166 readonly: bool,
167}
168
169#[derive(Debug, Clone)]
171pub struct PhysicalMetadata(fs::Metadata);
172
173impl VMetadata for PhysicalMetadata {
174 fn is_dir(&self) -> bool {
175 self.0.is_dir()
176 }
177 fn is_file(&self) -> bool {
178 self.0.is_file()
179 }
180 fn len(&self) -> u64 {
181 self.0.len()
182 }
183}
184
185fn sanitize_path(path: &path::Path) -> Option<PathBuf> {
197 let mut c = path.components();
198 match c.next() {
199 Some(path::Component::RootDir) => (),
200 _ => return None,
201 }
202
203 fn is_normal_component(comp: path::Component) -> Option<&str> {
204 match comp {
205 path::Component::Normal(s) => s.to_str(),
206 _ => None,
207 }
208 }
209
210 let mut accm = PathBuf::new();
212 for component in c {
213 if let Some(s) = is_normal_component(component) {
214 accm.push(s)
215 } else {
216 return None;
217 }
218 }
219 Some(accm)
220}
221
222impl PhysicalFS {
223 pub fn new(root: &Path, readonly: bool) -> Self {
225 PhysicalFS {
226 root: root.into(),
227 readonly,
228 }
229 }
230
231 fn to_absolute(&self, p: &Path) -> Result<PathBuf> {
240 if let Some(safe_path) = sanitize_path(p) {
241 let mut root_path = self.root.clone();
242 root_path.push(safe_path);
243 Ok(root_path)
244 } else {
245 let msg = format!(
246 "Path {:?} is not valid: must be an absolute path with no \
247 references to parent directories",
248 p
249 );
250 Err(Error::VfsError(msg))
251 }
252 }
253
254 fn create_root(&self) -> Result {
261 if !self.root.exists() {
262 fs::create_dir_all(&self.root).map_err(Error::from)
263 } else {
264 Ok(())
265 }
266 }
267}
268
269impl Debug for PhysicalFS {
270 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271 write!(f, "<PhysicalFS root: {}>", self.root.display())
272 }
273}
274
275impl VFS for PhysicalFS {
276 fn open_options(&self, path: &Path, open_options: OpenOptions) -> Result<Box<dyn VFile>> {
278 if self.readonly
279 && (open_options.write
280 || open_options.create
281 || open_options.append
282 || open_options.truncate)
283 {
284 let msg = format!(
285 "Cannot alter file {:?} in root {:?}, filesystem read-only",
286 path, self
287 );
288 return Err(Error::VfsError(msg));
289 }
290 self.create_root()?;
291 let p = self.to_absolute(path)?;
292 open_options
293 .to_fs_openoptions()
294 .open(p)
295 .map(|x| Box::new(x) as Box<dyn VFile>)
296 .map_err(Error::from)
297 }
298
299 fn mkdir(&self, path: &Path) -> Result {
301 if self.readonly {
302 return Err(Error::VfsError(
303 "Tried to make directory {} but FS is \
304 read-only"
305 .to_string(),
306 ));
307 }
308 self.create_root()?;
309 let p = self.to_absolute(path)?;
310 fs::DirBuilder::new()
312 .recursive(true)
313 .create(p)
314 .map_err(Error::from)
315 }
316
317 fn rm(&self, path: &Path) -> Result {
319 if self.readonly {
320 return Err(Error::VfsError(
321 "Tried to remove file {} but FS is read-only".to_string(),
322 ));
323 }
324
325 self.create_root()?;
326 let p = self.to_absolute(path)?;
327 if p.is_dir() {
328 fs::remove_dir(p).map_err(Error::from)
329 } else {
330 fs::remove_file(p).map_err(Error::from)
331 }
332 }
333
334 fn rmrf(&self, path: &Path) -> Result {
336 if self.readonly {
337 return Err(Error::VfsError(
338 "Tried to remove file/dir {} but FS is \
339 read-only"
340 .to_string(),
341 ));
342 }
343
344 self.create_root()?;
345 let p = self.to_absolute(path)?;
346 if p.is_dir() {
347 fs::remove_dir_all(p).map_err(Error::from)
348 } else {
349 fs::remove_file(p).map_err(Error::from)
350 }
351 }
352
353 fn exists(&self, path: &Path) -> bool {
355 match self.to_absolute(path) {
356 Ok(p) => p.exists(),
357 _ => false,
358 }
359 }
360
361 fn metadata(&self, path: &Path) -> Result<Box<dyn VMetadata>> {
363 self.create_root()?;
364 let p = self.to_absolute(path)?;
365 p.metadata()
366 .map(|m| Box::new(PhysicalMetadata(m)) as Box<dyn VMetadata>)
367 .map_err(Error::from)
368 }
369
370 fn read_dir(&self, path: &Path) -> Result<Box<dyn Iterator<Item = Result<PathBuf>>>> {
372 self.create_root()?;
373 let p = self.to_absolute(path)?;
374 let direntry_to_path = |entry: &fs::DirEntry| -> Result<PathBuf> {
382 let fname = entry
383 .file_name()
384 .into_string()
385 .expect("Non-unicode char in file path? Should never happen, I hope!");
386 let mut pathbuf = PathBuf::from(path);
387 pathbuf.push(fname);
388 Ok(pathbuf)
389 };
390 let itr = fs::read_dir(p)?
391 .map(|entry| direntry_to_path(&entry?))
392 .collect::<Vec<_>>()
393 .into_iter();
394 Ok(Box::new(itr))
395 }
396
397 fn to_path_buf(&self) -> Option<PathBuf> {
399 Some(self.root.clone())
400 }
401}
402
403#[derive(Debug)]
408pub struct OverlayFS {
409 roots: Vec<Box<dyn VFS>>,
410}
411
412impl OverlayFS {
413 pub fn new() -> Self {
415 Self { roots: Vec::new() }
416 }
417
418 pub fn push(&mut self, fs: Box<dyn VFS>) {
420 self.roots.push(fs);
421 }
422
423 pub fn roots(&self) -> &[Box<dyn VFS>] {
426 &self.roots
427 }
428}
429
430impl VFS for OverlayFS {
431 fn open_options(&self, path: &Path, open_options: OpenOptions) -> Result<Box<dyn VFile>> {
433 let mut tried: Vec<(PathBuf, Error)> = vec![];
434
435 for vfs in &self.roots {
436 match vfs.open_options(path, open_options) {
437 Err(e) => {
438 if let Some(vfs_path) = vfs.to_path_buf() {
439 tried.push((vfs_path, e));
440 } else {
441 tried.push((PathBuf::from("<invalid path>"), e));
442 }
443 }
444 f => return f,
445 }
446 }
447 let errmessage = String::from(convenient_path_to_str(path)?);
448 Err(Error::ResourceNotFound(errmessage, tried))
449 }
450
451 fn mkdir(&self, path: &Path) -> Result {
453 for vfs in &self.roots {
454 match vfs.mkdir(path) {
455 Err(_) => (),
456 f => return f,
457 }
458 }
459 Err(Error::VfsError(format!(
460 "Could not find anywhere writeable to make dir {:?}",
461 path
462 )))
463 }
464
465 fn rm(&self, path: &Path) -> Result {
467 for vfs in &self.roots {
468 match vfs.rm(path) {
469 Err(_) => (),
470 f => return f,
471 }
472 }
473 Err(Error::VfsError(format!("Could not remove file {:?}", path)))
474 }
475
476 fn rmrf(&self, path: &Path) -> Result {
478 for vfs in &self.roots {
479 match vfs.rmrf(path) {
480 Err(_) => (),
481 f => return f,
482 }
483 }
484 Err(Error::VfsError(format!(
485 "Could not remove file/dir {:?}",
486 path
487 )))
488 }
489
490 fn exists(&self, path: &Path) -> bool {
492 for vfs in &self.roots {
493 if vfs.exists(path) {
494 return true;
495 }
496 }
497
498 false
499 }
500
501 fn metadata(&self, path: &Path) -> Result<Box<dyn VMetadata>> {
503 for vfs in &self.roots {
504 match vfs.metadata(path) {
505 Err(_) => (),
506 f => return f,
507 }
508 }
509 Err(Error::VfsError(format!(
510 "Could not get metadata for file/dir {:?}",
511 path
512 )))
513 }
514
515 fn read_dir(&self, path: &Path) -> Result<Box<dyn Iterator<Item = Result<PathBuf>>>> {
517 let mut v = Vec::new();
520 for fs in &self.roots {
521 if let Ok(rddir) = fs.read_dir(path) {
522 v.extend(rddir)
523 }
524 }
525 Ok(Box::new(v.into_iter()))
526 }
527
528 fn to_path_buf(&self) -> Option<PathBuf> {
530 None
531 }
532}
533
534trait ZipArchiveAccess {
535 fn by_name<'a>(&'a mut self, name: &str) -> zip::result::ZipResult<zip::read::ZipFile<'a>>;
536 fn by_index<'a>(
537 &'a mut self,
538 file_number: usize,
539 ) -> zip::result::ZipResult<zip::read::ZipFile<'a>>;
540 fn len(&self) -> usize;
541}
542
543impl<T: Read + Seek> ZipArchiveAccess for zip::ZipArchive<T> {
544 fn by_name(&mut self, name: &str) -> zip::result::ZipResult<zip::read::ZipFile> {
545 self.by_name(name)
546 }
547
548 fn by_index(&mut self, file_number: usize) -> zip::result::ZipResult<zip::read::ZipFile> {
549 self.by_index(file_number)
550 }
551
552 fn len(&self) -> usize {
553 self.len()
554 }
555}
556
557impl Debug for dyn ZipArchiveAccess {
558 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
559 write!(f, "<ZipArchiveAccess>")
562 }
563}
564
565#[derive(Debug)]
567pub struct ZipFS {
568 source: Option<PathBuf>,
577 archive: RefCell<Box<dyn ZipArchiveAccess>>,
578 index: Vec<String>,
581}
582
583impl ZipFS {
584 pub fn new(filename: &Path) -> Result<Self> {
586 let f = fs::File::open(filename)?;
587 let archive = Box::new(zip::ZipArchive::new(f)?);
588 ZipFS::from_boxed_archive(archive, Some(filename.into()))
589 }
590
591 pub fn from_read<R>(reader: R) -> Result<Self>
594 where
595 R: Read + Seek + 'static,
596 {
597 let archive = Box::new(zip::ZipArchive::new(reader)?);
598 ZipFS::from_boxed_archive(archive, None)
599 }
600
601 fn from_boxed_archive(
602 mut archive: Box<dyn ZipArchiveAccess>,
603 source: Option<PathBuf>,
604 ) -> Result<Self> {
605 let idx = (0..archive.len())
606 .map(|i| {
607 archive
608 .by_index(i)
609 .expect("Should never happen!")
610 .name()
611 .to_string()
612 })
613 .collect();
614 Ok(Self {
615 source,
616 archive: RefCell::new(archive),
617 index: idx,
618 })
619 }
620}
621
622#[derive(Clone)]
632pub struct ZipFileWrapper {
633 buffer: io::Cursor<Vec<u8>>,
634}
635
636impl ZipFileWrapper {
637 fn new(z: &mut zip::read::ZipFile) -> Result<Self> {
638 let mut b = Vec::new();
639 let _ = z.read_to_end(&mut b)?;
640 Ok(Self {
641 buffer: io::Cursor::new(b),
642 })
643 }
644}
645
646impl io::Read for ZipFileWrapper {
647 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
648 self.buffer.read(buf)
649 }
650}
651
652impl io::Write for ZipFileWrapper {
653 fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
654 panic!("Cannot write to a zip file!")
655 }
656
657 fn flush(&mut self) -> io::Result<()> {
658 Ok(())
659 }
660}
661
662impl io::Seek for ZipFileWrapper {
663 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
664 self.buffer.seek(pos)
665 }
666}
667
668impl Debug for ZipFileWrapper {
669 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
670 write!(f, "<Zipfile>")
671 }
672}
673
674#[derive(Debug, Copy, Clone, PartialEq)]
675struct ZipMetadata {
676 len: u64,
677 is_dir: bool,
678 is_file: bool,
679}
680
681impl ZipMetadata {
682 fn new(name: &str, archive: &mut dyn ZipArchiveAccess) -> Option<Self> {
689 match archive.by_name(name) {
690 Err(_) => None,
691 Ok(zipfile) => {
692 let len = zipfile.size();
693 Some(ZipMetadata {
694 len,
695 is_file: true,
696 is_dir: false, })
698 }
699 }
700 }
701}
702
703impl VMetadata for ZipMetadata {
704 fn is_dir(&self) -> bool {
705 self.is_dir
706 }
707 fn is_file(&self) -> bool {
708 self.is_file
709 }
710 fn len(&self) -> u64 {
711 self.len
712 }
713}
714
715impl VFS for ZipFS {
716 fn open_options(&self, path: &Path, open_options: OpenOptions) -> Result<Box<dyn VFile>> {
717 let path = convenient_path_to_str(path)?;
719 if open_options.write || open_options.create || open_options.append || open_options.truncate
720 {
721 let msg = format!(
722 "Cannot alter file {:?} in zipfile {:?}, filesystem read-only",
723 path, self
724 );
725 return Err(Error::VfsError(msg));
726 }
727 let mut stupid_archive_borrow = self.archive
728 .try_borrow_mut()
729 .expect("Couldn't borrow ZipArchive in ZipFS::open_options(); should never happen! Report a bug at https://github.com/ggez/ggez/");
730 let mut f = stupid_archive_borrow.by_name(path)?;
731 let zipfile = ZipFileWrapper::new(&mut f)?;
732 Ok(Box::new(zipfile) as Box<dyn VFile>)
733 }
734
735 fn mkdir(&self, path: &Path) -> Result {
736 let msg = format!(
737 "Cannot mkdir {:?} in zipfile {:?}, filesystem read-only",
738 path, self
739 );
740 Err(Error::VfsError(msg))
741 }
742
743 fn rm(&self, path: &Path) -> Result {
744 let msg = format!(
745 "Cannot rm {:?} in zipfile {:?}, filesystem read-only",
746 path, self
747 );
748 Err(Error::VfsError(msg))
749 }
750
751 fn rmrf(&self, path: &Path) -> Result {
752 let msg = format!(
753 "Cannot rmrf {:?} in zipfile {:?}, filesystem read-only",
754 path, self
755 );
756 Err(Error::VfsError(msg))
757 }
758
759 fn exists(&self, path: &Path) -> bool {
760 let mut stupid_archive_borrow = self.archive
761 .try_borrow_mut()
762 .expect("Couldn't borrow ZipArchive in ZipFS::exists(); should never happen! Report a bug at https://github.com/ggez/ggez/");
763 if let Ok(path) = convenient_path_to_str(path) {
764 stupid_archive_borrow.by_name(path).is_ok()
765 } else {
766 false
767 }
768 }
769
770 fn metadata(&self, path: &Path) -> Result<Box<dyn VMetadata>> {
771 let path = convenient_path_to_str(path)?;
772 let mut stupid_archive_borrow = self.archive
773 .try_borrow_mut()
774 .expect("Couldn't borrow ZipArchive in ZipFS::metadata(); should never happen! Report a bug at https://github.com/ggez/ggez/");
775 match ZipMetadata::new(path, &mut **stupid_archive_borrow) {
776 None => Err(Error::VfsError(format!(
777 "Metadata not found in zip file for {}",
778 path
779 ))),
780 Some(md) => Ok(Box::new(md) as Box<dyn VMetadata>),
781 }
782 }
783
784 fn read_dir(&self, path: &Path) -> Result<Box<dyn Iterator<Item = Result<PathBuf>>>> {
787 let path = convenient_path_to_str(path)?;
788 let itr = self
789 .index
790 .iter()
791 .filter(|s| s.starts_with(path))
792 .map(|s| Ok(PathBuf::from(s)))
793 .collect::<Vec<_>>();
794 Ok(Box::new(itr.into_iter()))
795 }
796
797 fn to_path_buf(&self) -> Option<PathBuf> {
798 self.source.clone()
799 }
800}
801
802#[cfg(test)]
803mod tests {
804 use super::*;
805 use std::io::{self, BufRead};
806
807 #[test]
808 fn headless_test_path_filtering() {
809 let p = path::Path::new("/foo");
811 assert!(sanitize_path(p).is_some());
812
813 let p = path::Path::new("/foo/");
814 assert!(sanitize_path(p).is_some());
815
816 let p = path::Path::new("/foo/bar.txt");
817 assert!(sanitize_path(p).is_some());
818
819 let p = path::Path::new("/");
820 assert!(sanitize_path(p).is_some());
821
822 let p = path::Path::new("../foo");
824 assert!(sanitize_path(p).is_none());
825
826 let p = path::Path::new("foo");
827 assert!(sanitize_path(p).is_none());
828
829 let p = path::Path::new("/foo/../../");
830 assert!(sanitize_path(p).is_none());
831
832 let p = path::Path::new("/foo/../bop");
833 assert!(sanitize_path(p).is_none());
834
835 let p = path::Path::new("/../bar");
836 assert!(sanitize_path(p).is_none());
837
838 let p = path::Path::new("");
839 assert!(sanitize_path(p).is_none());
840 }
841
842 #[test]
843 fn headless_test_read() {
844 let cargo_path = Path::new(env!("CARGO_MANIFEST_DIR"));
845 let fs = PhysicalFS::new(cargo_path, true);
846 let f = fs.open(Path::new("/Cargo.toml")).unwrap();
847 let mut bf = io::BufReader::new(f);
848 let mut s = String::new();
849 let _ = bf.read_line(&mut s).unwrap();
850 let trimmed_string = s.trim();
853 assert_eq!(trimmed_string, "[package]");
854 }
855
856 #[test]
857 fn headless_test_read_overlay() {
858 let cargo_path = Path::new(env!("CARGO_MANIFEST_DIR"));
859 let fs1 = PhysicalFS::new(cargo_path, true);
860 let mut f2path = PathBuf::from(cargo_path);
861 f2path.push("src");
862 let fs2 = PhysicalFS::new(&f2path, true);
863 let mut ofs = OverlayFS::new();
864 ofs.push(Box::new(fs1));
865 ofs.push(Box::new(fs2));
866
867 assert!(ofs.exists(Path::new("/Cargo.toml")));
868 assert!(ofs.exists(Path::new("/lib.rs")));
869 assert!(!ofs.exists(Path::new("/foobaz.rs")));
870 }
871
872 #[test]
873 fn headless_test_physical_all() {
874 let cargo_path = Path::new(env!("CARGO_MANIFEST_DIR"));
875 let fs = PhysicalFS::new(cargo_path, false);
876 let testdir = Path::new("/testdir");
877 let f1 = Path::new("/testdir/file1.txt");
878
879 if fs.exists(testdir) {
881 fs.rmrf(testdir).unwrap();
882 }
883 assert!(!fs.exists(testdir));
884
885 fs.mkdir(testdir).unwrap();
887 assert!(fs.exists(testdir));
888 fs.rm(testdir).unwrap();
889 assert!(!fs.exists(testdir));
890
891 let test_string = "Foo!";
892 fs.mkdir(testdir).unwrap();
893 {
894 let mut f = fs.append(f1).unwrap();
895 let _ = f.write(test_string.as_bytes()).unwrap();
896 }
897 {
898 let mut buf = Vec::new();
899 let mut f = fs.open(f1).unwrap();
900 let _ = f.read_to_end(&mut buf).unwrap();
901 assert_eq!(&buf[..], test_string.as_bytes());
902 }
903
904 {
905 let m = fs.metadata(f1).unwrap();
907 assert!(m.is_file());
908 assert!(!m.is_dir());
909 assert_eq!(m.len(), 4);
910
911 let m = fs.metadata(testdir).unwrap();
912 assert!(!m.is_file());
913 assert!(m.is_dir());
914 }
922
923 {
924 let r = fs.read_dir(testdir).unwrap();
926 assert_eq!(r.count(), 1);
927 let r = fs.read_dir(testdir).unwrap();
928 for f in r {
929 let fname = f.unwrap();
930 assert!(fs.exists(&fname));
931 }
932 }
933
934 {
935 assert!(fs.exists(f1));
936 fs.rm(f1).unwrap();
937 assert!(!fs.exists(f1));
938 }
939
940 fs.rmrf(testdir).unwrap();
941 assert!(!fs.exists(testdir));
942 }
943
944 fn make_zip_fs() -> Box<dyn VFS> {
945 let mut finished_zip_bytes: io::Cursor<_> = {
946 let zip_bytes = io::Cursor::new(vec![]);
947 let mut zip_archive = zip::ZipWriter::new(zip_bytes);
948
949 zip_archive
950 .start_file("fake_file_name.txt", zip::write::FileOptions::default())
951 .unwrap();
952 let _bytes = zip_archive.write(b"Zip contents!").unwrap();
953 zip_archive.add_directory("fake_dir", zip::write::FileOptions::default())
954 .unwrap();
955 zip_archive
956 .start_file("fake_dir/file.txt", zip::write::FileOptions::default())
957 .unwrap();
958 let _bytes = zip_archive.write(b"Zip contents!").unwrap();
959
960 zip_archive.finish().unwrap()
961 };
962
963 let _bytes = finished_zip_bytes.seek(io::SeekFrom::Start(0)).unwrap();
964 let zfs = ZipFS::from_read(finished_zip_bytes).unwrap();
965 Box::new(zfs)
966 }
967
968 #[test]
969 fn test_zip_files() {
970 let zfs = make_zip_fs();
971
972 assert!(zfs.exists(Path::new("fake_file_name.txt".into())));
973
974 let mut contents = String::new();
975 let _bytes = zfs
976 .open(Path::new("fake_file_name.txt"))
977 .unwrap()
978 .read_to_string(&mut contents)
979 .unwrap();
980 assert_eq!(contents, "Zip contents!");
981 }
982
983 #[test]
984 fn headless_test_zip_all() {
985 let fs = make_zip_fs();
986 let testdir = Path::new("/testdir");
987 let testfile = Path::new("/file1.txt");
988 let existing_file = Path::new("fake_file_name.txt");
990 let existing_dir = Path::new("fake_dir");
991
992 assert!(!fs.exists(testfile));
993 assert!(!fs.exists(testdir));
994 assert!(fs.exists(existing_file));
995 assert!(fs.mkdir(testdir).is_err());
1001 assert!(!fs.exists(testdir));
1002 assert!(fs.rm(testdir).is_err());
1003
1004 let _ = fs.open(existing_file).unwrap();
1006 assert!(fs.create(testfile).is_err());
1008 assert!(fs.append(testfile).is_err());
1010
1011 {
1012 let m = fs.metadata(existing_file).unwrap();
1014 assert!(m.is_file());
1015 assert!(!m.is_dir());
1016 assert_eq!(m.len(), 13);
1017
1018 assert!(fs.metadata(testfile).is_err());
1026 }
1027
1028 {
1029 }
1040
1041 assert!(fs.rmrf(testdir).is_err());
1042 assert!(fs.rmrf(existing_dir).is_err());
1043
1044 }
1045
1046 }