1use crate::error::{Result, SZipError};
7use crate::format::{
8 find_eocd_in_buffer, find_zip64_eocd_offset, parse_zip64_extra_field,
9 CENTRAL_DIRECTORY_SIGNATURE, END_OF_CENTRAL_DIRECTORY_SIGNATURE, LOCAL_FILE_HEADER_SIGNATURE,
10 MAX_ENTRY_ALLOC, ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE,
11};
12
13#[cfg(feature = "encryption")]
14use crate::format::parse_aes_extra_field_buf;
15use flate2::read::DeflateDecoder;
16use std::fs::File;
17use std::io::{BufReader, Read, Seek, SeekFrom};
18use std::path::Path;
19
20#[cfg(feature = "encryption")]
21use crate::encryption::{AesDecryptor, AesStrength};
22
23pub use crate::format::ZipEntry;
25
26pub struct StreamingZipReader {
28 file: BufReader<File>,
29 entries: Vec<ZipEntry>,
30 #[cfg(feature = "encryption")]
31 password: Option<String>,
32}
33
34impl StreamingZipReader {
35 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
37 Self::open_with_buffer_size(path, None)
38 }
39
40 pub fn open_with_buffer_size<P: AsRef<Path>>(
60 path: P,
61 buffer_size: Option<usize>,
62 ) -> Result<Self> {
63 let file = File::open(path)?;
64
65 let buf_size = buffer_size.unwrap_or(512 * 1024); let mut file = BufReader::with_capacity(buf_size, file);
68
69 let entries = Self::read_central_directory(&mut file)?;
71
72 Ok(StreamingZipReader {
73 file,
74 entries,
75 #[cfg(feature = "encryption")]
76 password: None,
77 })
78 }
79
80 #[cfg(feature = "encryption")]
82 pub fn set_password(&mut self, password: impl Into<String>) -> &mut Self {
83 self.password = Some(password.into());
84 self
85 }
86
87 #[cfg(feature = "encryption")]
89 pub fn clear_password(&mut self) -> &mut Self {
90 self.password = None;
91 self
92 }
93
94 pub fn entries(&self) -> &[ZipEntry] {
96 &self.entries
97 }
98
99 pub fn find_entry(&self, name: &str) -> Option<&ZipEntry> {
101 self.entries.iter().find(|e| e.name == name)
102 }
103
104 pub fn read_entry(&mut self, entry: &ZipEntry) -> Result<Vec<u8>> {
106 self.file.seek(SeekFrom::Start(entry.offset))?;
108
109 let signature = self.read_u32_le()?;
111 if signature != LOCAL_FILE_HEADER_SIGNATURE {
112 return Err(SZipError::InvalidFormat(
113 "Invalid local file header signature".to_string(),
114 ));
115 }
116
117 self.file.seek(SeekFrom::Current(2))?;
119
120 let flags = self.read_u16_le()?;
122 let is_encrypted = (flags & 0x01) != 0;
123
124 let _compression_method = self.read_u16_le()?;
126
127 self.file.seek(SeekFrom::Current(8))?;
129
130 self.file.seek(SeekFrom::Current(8))?;
132
133 let filename_len = self.read_u16_le()? as i64;
135 let extra_len = self.read_u16_le()? as usize;
136
137 self.file.seek(SeekFrom::Current(filename_len))?;
139
140 #[cfg(feature = "encryption")]
142 let encryption_info = if is_encrypted {
143 self.parse_aes_extra_field(extra_len)?
144 } else {
145 self.file.seek(SeekFrom::Current(extra_len as i64))?;
147 None
148 };
149
150 #[cfg(not(feature = "encryption"))]
151 {
152 if is_encrypted {
153 return Err(SZipError::InvalidFormat(
154 "Encrypted entry found but encryption feature not enabled".to_string(),
155 ));
156 }
157 self.file.seek(SeekFrom::Current(extra_len as i64))?;
159 }
160
161 #[cfg(feature = "encryption")]
163 let data_size = if let Some((strength, _, _)) = encryption_info {
164 entry
166 .compressed_size
167 .saturating_sub((strength.salt_size() + 2 + 10) as u64)
168 } else {
169 entry.compressed_size
170 };
171
172 #[cfg(not(feature = "encryption"))]
173 let data_size = entry.compressed_size;
174
175 if data_size > MAX_ENTRY_ALLOC {
178 return Err(SZipError::InvalidFormat(format!(
179 "Entry '{}' is too large to read into memory ({} bytes). \
180 Use read_entry_streaming() for entries larger than 2 GiB.",
181 entry.name, data_size
182 )));
183 }
184
185 let mut compressed_data = vec![0u8; data_size as usize];
187 self.file.read_exact(&mut compressed_data)?;
188
189 #[cfg(feature = "encryption")]
191 let auth_code = if encryption_info.is_some() {
192 let mut ac = vec![0u8; 10];
193 self.file.read_exact(&mut ac)?;
194 Some(ac)
195 } else {
196 None
197 };
198
199 #[cfg(feature = "encryption")]
201 let decryptor_opt = if let Some((strength, salt, pw_verify)) = encryption_info {
202 let password = self.password.as_ref().ok_or_else(|| {
203 SZipError::InvalidFormat("Encrypted entry but no password set".to_string())
204 })?;
205
206 let mut decryptor = AesDecryptor::new(password, strength, &salt, &pw_verify)?;
208
209 decryptor.decrypt(&mut compressed_data)?;
211
212 Some(decryptor)
213 } else {
214 None
215 };
216
217 let data = if entry.compression_method == 8 {
219 let mut decoder = DeflateDecoder::new(&compressed_data[..]);
221 let mut decompressed = Vec::new();
222 decoder.read_to_end(&mut decompressed)?;
223 decompressed
224 } else if entry.compression_method == 0 {
225 compressed_data
227 } else if entry.compression_method == 93 {
228 #[cfg(feature = "zstd-support")]
230 {
231 zstd::decode_all(&compressed_data[..])?
232 }
233 #[cfg(not(feature = "zstd-support"))]
234 {
235 return Err(SZipError::UnsupportedCompression(entry.compression_method));
236 }
237 } else {
238 return Err(SZipError::UnsupportedCompression(entry.compression_method));
239 };
240
241 #[cfg(feature = "encryption")]
243 if let Some(mut decryptor) = decryptor_opt {
244 decryptor.update_hmac(&data);
246
247 if let Some(ac) = auth_code {
249 decryptor.verify_auth_code(&ac)?;
250 }
251 }
252
253 #[cfg(not(feature = "encryption"))]
257 {
258 let actual_crc = crc32fast::hash(&data);
259 if entry.crc32 != 0 && actual_crc != entry.crc32 {
260 return Err(SZipError::InvalidFormat(format!(
261 "CRC-32 mismatch for '{}': expected {:#010x}, got {:#010x}. \
262 The entry may be corrupt or the download may be incomplete.",
263 entry.name, entry.crc32, actual_crc
264 )));
265 }
266 }
267 #[cfg(feature = "encryption")]
268 if entry.crc32 != 0 && !entry.is_encrypted {
269 let actual_crc = crc32fast::hash(&data);
270 if actual_crc != entry.crc32 {
271 return Err(SZipError::InvalidFormat(format!(
272 "CRC-32 mismatch for '{}': expected {:#010x}, got {:#010x}. \
273 The entry may be corrupt or the download may be incomplete.",
274 entry.name, entry.crc32, actual_crc
275 )));
276 }
277 }
278
279 Ok(data)
280 }
281
282 pub fn read_entry_by_name(&mut self, name: &str) -> Result<Vec<u8>> {
284 let entry = self
285 .find_entry(name)
286 .ok_or_else(|| SZipError::EntryNotFound(name.to_string()))?
287 .clone();
288
289 self.read_entry(&entry)
290 }
291
292 pub fn read_entry_streaming_by_name(&mut self, name: &str) -> Result<Box<dyn Read + '_>> {
295 let entry = self
296 .find_entry(name)
297 .ok_or_else(|| SZipError::EntryNotFound(name.to_string()))?
298 .clone();
299
300 self.read_entry_streaming(&entry)
301 }
302
303 pub fn read_entry_streaming(&mut self, entry: &ZipEntry) -> Result<Box<dyn Read + '_>> {
324 self.file.seek(SeekFrom::Start(entry.offset))?;
326
327 let signature = self.read_u32_le()?;
329 if signature != LOCAL_FILE_HEADER_SIGNATURE {
330 return Err(SZipError::InvalidFormat(
331 "Invalid local file header signature".to_string(),
332 ));
333 }
334
335 self.file.seek(SeekFrom::Current(6))?;
337
338 self.file.seek(SeekFrom::Current(8))?;
340
341 self.file.seek(SeekFrom::Current(8))?;
343
344 let filename_len = self.read_u16_le()? as i64;
346 let extra_len = self.read_u16_le()? as usize;
347
348 self.file.seek(SeekFrom::Current(filename_len))?;
350
351 #[cfg(feature = "encryption")]
353 if entry.is_encrypted {
354 let encryption_info = self.parse_aes_extra_field(extra_len)?;
355
356 if let Some((strength, salt, pw_verify)) = encryption_info {
357 let password = self.password.as_ref().ok_or_else(|| {
358 SZipError::EncryptionError(
359 "Encrypted entry but no password set. Call set_password() first."
360 .to_string(),
361 )
362 })?;
363
364 let overhead = (strength.salt_size() + 2 + 10) as u64;
366 let cipher_size = entry.compressed_size.saturating_sub(overhead);
367
368 let current_pos = self.file.stream_position()?;
375 self.file.seek(SeekFrom::Start(current_pos + cipher_size))?;
376 let mut auth_code = vec![0u8; 10];
377 self.file.read_exact(&mut auth_code)?;
378
379 self.file.seek(SeekFrom::Start(current_pos))?;
381
382 let limited_reader = (&mut self.file).take(cipher_size);
383
384 use crate::decrypt_reader::sync::DecryptingReader;
385 let decrypt_reader = DecryptingReader::new(
386 limited_reader,
387 password,
388 strength,
389 &salt,
390 &pw_verify,
391 auth_code,
392 )?;
393
394 return if entry.compression_method == 8 {
396 Ok(Box::new(DeflateDecoder::new(decrypt_reader)))
397 } else if entry.compression_method == 0 {
398 Ok(Box::new(decrypt_reader))
399 } else {
400 Err(SZipError::UnsupportedCompression(entry.compression_method))
401 };
402 } else {
403 self.file.seek(SeekFrom::Current(extra_len as i64))?;
405 }
406 }
407
408 #[cfg(feature = "encryption")]
410 if !entry.is_encrypted {
411 self.file.seek(SeekFrom::Current(extra_len as i64))?;
412 }
413
414 #[cfg(not(feature = "encryption"))]
415 self.file.seek(SeekFrom::Current(extra_len as i64))?;
416
417 let limited_reader = (&mut self.file).take(entry.compressed_size);
419
420 if entry.compression_method == 8 {
422 Ok(Box::new(DeflateDecoder::new(limited_reader)))
424 } else if entry.compression_method == 0 {
425 Ok(Box::new(limited_reader))
427 } else if entry.compression_method == 93 {
428 #[cfg(feature = "zstd-support")]
430 {
431 Ok(Box::new(zstd::Decoder::new(limited_reader)?))
432 }
433 #[cfg(not(feature = "zstd-support"))]
434 {
435 Err(SZipError::UnsupportedCompression(entry.compression_method))
436 }
437 } else {
438 Err(SZipError::UnsupportedCompression(entry.compression_method))
439 }
440 }
441
442 pub fn read_entry_by_name_streaming(&mut self, name: &str) -> Result<Box<dyn Read + '_>> {
444 let entry = self
445 .find_entry(name)
446 .ok_or_else(|| SZipError::EntryNotFound(name.to_string()))?
447 .clone();
448
449 self.read_entry_streaming(&entry)
450 }
451
452 fn read_central_directory(file: &mut BufReader<File>) -> Result<Vec<ZipEntry>> {
454 let eocd_offset = Self::find_eocd(file)?;
456
457 file.seek(SeekFrom::Start(eocd_offset))?;
459
460 let signature = Self::read_u32_le_static(file)?;
462 if signature != END_OF_CENTRAL_DIRECTORY_SIGNATURE {
463 return Err(SZipError::InvalidFormat(format!(
464 "Invalid end of central directory signature: 0x{:08x}",
465 signature
466 )));
467 }
468
469 file.seek(SeekFrom::Current(4))?;
471
472 let _entries_on_disk = Self::read_u16_le_static(file)?;
474
475 let total_entries_16 = Self::read_u16_le_static(file)?;
479
480 let cd_size_32 = Self::read_u32_le_static(file)?;
482
483 let cd_offset_32 = Self::read_u32_le_static(file)? as u64;
485
486 let mut total_entries = total_entries_16 as usize;
488 let mut cd_offset = cd_offset_32;
489 let _cd_size = cd_size_32 as u64;
490
491 if total_entries_16 == 0xFFFF || cd_size_32 == 0xFFFFFFFF || cd_offset_32 == 0xFFFFFFFF {
492 let (zip64_total_entries, zip64_cd_size, zip64_cd_offset) =
494 Self::read_zip64_eocd(file, eocd_offset)?;
495 total_entries = zip64_total_entries as usize;
496 cd_offset = zip64_cd_offset;
497 let _ = zip64_cd_size;
499 }
500
501 file.seek(SeekFrom::Start(cd_offset))?;
503
504 let mut entries = Vec::with_capacity(total_entries);
506 for _ in 0..total_entries {
507 let signature = Self::read_u32_le_static(file)?;
508 if signature != CENTRAL_DIRECTORY_SIGNATURE {
509 break;
510 }
511
512 file.seek(SeekFrom::Current(4))?;
514
515 #[cfg_attr(not(feature = "encryption"), allow(unused_variables))]
517 let flags = Self::read_u16_le_static(file)?;
518
519 let compression_method = Self::read_u16_le_static(file)?;
520
521 file.seek(SeekFrom::Current(4))?; let crc32 = Self::read_u32_le_static(file)?;
524
525 let compressed_size_32 = Self::read_u32_le_static(file)? as u64;
527 let uncompressed_size_32 = Self::read_u32_le_static(file)? as u64;
528 let filename_len = Self::read_u16_le_static(file)? as usize;
529 let extra_len = Self::read_u16_le_static(file)? as usize;
530 let comment_len = Self::read_u16_le_static(file)? as usize;
531
532 file.seek(SeekFrom::Current(8))?;
534
535 let offset_32 = Self::read_u32_le_static(file)? as u64;
536
537 let mut filename_buf = vec![0u8; filename_len];
539 file.read_exact(&mut filename_buf)?;
540 let name = String::from_utf8_lossy(&filename_buf).to_string();
541
542 let mut extra_buf = vec![0u8; extra_len];
544 if extra_len > 0 {
545 file.read_exact(&mut extra_buf)?;
546 }
547
548 let (uncompressed_size, compressed_size, offset) = if compressed_size_32 == 0xFFFFFFFF
550 || uncompressed_size_32 == 0xFFFFFFFF
551 || offset_32 == 0xFFFFFFFF
552 {
553 parse_zip64_extra_field(
554 &extra_buf,
555 compressed_size_32,
556 uncompressed_size_32,
557 offset_32,
558 )
559 } else {
560 (uncompressed_size_32, compressed_size_32, offset_32)
561 };
562
563 if comment_len > 0 {
565 file.seek(SeekFrom::Current(comment_len as i64))?;
566 }
567
568 entries.push(ZipEntry {
569 name,
570 compressed_size,
571 uncompressed_size,
572 compression_method,
573 offset,
574 crc32,
575 is_encrypted: (flags & 0x01) != 0,
576 });
577 }
578
579 Ok(entries)
580 }
581
582 fn read_zip64_eocd(file: &mut BufReader<File>, eocd_offset: u64) -> Result<(u64, u64, u64)> {
584 let search_start = eocd_offset.saturating_sub(65557);
586 file.seek(SeekFrom::Start(search_start))?;
587 let mut buffer = Vec::new();
588 file.read_to_end(&mut buffer)?;
589
590 let zip64_eocd_offset = find_zip64_eocd_offset(&buffer)
591 .ok_or_else(|| SZipError::InvalidFormat("ZIP64 EOCD locator not found".to_string()))?;
592
593 file.seek(SeekFrom::Start(zip64_eocd_offset))?;
595
596 let sig = Self::read_u32_le_static(file)?;
597 if sig != ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE {
598 return Err(SZipError::InvalidFormat(format!(
599 "Invalid ZIP64 EOCD signature: 0x{:08x}",
600 sig
601 )));
602 }
603
604 let _size = {
606 let mut buf = [0u8; 8];
607 file.read_exact(&mut buf)?;
608 u64::from_le_bytes(buf)
609 };
610
611 file.seek(SeekFrom::Current(12))?;
613
614 let total_entries = {
616 let mut buf = [0u8; 8];
617 file.read_exact(&mut buf)?;
618 u64::from_le_bytes(buf)
619 };
620
621 {
623 let mut buf = [0u8; 8];
624 file.read_exact(&mut buf)?;
625 }
626
627 let cd_size = {
629 let mut buf = [0u8; 8];
630 file.read_exact(&mut buf)?;
631 u64::from_le_bytes(buf)
632 };
633
634 let cd_offset = {
636 let mut buf = [0u8; 8];
637 file.read_exact(&mut buf)?;
638 u64::from_le_bytes(buf)
639 };
640
641 Ok((total_entries, cd_size, cd_offset))
642 }
643
644 fn find_eocd(file: &mut BufReader<File>) -> Result<u64> {
646 let file_size = file.seek(SeekFrom::End(0))?;
647
648 let search_start = file_size.saturating_sub(65557);
650 file.seek(SeekFrom::Start(search_start))?;
651
652 let mut buffer = Vec::new();
653 file.read_to_end(&mut buffer)?;
654
655 find_eocd_in_buffer(&buffer, search_start).ok_or_else(|| {
656 SZipError::InvalidFormat("End of central directory not found".to_string())
657 })
658 }
659
660 fn read_u16_le(&mut self) -> Result<u16> {
661 let mut buf = [0u8; 2];
662 self.file.read_exact(&mut buf)?;
663 Ok(u16::from_le_bytes(buf))
664 }
665
666 fn read_u32_le(&mut self) -> Result<u32> {
667 let mut buf = [0u8; 4];
668 self.file.read_exact(&mut buf)?;
669 Ok(u32::from_le_bytes(buf))
670 }
671
672 fn read_u16_le_static(file: &mut BufReader<File>) -> Result<u16> {
673 let mut buf = [0u8; 2];
674 file.read_exact(&mut buf)?;
675 Ok(u16::from_le_bytes(buf))
676 }
677
678 fn read_u32_le_static(file: &mut BufReader<File>) -> Result<u32> {
679 let mut buf = [0u8; 4];
680 file.read_exact(&mut buf)?;
681 Ok(u32::from_le_bytes(buf))
682 }
683
684 #[cfg(feature = "encryption")]
686 #[allow(clippy::type_complexity)]
687 fn parse_aes_extra_field(
688 &mut self,
689 extra_len: usize,
690 ) -> Result<Option<(AesStrength, Vec<u8>, [u8; 2])>> {
691 if extra_len == 0 {
692 return Ok(None);
693 }
694
695 let mut extra_buf = vec![0u8; extra_len];
696 self.file.read_exact(&mut extra_buf)?;
697
698 let strength_code = match parse_aes_extra_field_buf(&extra_buf) {
700 Some(code) => code,
701 None => return Ok(None),
702 };
703
704 let strength = match strength_code {
705 0x03 => AesStrength::Aes256,
706 _ => {
707 return Err(SZipError::InvalidFormat(format!(
708 "Unsupported AES strength: {}",
709 strength_code
710 )))
711 }
712 };
713
714 let salt_size = strength.salt_size();
717
718 let mut salt = vec![0u8; salt_size];
719 self.file.read_exact(&mut salt)?;
720
721 let mut pw_verify = [0u8; 2];
722 self.file.read_exact(&mut pw_verify)?;
723
724 Ok(Some((strength, salt, pw_verify)))
725 }
726}