summaryrefslogtreecommitdiffstats
path: root/src/launcher/extract.rs
blob: c9e0dc94f56918c8b870a02e67925f1301a7296e (plain) (blame)
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)
}