use std::error::Error; use std::fmt::{Display, Formatter}; use std::{fs, io}; use std::fs::File; use std::io::{BufReader, Error as IOError}; use std::path::{Component, Path, PathBuf}; use log::debug; use zip::result::ZipError; use zip::ZipArchive; #[derive(Debug)] pub enum ZipExtractError { IO { what: &'static str, error: IOError }, Zip { what: &'static str, error: ZipError }, InvalidEntry { why: &'static str, name: String } } impl From<(&'static str, IOError)> for ZipExtractError { fn from((what, error): (&'static str, IOError)) -> Self { ZipExtractError::IO { what, error } } } impl From<(&'static str, ZipError)> for ZipExtractError { fn from((what, error): (&'static str, ZipError)) -> Self { ZipExtractError::Zip { what, error } } } impl Display for ZipExtractError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ZipExtractError::IO { what, error } => write!(f, "i/o error ({what}): {error}"), ZipExtractError::Zip { what, error } => write!(f, "zip error ({what}): {error}"), ZipExtractError::InvalidEntry { why, name } => write!(f, "invalid entry in zip file ({why}): {name}") } } } impl Error for ZipExtractError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { ZipExtractError::IO { error, .. } => Some(error), ZipExtractError::Zip { error, .. } => Some(error), _ => None } } } pub fn extract_zip(zip_path: impl AsRef, extract_root: impl AsRef, condition: F) -> Result where F: Fn(&str) -> bool { debug!("Extracting zip file {} into {}", zip_path.as_ref().display(), extract_root.as_ref().display()); fs::create_dir_all(&extract_root).map_err(|e| ZipExtractError::from(("create extract root", e)))?; let mut extracted = 0usize; let file = File::open(zip_path).map_err(|e| ZipExtractError::from(("extract zip file (open)", e)))?; let read = BufReader::new(file); let mut archive = ZipArchive::new(read).map_err(|e| ZipExtractError::from(("read zip archive", e)))?; for n in 0..archive.len() { let mut entry = archive.by_index(n).map_err(|e| ZipExtractError::from(("access zip entry", e)))?; let name = entry.name(); if !condition(name) { continue; } let entry_path: &Path = Path::new(name); let mut depth = 0usize; for component in entry_path.components() { depth = match component { Component::Prefix(_) | Component::RootDir => return Err(ZipExtractError::InvalidEntry { why: "root path component in entry", name: name.to_owned() }), Component::ParentDir => depth.checked_sub(1) .map_or_else(|| Err(ZipExtractError::InvalidEntry { why: "entry path escapes extraction root", name: name.to_owned() }), |s| Ok(s))?, Component::Normal(_) => depth + 1, _ => depth } } let entry_path: PathBuf = [extract_root.as_ref(), entry_path].iter().collect(); // hmm some redundant directory creations will be happening here on linux :( if let Some(parent) = entry_path.parent() { fs::create_dir_all(parent).map_err(|e| ZipExtractError::from(("create entry directory", e)))?; } let mut outfile = File::create(&entry_path).map_err(|e| ZipExtractError::from(("extract zip entry (open)", e)))?; io::copy(&mut entry, &mut outfile).map_err(|e| ZipExtractError::from(("extract zip entry (write)", e)))?; extracted += 1; } Ok(extracted) }