1#[cfg(feature = "vfs04")] mod vfs04;
2
3use crate::{Error, Result};
4
5use std::collections::*;
6use std::fmt::{self, Debug, Formatter};
7use std::io::{Read, Seek};
8use std::path::*;
9use std::sync::Mutex;
10
11
12
13pub struct ZipReadOnly<IO: Read + Seek + Send + 'static> {
15 archive: Mutex<zip::read::ZipArchive<IO>>,
16 files: BTreeMap<String, usize>, dirs: BTreeMap<String, BTreeSet<String>>, }
19
20impl<IO: Read + Seek + Send> Debug for ZipReadOnly<IO> {
21 fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
22 write!(fmt, "ZipReadOnly")
23 }
24}
25
26impl<IO: Read + Seek + Send> ZipReadOnly<IO> {
27 pub fn new_strict(io: IO) -> Result<Self> { Self::new_impl(io, false) }
30
31 pub fn new_relaxed(io: IO) -> Result<Self> { Self::new_impl(io, true) }
34
35 fn new_impl(io: IO, ignore_file_errors: bool) -> Result<Self> {
36 let mut za = Self {
37 archive: Mutex::new(zip::read::ZipArchive::new(io).map_err(Error)?),
38 files: Default::default(),
39 dirs: Default::default(),
40 };
41 za.dirs.insert(String::new(), Default::default()); let mut archive = za.archive.lock().unwrap();
44 'files: for i in 0..archive.len() {
45 let entry = archive.by_index(i);
46 if ignore_file_errors && entry.is_err() { continue; }
47 let entry = entry.map_err(Error)?;
48 let abs = entry.name();
49 if abs.contains('\\') { if ignore_file_errors { continue } return Err(Error::unsupported("vfs-zip doesn't support zip archives containing backslashes in paths")); }
50 if abs.contains("//") { if ignore_file_errors { continue } return Err(Error::unsupported("vfs-zip doesn't support zip archives containing 0-length directory names")); }
51 let mut abs = abs.trim_end_matches('/');
52 if Path::new(abs).is_absolute() { if ignore_file_errors { continue } return Err(Error::unsupported("vfs-zip doesn't support zip archives containing absolute paths")); }
53
54 if entry.is_file() {
55 if za.files.insert(abs.into(), i).is_some() { continue 'files; } } else if entry.is_dir() {
57 za.dirs.entry(abs.into()).or_default();
58 }
59
60 while let Some(slash) = abs.rfind('/') {
61 let dir_name = &abs[..slash];
62 let leaf_name = &abs[slash+1..];
63
64 let dir = za.dirs.entry(dir_name.into()).or_default();
65 if !dir.insert(leaf_name.into()) { continue 'files; } abs = dir_name;
68 }
69
70 let root = za.dirs.get_mut("").unwrap();
71 root.insert(abs.into());
72 }
73
74 std::mem::drop(archive); Ok(za)
76 }
77}