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
10pub 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
17pub 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
34pub 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
49pub 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
72pub fn is_zip(file: &PathBuf) -> bool {
74 try_is_zip(file).unwrap_or_default()
75}
76
77pub trait ZipArchiveExtensions {
78 fn extract(&mut self, path: &PathBuf) -> ZipResult<()>;
80
81 fn extract_file(
83 &mut self,
84 file_number: usize,
85 destination_file_path: &PathBuf,
86 overwrite: bool,
87 ) -> ZipResult<()>;
88
89 fn extract_file_to_memory(&mut self, file_number: usize, buffer: &mut Vec<u8>)
91 -> ZipResult<()>;
92
93 fn entry_path(&mut self, file_number: usize) -> ZipResult<PathBuf>;
95
96 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}