1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
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<F>(zip_path: impl AsRef<Path>, extract_root: impl AsRef<Path>, condition: F) -> Result<usize, ZipExtractError>
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)
}
|