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
12#[cfg(feature = "encryption")]
13use crate::encryption::{AesDecryptor, AesStrength};
14
15const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50;
17
18const CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x02014b50;
20
21const END_OF_CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x06054b50;
23
24const ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x06064b50;
26
27#[derive(Debug, Clone)]
31pub struct ZipEntry {
32 pub name: String,
33 pub compressed_size: u64,
34 pub uncompressed_size: u64,
35 pub compression_method: u16,
36 pub offset: u64,
37 #[cfg(feature = "encryption")]
38 pub is_encrypted: bool,
39}
40
41pub struct StreamingZipReader {
43 file: BufReader<File>,
44 entries: Vec<ZipEntry>,
45 #[cfg(feature = "encryption")]
46 password: Option<String>,
47}
48
49impl StreamingZipReader {
50 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
52 Self::open_with_buffer_size(path, None)
53 }
54
55 pub fn open_with_buffer_size<P: AsRef<Path>>(
75 path: P,
76 buffer_size: Option<usize>,
77 ) -> Result<Self> {
78 let file = File::open(path)?;
79
80 let buf_size = buffer_size.unwrap_or(512 * 1024); let mut file = BufReader::with_capacity(buf_size, file);
83
84 let entries = Self::read_central_directory(&mut file)?;
86
87 Ok(StreamingZipReader {
88 file,
89 entries,
90 #[cfg(feature = "encryption")]
91 password: None,
92 })
93 }
94
95 #[cfg(feature = "encryption")]
97 pub fn set_password(&mut self, password: impl Into<String>) -> &mut Self {
98 self.password = Some(password.into());
99 self
100 }
101
102 #[cfg(feature = "encryption")]
104 pub fn clear_password(&mut self) -> &mut Self {
105 self.password = None;
106 self
107 }
108
109 pub fn entries(&self) -> &[ZipEntry] {
111 &self.entries
112 }
113
114 pub fn find_entry(&self, name: &str) -> Option<&ZipEntry> {
116 self.entries.iter().find(|e| e.name == name)
117 }
118
119 pub fn read_entry(&mut self, entry: &ZipEntry) -> Result<Vec<u8>> {
121 self.file.seek(SeekFrom::Start(entry.offset))?;
123
124 let signature = self.read_u32_le()?;
126 if signature != LOCAL_FILE_HEADER_SIGNATURE {
127 return Err(SZipError::InvalidFormat(
128 "Invalid local file header signature".to_string(),
129 ));
130 }
131
132 self.file.seek(SeekFrom::Current(2))?;
134
135 let flags = self.read_u16_le()?;
137 let is_encrypted = (flags & 0x01) != 0;
138
139 let _compression_method = self.read_u16_le()?;
141
142 self.file.seek(SeekFrom::Current(8))?;
144
145 self.file.seek(SeekFrom::Current(8))?;
147
148 let filename_len = self.read_u16_le()? as i64;
150 let extra_len = self.read_u16_le()? as usize;
151
152 self.file.seek(SeekFrom::Current(filename_len))?;
154
155 #[cfg(feature = "encryption")]
157 let encryption_info = if is_encrypted {
158 self.parse_aes_extra_field(extra_len)?
159 } else {
160 self.file.seek(SeekFrom::Current(extra_len as i64))?;
162 None
163 };
164
165 #[cfg(not(feature = "encryption"))]
166 {
167 if is_encrypted {
168 return Err(SZipError::InvalidFormat(
169 "Encrypted entry found but encryption feature not enabled".to_string(),
170 ));
171 }
172 self.file.seek(SeekFrom::Current(extra_len as i64))?;
174 }
175
176 #[cfg(feature = "encryption")]
178 let data_size = if let Some((strength, _, _)) = encryption_info {
179 entry
181 .compressed_size
182 .saturating_sub((strength.salt_size() + 2 + 10) as u64)
183 } else {
184 entry.compressed_size
185 };
186
187 #[cfg(not(feature = "encryption"))]
188 let data_size = entry.compressed_size;
189
190 let mut compressed_data = vec![0u8; data_size as usize];
192 self.file.read_exact(&mut compressed_data)?;
193
194 #[cfg(feature = "encryption")]
196 let auth_code = if encryption_info.is_some() {
197 let mut ac = vec![0u8; 10];
198 self.file.read_exact(&mut ac)?;
199 Some(ac)
200 } else {
201 None
202 };
203
204 #[cfg(feature = "encryption")]
206 let decryptor_opt = if let Some((strength, salt, pw_verify)) = encryption_info {
207 let password = self.password.as_ref().ok_or_else(|| {
208 SZipError::InvalidFormat("Encrypted entry but no password set".to_string())
209 })?;
210
211 let mut decryptor = AesDecryptor::new(password, strength, &salt, &pw_verify)?;
213
214 decryptor.decrypt(&mut compressed_data)?;
216
217 Some(decryptor)
218 } else {
219 None
220 };
221
222 let data = if entry.compression_method == 8 {
224 let mut decoder = DeflateDecoder::new(&compressed_data[..]);
226 let mut decompressed = Vec::new();
227 decoder.read_to_end(&mut decompressed)?;
228 decompressed
229 } else if entry.compression_method == 0 {
230 compressed_data
232 } else if entry.compression_method == 93 {
233 #[cfg(feature = "zstd-support")]
235 {
236 zstd::decode_all(&compressed_data[..])?
237 }
238 #[cfg(not(feature = "zstd-support"))]
239 {
240 return Err(SZipError::UnsupportedCompression(entry.compression_method));
241 }
242 } else {
243 return Err(SZipError::UnsupportedCompression(entry.compression_method));
244 };
245
246 #[cfg(feature = "encryption")]
248 if let Some(mut decryptor) = decryptor_opt {
249 decryptor.update_hmac(&data);
251
252 if let Some(ac) = auth_code {
254 decryptor.verify_auth_code(&ac)?;
255 }
256 }
257
258 Ok(data)
259 }
260
261 pub fn read_entry_by_name(&mut self, name: &str) -> Result<Vec<u8>> {
263 let entry = self
264 .find_entry(name)
265 .ok_or_else(|| SZipError::EntryNotFound(name.to_string()))?
266 .clone();
267
268 self.read_entry(&entry)
269 }
270
271 pub fn read_entry_streaming_by_name(&mut self, name: &str) -> Result<Box<dyn Read + '_>> {
274 let entry = self
275 .find_entry(name)
276 .ok_or_else(|| SZipError::EntryNotFound(name.to_string()))?
277 .clone();
278
279 self.read_entry_streaming(&entry)
280 }
281
282 pub fn read_entry_streaming(&mut self, entry: &ZipEntry) -> Result<Box<dyn Read + '_>> {
285 self.file.seek(SeekFrom::Start(entry.offset))?;
287
288 let signature = self.read_u32_le()?;
290 if signature != LOCAL_FILE_HEADER_SIGNATURE {
291 return Err(SZipError::InvalidFormat(
292 "Invalid local file header signature".to_string(),
293 ));
294 }
295
296 self.file.seek(SeekFrom::Current(6))?;
298
299 self.file.seek(SeekFrom::Current(8))?;
301
302 self.file.seek(SeekFrom::Current(8))?;
304
305 let filename_len = self.read_u16_le()? as i64;
307 let extra_len = self.read_u16_le()? as i64;
308
309 self.file
311 .seek(SeekFrom::Current(filename_len + extra_len))?;
312
313 let limited_reader = (&mut self.file).take(entry.compressed_size);
315
316 if entry.compression_method == 8 {
318 Ok(Box::new(DeflateDecoder::new(limited_reader)))
320 } else if entry.compression_method == 0 {
321 Ok(Box::new(limited_reader))
323 } else if entry.compression_method == 93 {
324 #[cfg(feature = "zstd-support")]
326 {
327 Ok(Box::new(zstd::Decoder::new(limited_reader)?))
328 }
329 #[cfg(not(feature = "zstd-support"))]
330 {
331 Err(SZipError::UnsupportedCompression(entry.compression_method))
332 }
333 } else {
334 Err(SZipError::UnsupportedCompression(entry.compression_method))
335 }
336 }
337
338 pub fn read_entry_by_name_streaming(&mut self, name: &str) -> Result<Box<dyn Read + '_>> {
340 let entry = self
341 .find_entry(name)
342 .ok_or_else(|| SZipError::EntryNotFound(name.to_string()))?
343 .clone();
344
345 self.read_entry_streaming(&entry)
346 }
347
348 fn read_central_directory(file: &mut BufReader<File>) -> Result<Vec<ZipEntry>> {
350 let eocd_offset = Self::find_eocd(file)?;
352
353 file.seek(SeekFrom::Start(eocd_offset))?;
355
356 let signature = Self::read_u32_le_static(file)?;
358 if signature != END_OF_CENTRAL_DIRECTORY_SIGNATURE {
359 return Err(SZipError::InvalidFormat(format!(
360 "Invalid end of central directory signature: 0x{:08x}",
361 signature
362 )));
363 }
364
365 file.seek(SeekFrom::Current(4))?;
367
368 let _entries_on_disk = Self::read_u16_le_static(file)?;
370
371 let total_entries_16 = Self::read_u16_le_static(file)?;
375
376 let cd_size_32 = Self::read_u32_le_static(file)?;
378
379 let cd_offset_32 = Self::read_u32_le_static(file)? as u64;
381
382 let mut total_entries = total_entries_16 as usize;
384 let mut cd_offset = cd_offset_32;
385 let _cd_size = cd_size_32 as u64;
386
387 if total_entries_16 == 0xFFFF || cd_size_32 == 0xFFFFFFFF || cd_offset_32 == 0xFFFFFFFF {
388 let (zip64_total_entries, zip64_cd_size, zip64_cd_offset) =
390 Self::read_zip64_eocd(file, eocd_offset)?;
391 total_entries = zip64_total_entries as usize;
392 cd_offset = zip64_cd_offset;
393 let _ = zip64_cd_size;
395 }
396
397 file.seek(SeekFrom::Start(cd_offset))?;
399
400 let mut entries = Vec::with_capacity(total_entries);
402 for _ in 0..total_entries {
403 let signature = Self::read_u32_le_static(file)?;
404 if signature != CENTRAL_DIRECTORY_SIGNATURE {
405 break;
406 }
407
408 file.seek(SeekFrom::Current(4))?;
410
411 #[cfg_attr(not(feature = "encryption"), allow(unused_variables))]
413 let flags = Self::read_u16_le_static(file)?;
414
415 let compression_method = Self::read_u16_le_static(file)?;
416
417 file.seek(SeekFrom::Current(8))?;
419
420 let compressed_size_32 = Self::read_u32_le_static(file)? as u64;
422 let uncompressed_size_32 = Self::read_u32_le_static(file)? as u64;
423 let filename_len = Self::read_u16_le_static(file)? as usize;
424 let extra_len = Self::read_u16_le_static(file)? as usize;
425 let comment_len = Self::read_u16_le_static(file)? as usize;
426
427 file.seek(SeekFrom::Current(8))?;
429
430 let mut offset = Self::read_u32_le_static(file)? as u64;
431
432 let mut filename_buf = vec![0u8; filename_len];
434 file.read_exact(&mut filename_buf)?;
435 let name = String::from_utf8_lossy(&filename_buf).to_string();
436
437 let mut extra_buf = vec![0u8; extra_len];
439 if extra_len > 0 {
440 file.read_exact(&mut extra_buf)?;
441 }
442
443 let mut compressed_size = compressed_size_32;
445 let mut uncompressed_size = uncompressed_size_32;
446
447 if compressed_size_32 == 0xFFFFFFFF
448 || uncompressed_size_32 == 0xFFFFFFFF
449 || offset == 0xFFFFFFFF
450 {
451 let mut i = 0usize;
453 while i + 4 <= extra_buf.len() {
454 let id = u16::from_le_bytes([extra_buf[i], extra_buf[i + 1]]);
455 let data_len =
456 u16::from_le_bytes([extra_buf[i + 2], extra_buf[i + 3]]) as usize;
457 i += 4;
458 if i + data_len > extra_buf.len() {
459 break;
460 }
461 if id == 0x0001 {
462 let mut cursor = 0usize;
464 if uncompressed_size_32 == 0xFFFFFFFF && cursor + 8 <= data_len {
466 uncompressed_size = u64::from_le_bytes([
467 extra_buf[i + cursor],
468 extra_buf[i + cursor + 1],
469 extra_buf[i + cursor + 2],
470 extra_buf[i + cursor + 3],
471 extra_buf[i + cursor + 4],
472 extra_buf[i + cursor + 5],
473 extra_buf[i + cursor + 6],
474 extra_buf[i + cursor + 7],
475 ]);
476 cursor += 8;
477 }
478 if compressed_size_32 == 0xFFFFFFFF && cursor + 8 <= data_len {
480 compressed_size = u64::from_le_bytes([
481 extra_buf[i + cursor],
482 extra_buf[i + cursor + 1],
483 extra_buf[i + cursor + 2],
484 extra_buf[i + cursor + 3],
485 extra_buf[i + cursor + 4],
486 extra_buf[i + cursor + 5],
487 extra_buf[i + cursor + 6],
488 extra_buf[i + cursor + 7],
489 ]);
490 cursor += 8;
491 }
492 if offset == 0xFFFFFFFF && cursor + 8 <= data_len {
494 offset = u64::from_le_bytes([
495 extra_buf[i + cursor],
496 extra_buf[i + cursor + 1],
497 extra_buf[i + cursor + 2],
498 extra_buf[i + cursor + 3],
499 extra_buf[i + cursor + 4],
500 extra_buf[i + cursor + 5],
501 extra_buf[i + cursor + 6],
502 extra_buf[i + cursor + 7],
503 ]);
504 }
505 break;
507 }
508 i += data_len;
509 }
510 }
511
512 if comment_len > 0 {
514 file.seek(SeekFrom::Current(comment_len as i64))?;
515 }
516
517 entries.push(ZipEntry {
518 name,
519 compressed_size,
520 uncompressed_size,
521 compression_method,
522 offset,
523 #[cfg(feature = "encryption")]
524 is_encrypted: (flags & 0x01) != 0,
525 });
526 }
527
528 Ok(entries)
529 }
530
531 fn read_zip64_eocd(file: &mut BufReader<File>, eocd_offset: u64) -> Result<(u64, u64, u64)> {
533 let search_start = eocd_offset.saturating_sub(65557);
535 file.seek(SeekFrom::Start(search_start))?;
536 let mut buffer = Vec::new();
537 file.read_to_end(&mut buffer)?;
538
539 let mut locator_pos: Option<usize> = None;
540 for i in (0..buffer.len().saturating_sub(3)).rev() {
541 if buffer[i] == 0x50
542 && buffer[i + 1] == 0x4b
543 && buffer[i + 2] == 0x06
544 && buffer[i + 3] == 0x07
545 {
546 locator_pos = Some(i);
547 break;
548 }
549 }
550
551 let locator_pos = locator_pos
552 .ok_or_else(|| SZipError::InvalidFormat("ZIP64 EOCD locator not found".to_string()))?;
553
554 let rel_off_bytes = &buffer[locator_pos + 8..locator_pos + 16];
557 let zip64_eocd_offset = u64::from_le_bytes([
558 rel_off_bytes[0],
559 rel_off_bytes[1],
560 rel_off_bytes[2],
561 rel_off_bytes[3],
562 rel_off_bytes[4],
563 rel_off_bytes[5],
564 rel_off_bytes[6],
565 rel_off_bytes[7],
566 ]);
567
568 file.seek(SeekFrom::Start(zip64_eocd_offset))?;
570
571 let sig = Self::read_u32_le_static(file)?;
572 if sig != ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE {
573 return Err(SZipError::InvalidFormat(format!(
574 "Invalid ZIP64 EOCD signature: 0x{:08x}",
575 sig
576 )));
577 }
578
579 let _size = {
581 let mut buf = [0u8; 8];
582 file.read_exact(&mut buf)?;
583 u64::from_le_bytes(buf)
584 };
585
586 file.seek(SeekFrom::Current(12))?;
588
589 let total_entries = {
591 let mut buf = [0u8; 8];
592 file.read_exact(&mut buf)?;
593 u64::from_le_bytes(buf)
594 };
595
596 {
598 let mut buf = [0u8; 8];
599 file.read_exact(&mut buf)?;
600 }
602
603 let cd_size = {
605 let mut buf = [0u8; 8];
606 file.read_exact(&mut buf)?;
607 u64::from_le_bytes(buf)
608 };
609
610 let cd_offset = {
612 let mut buf = [0u8; 8];
613 file.read_exact(&mut buf)?;
614 u64::from_le_bytes(buf)
615 };
616
617 Ok((total_entries, cd_size, cd_offset))
618 }
619
620 fn find_eocd(file: &mut BufReader<File>) -> Result<u64> {
622 let file_size = file.seek(SeekFrom::End(0))?;
623
624 let search_start = file_size.saturating_sub(65557);
626 file.seek(SeekFrom::Start(search_start))?;
627
628 let mut buffer = Vec::new();
629 file.read_to_end(&mut buffer)?;
630
631 for i in (0..buffer.len().saturating_sub(3)).rev() {
633 if buffer[i] == 0x50
634 && buffer[i + 1] == 0x4b
635 && buffer[i + 2] == 0x05
636 && buffer[i + 3] == 0x06
637 {
638 return Ok(search_start + i as u64);
639 }
640 }
641
642 Err(SZipError::InvalidFormat(
643 "End of central directory not found".to_string(),
644 ))
645 }
646
647 fn read_u16_le(&mut self) -> Result<u16> {
648 let mut buf = [0u8; 2];
649 self.file.read_exact(&mut buf)?;
650 Ok(u16::from_le_bytes(buf))
651 }
652
653 fn read_u32_le(&mut self) -> Result<u32> {
654 let mut buf = [0u8; 4];
655 self.file.read_exact(&mut buf)?;
656 Ok(u32::from_le_bytes(buf))
657 }
658
659 fn read_u16_le_static(file: &mut BufReader<File>) -> Result<u16> {
660 let mut buf = [0u8; 2];
661 file.read_exact(&mut buf)?;
662 Ok(u16::from_le_bytes(buf))
663 }
664
665 fn read_u32_le_static(file: &mut BufReader<File>) -> Result<u32> {
666 let mut buf = [0u8; 4];
667 file.read_exact(&mut buf)?;
668 Ok(u32::from_le_bytes(buf))
669 }
670
671 #[cfg(feature = "encryption")]
673 #[allow(clippy::type_complexity)]
674 fn parse_aes_extra_field(
675 &mut self,
676 extra_len: usize,
677 ) -> Result<Option<(AesStrength, Vec<u8>, [u8; 2])>> {
678 if extra_len == 0 {
679 return Ok(None);
680 }
681
682 let mut extra_buf = vec![0u8; extra_len];
683 self.file.read_exact(&mut extra_buf)?;
684
685 let mut i = 0usize;
687 while i + 4 <= extra_buf.len() {
688 let id = u16::from_le_bytes([extra_buf[i], extra_buf[i + 1]]);
689 let data_len = u16::from_le_bytes([extra_buf[i + 2], extra_buf[i + 3]]) as usize;
690 i += 4;
691
692 if i + data_len > extra_buf.len() {
693 break;
694 }
695
696 if id == 0x9901 {
697 if data_len < 7 {
701 return Err(SZipError::InvalidFormat(
702 "Invalid AES extra field".to_string(),
703 ));
704 }
705
706 let strength_code = extra_buf[i + 4]; let strength = match strength_code {
709 0x03 => AesStrength::Aes256,
710 _ => {
711 return Err(SZipError::InvalidFormat(format!(
712 "Unsupported AES strength: {}",
713 strength_code
714 )))
715 }
716 };
717
718 let salt_size = strength.salt_size();
721
722 let mut salt = vec![0u8; salt_size];
723 self.file.read_exact(&mut salt)?;
724
725 let mut pw_verify = [0u8; 2];
726 self.file.read_exact(&mut pw_verify)?;
727
728 return Ok(Some((strength, salt, pw_verify)));
729 }
730
731 i += data_len;
732 }
733
734 Ok(None)
735 }
736}