1use crate::error::{Result, SZipError};
7use flate2::read::DeflateDecoder;
8use std::fs::File;
9use std::io::{BufReader, Read, Seek, SeekFrom};
10use std::path::Path;
11
12const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50;
14
15const CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x02014b50;
17
18const END_OF_CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x06054b50;
20
21#[derive(Debug, Clone)]
23pub struct ZipEntry {
24 pub name: String,
25 pub compressed_size: u64,
26 pub uncompressed_size: u64,
27 pub compression_method: u16,
28 pub offset: u64,
29}
30
31pub struct StreamingZipReader {
33 file: BufReader<File>,
34 entries: Vec<ZipEntry>,
35}
36
37impl StreamingZipReader {
38 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
40 let mut file = BufReader::new(File::open(path)?);
41
42 let entries = Self::read_central_directory(&mut file)?;
44
45 Ok(StreamingZipReader { file, entries })
46 }
47
48 pub fn entries(&self) -> &[ZipEntry] {
50 &self.entries
51 }
52
53 pub fn find_entry(&self, name: &str) -> Option<&ZipEntry> {
55 self.entries.iter().find(|e| e.name == name)
56 }
57
58 pub fn read_entry(&mut self, entry: &ZipEntry) -> Result<Vec<u8>> {
60 self.file.seek(SeekFrom::Start(entry.offset))?;
62
63 let signature = self.read_u32_le()?;
65 if signature != LOCAL_FILE_HEADER_SIGNATURE {
66 return Err(SZipError::InvalidFormat(
67 "Invalid local file header signature".to_string(),
68 ));
69 }
70
71 self.file.seek(SeekFrom::Current(6))?;
73
74 self.file.seek(SeekFrom::Current(8))?;
76
77 self.file.seek(SeekFrom::Current(8))?;
79
80 let filename_len = self.read_u16_le()? as i64;
82 let extra_len = self.read_u16_le()? as i64;
83
84 self.file
86 .seek(SeekFrom::Current(filename_len + extra_len))?;
87
88 let mut compressed_data = vec![0u8; entry.compressed_size as usize];
90 self.file.read_exact(&mut compressed_data)?;
91
92 let data = if entry.compression_method == 8 {
94 let mut decoder = DeflateDecoder::new(&compressed_data[..]);
96 let mut decompressed = Vec::new();
97 decoder.read_to_end(&mut decompressed)?;
98 decompressed
99 } else if entry.compression_method == 0 {
100 compressed_data
102 } else {
103 return Err(SZipError::UnsupportedCompression(entry.compression_method));
104 };
105
106 Ok(data)
107 }
108
109 pub fn read_entry_by_name(&mut self, name: &str) -> Result<Vec<u8>> {
111 let entry = self
112 .find_entry(name)
113 .ok_or_else(|| SZipError::EntryNotFound(name.to_string()))?
114 .clone();
115
116 self.read_entry(&entry)
117 }
118
119 pub fn read_entry_streaming_by_name(&mut self, name: &str) -> Result<Box<dyn Read + '_>> {
122 let entry = self
123 .find_entry(name)
124 .ok_or_else(|| SZipError::EntryNotFound(name.to_string()))?
125 .clone();
126
127 self.read_entry_streaming(&entry)
128 }
129
130 pub fn read_entry_streaming(&mut self, entry: &ZipEntry) -> Result<Box<dyn Read + '_>> {
133 self.file.seek(SeekFrom::Start(entry.offset))?;
135
136 let signature = self.read_u32_le()?;
138 if signature != LOCAL_FILE_HEADER_SIGNATURE {
139 return Err(SZipError::InvalidFormat(
140 "Invalid local file header signature".to_string(),
141 ));
142 }
143
144 self.file.seek(SeekFrom::Current(6))?;
146
147 self.file.seek(SeekFrom::Current(8))?;
149
150 self.file.seek(SeekFrom::Current(8))?;
152
153 let filename_len = self.read_u16_le()? as i64;
155 let extra_len = self.read_u16_le()? as i64;
156
157 self.file
159 .seek(SeekFrom::Current(filename_len + extra_len))?;
160
161 let limited_reader = (&mut self.file).take(entry.compressed_size);
163
164 if entry.compression_method == 8 {
166 Ok(Box::new(DeflateDecoder::new(limited_reader)))
168 } else if entry.compression_method == 0 {
169 Ok(Box::new(limited_reader))
171 } else {
172 Err(SZipError::UnsupportedCompression(entry.compression_method))
173 }
174 }
175
176 pub fn read_entry_by_name_streaming(&mut self, name: &str) -> Result<Box<dyn Read + '_>> {
178 let entry = self
179 .find_entry(name)
180 .ok_or_else(|| SZipError::EntryNotFound(name.to_string()))?
181 .clone();
182
183 self.read_entry_streaming(&entry)
184 }
185
186 fn read_central_directory(file: &mut BufReader<File>) -> Result<Vec<ZipEntry>> {
188 let eocd_offset = Self::find_eocd(file)?;
190
191 file.seek(SeekFrom::Start(eocd_offset))?;
193
194 let signature = Self::read_u32_le_static(file)?;
196 if signature != END_OF_CENTRAL_DIRECTORY_SIGNATURE {
197 return Err(SZipError::InvalidFormat(format!(
198 "Invalid end of central directory signature: 0x{:08x}",
199 signature
200 )));
201 }
202
203 file.seek(SeekFrom::Current(4))?;
205
206 let _entries_on_disk = Self::read_u16_le_static(file)?;
208
209 let total_entries = Self::read_u16_le_static(file)? as usize;
211
212 let _cd_size = Self::read_u32_le_static(file)?;
214
215 let cd_offset = Self::read_u32_le_static(file)? as u64;
217
218 file.seek(SeekFrom::Start(cd_offset))?;
220
221 let mut entries = Vec::with_capacity(total_entries);
223 for _ in 0..total_entries {
224 let signature = Self::read_u32_le_static(file)?;
225 if signature != CENTRAL_DIRECTORY_SIGNATURE {
226 break;
227 }
228
229 file.seek(SeekFrom::Current(6))?;
231
232 let compression_method = Self::read_u16_le_static(file)?;
233
234 file.seek(SeekFrom::Current(8))?;
236
237 let compressed_size = Self::read_u32_le_static(file)? as u64;
238 let uncompressed_size = Self::read_u32_le_static(file)? as u64;
239 let filename_len = Self::read_u16_le_static(file)? as usize;
240 let extra_len = Self::read_u16_le_static(file)? as usize;
241 let comment_len = Self::read_u16_le_static(file)? as usize;
242
243 file.seek(SeekFrom::Current(8))?;
245
246 let offset = Self::read_u32_le_static(file)? as u64;
247
248 let mut filename_buf = vec![0u8; filename_len];
250 file.read_exact(&mut filename_buf)?;
251 let name = String::from_utf8_lossy(&filename_buf).to_string();
252
253 file.seek(SeekFrom::Current((extra_len + comment_len) as i64))?;
255
256 entries.push(ZipEntry {
257 name,
258 compressed_size,
259 uncompressed_size,
260 compression_method,
261 offset,
262 });
263 }
264
265 Ok(entries)
266 }
267
268 fn find_eocd(file: &mut BufReader<File>) -> Result<u64> {
270 let file_size = file.seek(SeekFrom::End(0))?;
271
272 let search_start = file_size.saturating_sub(65557);
274 file.seek(SeekFrom::Start(search_start))?;
275
276 let mut buffer = Vec::new();
277 file.read_to_end(&mut buffer)?;
278
279 for i in (0..buffer.len().saturating_sub(3)).rev() {
281 if buffer[i] == 0x50
282 && buffer[i + 1] == 0x4b
283 && buffer[i + 2] == 0x05
284 && buffer[i + 3] == 0x06
285 {
286 return Ok(search_start + i as u64);
287 }
288 }
289
290 Err(SZipError::InvalidFormat(
291 "End of central directory not found".to_string(),
292 ))
293 }
294
295 fn read_u16_le(&mut self) -> Result<u16> {
296 let mut buf = [0u8; 2];
297 self.file.read_exact(&mut buf)?;
298 Ok(u16::from_le_bytes(buf))
299 }
300
301 fn read_u32_le(&mut self) -> Result<u32> {
302 let mut buf = [0u8; 4];
303 self.file.read_exact(&mut buf)?;
304 Ok(u32::from_le_bytes(buf))
305 }
306
307 fn read_u16_le_static(file: &mut BufReader<File>) -> Result<u16> {
308 let mut buf = [0u8; 2];
309 file.read_exact(&mut buf)?;
310 Ok(u16::from_le_bytes(buf))
311 }
312
313 fn read_u32_le_static(file: &mut BufReader<File>) -> Result<u32> {
314 let mut buf = [0u8; 4];
315 file.read_exact(&mut buf)?;
316 Ok(u32::from_le_bytes(buf))
317 }
318}