vfs_zip/read/
_read.rs

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
13/// A read-only zip archive filesystem
14pub struct ZipReadOnly<IO: Read + Seek + Send + 'static> {
15    archive:    Mutex<zip::read::ZipArchive<IO>>,
16    files:      BTreeMap<String, usize>,            // abs path -> zip archive index
17    dirs:       BTreeMap<String, BTreeSet<String>>, // abs path -> [relative file/dir names]
18}
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    /// Create a new read-only zip filesystem.
28    /// Any archive errors (including unsupported paths) will result in errors.
29    pub fn new_strict(io: IO) -> Result<Self> { Self::new_impl(io, false) }
30
31    /// Create a new read-only zip filesystem.
32    /// Some archive errors (such as unsupported paths) will be ignored.
33    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()); // always have a root directory
42
43        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; } // already inserted
56            } 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; } // already inserted
66
67                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); // unlock
75        Ok(za)
76    }
77}