summaryrefslogtreecommitdiffstats
path: root/src/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'src/launcher')
-rw-r--r--src/launcher/extract.rs105
1 files changed, 105 insertions, 0 deletions
diff --git a/src/launcher/extract.rs b/src/launcher/extract.rs
new file mode 100644
index 0000000..c9e0dc9
--- /dev/null
+++ b/src/launcher/extract.rs
@@ -0,0 +1,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)
+}