zip_extensions/
read.rs

1use crate::file_utils::file_write_all_bytes;
2use std::fs::File;
3use std::io;
4use std::io::{Error, ErrorKind, Read};
5use std::path::PathBuf;
6use zip::read::ZipFile;
7use zip::result::{ZipError, ZipResult};
8use zip::ZipArchive;
9
10/// Extracts a ZIP file to the given directory.
11pub fn zip_extract(archive_file: &PathBuf, target_dir: &PathBuf) -> ZipResult<()> {
12    let file = File::open(archive_file)?;
13    let mut archive = ZipArchive::new(file)?;
14    archive.extract(target_dir)
15}
16
17/// Extracts and entry in the ZIP archive to the given directory.
18pub fn zip_extract_file(
19    archive_file: &PathBuf,
20    entry_path: &PathBuf,
21    target_dir: &PathBuf,
22    overwrite: bool,
23) -> ZipResult<()> {
24    let file = File::open(archive_file)?;
25    let mut archive = ZipArchive::new(file)?;
26    let file_number: usize = match archive.file_number(entry_path) {
27        Some(index) => index,
28        None => return Err(ZipError::FileNotFound),
29    };
30    let destination_file_path = target_dir.join(entry_path);
31    archive.extract_file(file_number, &destination_file_path, overwrite)
32}
33
34/// Extracts an entry in the ZIP archive to the given memory buffer.
35pub fn zip_extract_file_to_memory(
36    archive_file: &PathBuf,
37    entry_path: &PathBuf,
38    buffer: &mut Vec<u8>,
39) -> ZipResult<()> {
40    let file = File::open(archive_file)?;
41    let mut archive = ZipArchive::new(file)?;
42    let file_number: usize = match archive.file_number(entry_path) {
43        Some(index) => index,
44        None => return Err(ZipError::FileNotFound),
45    };
46    archive.extract_file_to_memory(file_number, buffer)
47}
48
49/// Determines whether the specified file is a ZIP file, or not.
50pub fn try_is_zip(file: &PathBuf) -> ZipResult<bool> {
51    const ZIP_SIGNATURE: [u8; 2] = [0x50, 0x4b];
52    const ZIP_ARCHIVE_FORMAT: [u8; 6] = [0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
53    let mut file = File::open(file)?;
54    let mut buffer: [u8; 4] = [0; 4];
55    let bytes_read = file.read(&mut buffer)?;
56    if bytes_read == buffer.len() {
57        for i in 0..ZIP_SIGNATURE.len() {
58            if buffer[i] != ZIP_SIGNATURE[i] {
59                return Ok(false);
60            }
61        }
62
63        for i in (0..ZIP_ARCHIVE_FORMAT.len()).step_by(2) {
64            if buffer[2] == ZIP_ARCHIVE_FORMAT[i] || buffer[3] == ZIP_ARCHIVE_FORMAT[i + 1] {
65                return Ok(true);
66            }
67        }
68    }
69    Ok(false)
70}
71
72/// Determines whether the specified file is a ZIP file, or not.
73pub fn is_zip(file: &PathBuf) -> bool {
74    try_is_zip(file).unwrap_or_default()
75}
76
77pub trait ZipArchiveExtensions {
78    /// Extracts the current archive to the given directory path.
79    fn extract(&mut self, path: &PathBuf) -> ZipResult<()>;
80
81    /// Extracts an entry in the zip archive to a file.
82    fn extract_file(
83        &mut self,
84        file_number: usize,
85        destination_file_path: &PathBuf,
86        overwrite: bool,
87    ) -> ZipResult<()>;
88
89    /// Extracts an entry in the ZIP archive to the given memory buffer.
90    fn extract_file_to_memory(&mut self, file_number: usize, buffer: &mut Vec<u8>)
91        -> ZipResult<()>;
92
93    /// Gets an entry´s path.
94    fn entry_path(&mut self, file_number: usize) -> ZipResult<PathBuf>;
95
96    /// Finds the index of the specified entry.
97    fn file_number(&mut self, entry_path: &PathBuf) -> Option<usize>;
98}
99
100impl<R: Read + io::Seek> ZipArchiveExtensions for ZipArchive<R> {
101    fn extract(&mut self, target_directory: &PathBuf) -> ZipResult<()> {
102        if !target_directory.is_dir() {
103            return Err(ZipError::Io(Error::new(
104                ErrorKind::InvalidInput,
105                "The specified path does not indicate a valid directory path.",
106            )));
107        }
108
109        for file_number in 0..self.len() {
110            let mut next: ZipFile<R> = self.by_index(file_number)?;
111            let sanitized_name = next.mangled_name();
112            if next.is_dir() {
113                let extracted_folder_path = target_directory.join(sanitized_name);
114                std::fs::create_dir_all(extracted_folder_path)?;
115            } else if next.is_file() {
116                let mut buffer: Vec<u8> = Vec::new();
117                let _bytes_read = next.read_to_end(&mut buffer)?;
118                let extracted_file_path = target_directory.join(sanitized_name);
119                file_write_all_bytes(extracted_file_path, buffer.as_ref(), true)?;
120            }
121        }
122
123        Ok(())
124    }
125
126    fn extract_file(
127        &mut self,
128        file_number: usize,
129        destination_file_path: &PathBuf,
130        overwrite: bool,
131    ) -> ZipResult<()> {
132        let mut buffer: Vec<u8> = Vec::new();
133        self.extract_file_to_memory(file_number, &mut buffer)?;
134        file_write_all_bytes(
135            destination_file_path.to_path_buf(),
136            buffer.as_ref(),
137            overwrite,
138        )?;
139        Ok(())
140    }
141
142    fn extract_file_to_memory(
143        &mut self,
144        file_number: usize,
145        buffer: &mut Vec<u8>,
146    ) -> ZipResult<()> {
147        let mut next: ZipFile<R> = self.by_index(file_number)?;
148        if next.is_file() {
149            let _bytes_read = next.read_to_end(buffer)?;
150            return Ok(());
151        }
152        Err(ZipError::Io(Error::new(
153            ErrorKind::InvalidInput,
154            "The specified index does not indicate a file entry.",
155        )))
156    }
157
158    fn entry_path(&mut self, file_number: usize) -> ZipResult<PathBuf> {
159        let next: ZipFile<R> = self.by_index(file_number)?;
160        Ok(next.mangled_name())
161    }
162
163    fn file_number(&mut self, entry_path: &PathBuf) -> Option<usize> {
164        for file_number in 0..self.len() {
165            if let Ok(next) = self.by_index(file_number) {
166                let sanitized_name = next.mangled_name();
167                if sanitized_name == *entry_path {
168                    return Some(file_number);
169                }
170            }
171        }
172        None
173    }
174}