Skip to main content

pacenotes_installer\zip/
extraction.rs

1/// One file a package will install.
2#[derive(Clone, Debug, PartialEq, Eq)]
3pub struct FileEntry {
4    /// Relative, `/`-separated, validated.
5    pub path: String,
6    /// Uncompressed, so the pre-flight can tell whether an install fits without extracting it.
7    pub size: u64,
8}
9
10/// The files a package will own, in zip order; directory entries are left out,
11/// since an empty directory is nothing to back up, record or delete.
12pub fn entries(zip_bytes: &[u8]) -> Result<Vec<FileEntry>, super::ZipError> {
13    Ok(super::central_directory::read(zip_bytes)?
14        .into_iter()
15        .filter(|entry| !entry.is_dir)
16        .map(|entry| FileEntry {
17            path: entry.path,
18            size: entry.uncompressed as u64,
19        })
20        .collect())
21}
22
23pub fn entry_paths(zip_bytes: &[u8]) -> Result<Vec<String>, super::ZipError> {
24    Ok(entries(zip_bytes)?
25        .into_iter()
26        .map(|entry| entry.path)
27        .collect())
28}
29
30/// One file's bytes, matched case-insensitively. Reading several from one
31/// package goes through `Archive`, which reads the directory only once.
32pub fn read_entry(zip_bytes: &[u8], path: &str) -> Result<Vec<u8>, super::ZipError> {
33    super::Archive::open(zip_bytes)?.read(path)
34}
35
36/// Extracts into `dest_dir`, overwriting existing files as the legacy installer
37/// did, and returns the files written in the form `entry_paths` reports them.
38///
39/// Every name is validated before anything is written, so a hostile entry
40/// leaves the target untouched.
41pub fn extract(
42    zip_bytes: &[u8],
43    dest_dir: &std::path::Path,
44) -> Result<Vec<String>, super::ZipError> {
45    let entries = super::central_directory::read(zip_bytes)?;
46    create_dir(dest_dir)?;
47
48    let mut written = Vec::new();
49    for entry in &entries {
50        let target = crate::relative_path::join(dest_dir, &entry.path);
51        if entry.is_dir {
52            create_dir(&target)?;
53            continue;
54        }
55        // .NET's zips omit some parent directories as entries of their own.
56        if let Some(parent) = target.parent() {
57            create_dir(parent)?;
58        }
59        let data = super::entry_data::read(zip_bytes, entry)?;
60        std::fs::write(&target, &data)
61            .map_err(|e| super::ZipError::new(format!("cannot write {}: {e}", target.display())))?;
62        written.push(entry.path.clone());
63    }
64    Ok(written)
65}
66
67fn create_dir(path: &std::path::Path) -> Result<(), super::ZipError> {
68    std::fs::create_dir_all(path)
69        .map_err(|e| super::ZipError::new(format!("cannot create {}: {e}", path.display())))
70}