use std::error::Error; use std::fmt::{Display, Formatter}; use std::{fs, io, os}; use std::fs::File; use std::io::{BufReader, Error as IOError, Read}; use std::path::{Component, Path, PathBuf}; use log::{debug, trace}; use zip::result::ZipError; use zip::ZipArchive; use crate::util; #[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 } } } fn check_entry_path(name: &str) -> Result<&Path, ZipExtractError> { util::check_path(name).map_err(|e| ZipExtractError::InvalidEntry { why: e, name: name.to_owned() }) } #[cfg(unix)] fn extract_symlink(path: impl AsRef, target: &str) -> io::Result<()> { os::unix::fs::symlink(target, path) } #[cfg(windows)] fn extract_symlink(path: impl AsRef, target: &str) -> io::Result<()> { os::windows::fs::symlink_file(target, path) } #[cfg(not(any(unix, windows)))] fn extract_symlink(path: impl AsRef, _target: &str) -> io::Result<()> { warn!("Refusing to extract symbolic link to {}. I don't know how to do it on this platform!", path.as_ref().display()); Ok(()) } 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)))?; // create directories for n in 0..archive.len() { let entry = archive.by_index(n).map_err(|e| ZipExtractError::from(("read zip entry (1)", e)))?; if !entry.is_dir() { continue; } let name = entry.name(); if !condition(name) { continue; } let entry_path = check_entry_path(name)?; let entry_path: PathBuf = [extract_root.as_ref(), entry_path].iter().collect(); trace!("Extracting directory {} from {}", entry.name(), zip_path.as_ref().display()); fs::create_dir_all(entry_path).map_err(|e| ZipExtractError::from(("extract directory", e)))?; } // extract the files for n in 0..archive.len() { let mut entry = archive.by_index(n).map_err(|e| ZipExtractError::from(("read zip entry (2)", e)))?; let name = entry.name(); if entry.is_dir() { continue; } if !condition(name) { continue; } let entry_path = check_entry_path(name)?; let entry_path: PathBuf = [extract_root.as_ref(), entry_path].iter().collect(); if entry.is_symlink() { let mut target = String::new(); entry.read_to_string(&mut target).map_err(|e| ZipExtractError::from(("read to symlink target", e)))?; trace!("Extracting symbolic link {} -> {} from {}", entry.name(), target, zip_path.as_ref().display()); extract_symlink(entry_path.as_path(), target.as_str()).map_err(|e| ZipExtractError::from(("extract symlink", e)))?; } else if entry.is_file() { let mut outfile = File::create(&entry_path).map_err(|e| ZipExtractError::from(("extract zip entry (open)", e)))?; trace!("Extracting file {} from {}", entry.name(), zip_path.as_ref().display()); io::copy(&mut entry, &mut outfile).map_err(|e| ZipExtractError::from(("extract zip entry (write)", e)))?; extracted += 1; } } Ok(extracted) }