1use crate::compression::CompressionMethod;
4use crate::format::blocks::{FixedSizeBlock, ZipCentralEntryBlock};
5use crate::format::find_central_directory_end;
6use crate::read::config::Config;
7use crate::read::extract::RootDirFilter;
8use crate::read::readers::{ZipFileReader, ZipFileSeekReader};
9use crate::read::zipfile::{ZipFileEntry, ZipFileEntryWithDataDescriptor};
10use crate::read::{
11 ArchiveOffset, CentralDirectoryInfo, ZipFile, ZipFileSeek, ZipReadOptions,
12 central_header_to_zip_file_inner,
13};
14use crate::result::{ZipError, ZipResult};
15use crate::types::ZipFileData;
16use crate::unstable::path_to_string;
17use core::ops::Range;
18use indexmap::IndexMap;
19use std::borrow::Cow;
20use std::io::{Read, Seek, SeekFrom};
21use std::path::{Path, PathBuf};
22use std::sync::Arc;
23#[derive(Debug)]
25pub struct ZipArchiveMetadata {
26 pub(crate) files: IndexMap<Box<[u8]>, ZipFileData>,
27 pub(crate) offset: u64,
28 pub(crate) dir_start: u64,
29 #[allow(dead_code)]
31 pub(crate) config: Config,
32 pub(crate) comment: Box<[u8]>,
33 pub(crate) zip64_extensible_data_sector: Option<Box<[u8]>>,
34}
35
36impl ZipArchiveMetadata {
37 #[must_use]
39 pub fn len(&self) -> usize {
40 self.files.len()
41 }
42
43 #[must_use]
45 pub fn is_empty(&self) -> bool {
46 self.len() == 0
47 }
48
49 pub fn entry(&self, index: usize) -> ZipResult<ZipFileEntry<'_>> {
51 let (file_name_raw, data) = self.files.get_index(index).ok_or(ZipError::FileNotFound)?;
52 Ok(ZipFileEntry {
53 file_name_raw: Cow::Borrowed(file_name_raw),
54 data: Cow::Borrowed(data),
55 })
56 }
57}
58
59#[derive(Debug)]
60pub(crate) struct SharedBuilder {
61 pub(crate) files: Vec<(Box<[u8]>, ZipFileData)>,
62 pub(super) offset: u64,
63 pub(super) dir_start: u64,
64 #[allow(dead_code)]
66 pub(super) config: Config,
67}
68
69impl SharedBuilder {
70 pub(crate) fn build(
71 self,
72 comment: Box<[u8]>,
73 zip64_extensible_data_sector: Option<Box<[u8]>>,
74 ) -> ZipArchiveMetadata {
75 let mut index_map = IndexMap::with_capacity(self.files.len());
76 self.files.into_iter().for_each(|(file_name_raw, file)| {
77 index_map.insert(file_name_raw, file);
78 });
79 ZipArchiveMetadata {
80 files: index_map,
81 offset: self.offset,
82 dir_start: self.dir_start,
83 config: self.config,
84 comment,
85 zip64_extensible_data_sector,
86 }
87 }
88}
89
90#[derive(Clone, Debug)]
112pub struct ZipArchive<R> {
113 pub(super) reader: R,
114 pub(crate) shared: Arc<ZipArchiveMetadata>,
115}
116
117impl<R> ZipArchive<R> {
118 pub(crate) fn from_finalized_writer(
119 files: IndexMap<Box<[u8]>, ZipFileData>,
120 comment: Box<[u8]>,
121 zip64_extensible_data_sector: Option<Box<[u8]>>,
122 reader: R,
123 central_start: u64,
124 ) -> Self {
125 let initial_offset = match files.first() {
126 Some((_, file)) => file.header_start,
127 None => central_start,
128 };
129 let shared = Arc::new(ZipArchiveMetadata {
130 files,
131 offset: initial_offset,
132 dir_start: central_start,
133 config: Config {
134 archive_offset: ArchiveOffset::Known(initial_offset),
135 },
136 comment,
137 zip64_extensible_data_sector,
138 });
139 Self { reader, shared }
140 }
141
142 pub fn decompressed_size(&self) -> Option<u128> {
145 let mut total = 0u128;
146 for file in self.shared.files.values() {
147 if file.is_using_data_descriptor() {
148 return None;
149 }
150 total = total.checked_add(u128::from(file.uncompressed_size))?;
151 }
152 Some(total)
153 }
154}
155
156impl<R: Read + Seek> ZipArchive<R> {
157 pub(crate) fn get_metadata(config: Config, reader: &mut R) -> ZipResult<ZipArchiveMetadata> {
160 let file_len = reader.seek(SeekFrom::End(0))?;
162 let mut end_exclusive = file_len;
163 let mut last_err = None;
164
165 loop {
166 let cde = match find_central_directory_end(
168 reader,
169 config.archive_offset,
170 end_exclusive,
171 file_len,
172 ) {
173 Ok(cde) => cde,
174 Err(e) => {
175 return Err(last_err.unwrap_or(e));
177 }
178 };
179
180 match CentralDirectoryInfo::try_from(&cde)
182 .and_then(|info| Self::read_central_header(&info, config, reader))
183 {
184 Ok(shared) => {
185 let zip64_extensible_data_sector = if let Some(eocd64) = cde.eocd64 {
186 eocd64.data.zip64_extensible_data_sector
187 } else {
188 None
189 };
190 return Ok(
191 shared.build(cde.eocd.data.zip_file_comment, zip64_extensible_data_sector)
192 );
193 }
194 Err(e) => {
195 last_err = Some(e);
196 }
197 }
198 end_exclusive = cde.eocd.position;
200 continue;
201 }
202 }
203
204 fn read_central_header(
205 dir_info: &CentralDirectoryInfo,
206 config: Config,
207 reader: &mut R,
208 ) -> Result<SharedBuilder, ZipError> {
209 let file_capacity = if dir_info.number_of_files > dir_info.directory_start as usize {
212 0
213 } else {
214 dir_info.number_of_files
215 };
216
217 if dir_info.disk_number != dir_info.disk_with_central_directory {
218 return Err(ZipError::UnsupportedArchive(
219 "Support for multi-disk files is not implemented",
220 ));
221 }
222
223 if file_capacity.saturating_mul(size_of::<ZipFileData>()) > isize::MAX as usize {
224 return Err(ZipError::UnsupportedArchive("Oversized central directory"));
225 }
226
227 let mut files = Vec::with_capacity(file_capacity);
228 reader.seek(SeekFrom::Start(dir_info.directory_start))?;
229 for _ in 0..dir_info.number_of_files {
230 let central_header_start = reader.stream_position()?;
231
232 let block = ZipCentralEntryBlock::parse(reader)?;
234
235 let (file, file_name_raw) = central_header_to_zip_file_inner(
236 reader,
237 dir_info.archive_offset,
238 central_header_start,
239 block,
240 )?;
241 files.push((file_name_raw.into(), file));
242 }
243
244 Ok(SharedBuilder {
245 files,
246 offset: dir_info.archive_offset,
247 dir_start: dir_info.directory_start,
248 config,
249 })
250 }
251
252 #[cfg(feature = "aes-crypto")]
260 pub fn get_aes_verification_key_and_salt(
261 &mut self,
262 file_number: usize,
263 ) -> ZipResult<Option<crate::aes::AesInfo>> {
264 let (_, data) = self
265 .shared
266 .files
267 .get_index(file_number)
268 .ok_or(ZipError::FileNotFound)?;
269
270 let limit_reader = data.find_content(&mut self.reader)?;
271 match data.aes_settings() {
272 None => Ok(None),
273 Some((aes_mode, _)) => {
274 let (verification_value, salt) =
275 crate::aes::AesReader::new(limit_reader, aes_mode, data.compressed_size)?
276 .get_verification_value_and_salt()?;
277 let aes_info = crate::aes::AesInfo {
278 aes_mode,
279 verification_value,
280 salt,
281 };
282 Ok(Some(aes_info))
283 }
284 }
285 }
286
287 pub fn new(reader: R) -> ZipResult<ZipArchive<R>> {
293 Self::with_config(Config::default(), reader)
294 }
295
296 pub fn metadata(&self) -> Arc<ZipArchiveMetadata> {
301 self.shared.clone()
302 }
303
304 pub unsafe fn unsafe_new_with_metadata(reader: R, metadata: Arc<ZipArchiveMetadata>) -> Self {
341 Self {
342 reader,
343 shared: metadata,
344 }
345 }
346
347 pub fn with_config(config: Config, mut reader: R) -> ZipResult<ZipArchive<R>> {
351 let shared = Self::get_metadata(config, &mut reader)?;
352
353 Ok(ZipArchive {
354 reader,
355 shared: Arc::new(shared),
356 })
357 }
358
359 pub fn len(&self) -> usize {
361 self.shared.len()
362 }
363
364 pub fn central_directory_start(&self) -> u64 {
366 self.shared.dir_start
367 }
368
369 pub fn is_empty(&self) -> bool {
371 self.len() == 0
372 }
373
374 pub fn offset(&self) -> u64 {
379 self.shared.offset
380 }
381
382 pub fn comment(&self) -> &[u8] {
384 &self.shared.comment
385 }
386
387 pub fn raw_zip64_extensible_data_sector(&self) -> Option<&[u8]> {
389 self.shared.zip64_extensible_data_sector.as_deref()
390 }
391
392 pub fn file_names(&self) -> impl Iterator<Item = ZipResult<Cow<'_, str>>> {
394 self.shared
395 .files
396 .iter()
397 .map(|(file_name_raw, data)| data.name(file_name_raw))
398 }
399
400 pub fn has_overlapping_files(&mut self) -> ZipResult<bool> {
411 self.has_overlap(false)
412 }
413
414 pub fn has_overlapping_entries(&mut self) -> ZipResult<bool> {
424 self.has_overlap(true)
425 }
426
427 fn has_overlap(&mut self, include_header: bool) -> ZipResult<bool> {
428 let mut ranges = Vec::<Range<u64>>::with_capacity(self.shared.files.len());
429 for file in self.shared.files.values() {
430 if !include_header && file.compressed_size == 0 {
431 continue;
432 }
433 let data_start = file.data_start(&mut self.reader)?;
434 let start = if include_header {
435 file.header_start
436 } else {
437 data_start
438 };
439 let end =
440 data_start
441 .checked_add(file.compressed_size)
442 .ok_or(ZipError::InvalidArchive(Cow::Borrowed(
443 "File data end offset overflows u64",
444 )))?;
445 ranges.push(start..end);
446 }
447 ranges.sort_unstable_by_key(|r| (r.start, r.end));
448 Ok(ranges.windows(2).any(|w| w[0].end > w[1].start))
449 }
450
451 pub fn by_name_decrypt(&mut self, name: &str, password: &[u8]) -> ZipResult<ZipFile<'_, R>> {
465 self.by_name_with_optional_password(name, Some(password))
466 }
467
468 pub fn by_name(&mut self, name: &str) -> ZipResult<ZipFile<'_, R>> {
470 self.by_name_with_optional_password(name, None)
471 }
472
473 #[inline]
475 pub fn index_for_name(&self, name: &str) -> Option<usize> {
476 self.shared.files.get_index_of(name.as_bytes())
477 }
478
479 pub fn by_index_with_data_descriptor(
484 &mut self,
485 index: usize,
486 ) -> ZipResult<ZipFileEntryWithDataDescriptor<'_>> {
487 let (file_name_raw, data) = self
488 .shared
489 .files
490 .get_index(index)
491 .ok_or(ZipError::FileNotFound)?;
492
493 let data_descriptor = data.get_data_descriptor(&mut self.reader)?;
494 Ok(ZipFileEntryWithDataDescriptor {
495 file_name_raw: Cow::Borrowed(file_name_raw),
496 data: Cow::Borrowed(data),
497 data_descriptor,
498 })
499 }
500
501 pub fn by_path_decrypt<T: AsRef<Path>>(
515 &mut self,
516 path: T,
517 password: &[u8],
518 ) -> ZipResult<ZipFile<'_, R>> {
519 self.index_for_path(path)
520 .ok_or(ZipError::FileNotFound)
521 .and_then(|index| {
522 self.by_index_with_options(index, ZipReadOptions::new().password(Some(password)))
523 })
524 }
525
526 pub fn by_path<T: AsRef<Path>>(&mut self, path: T) -> ZipResult<ZipFile<'_, R>> {
528 self.index_for_path(path)
529 .ok_or(ZipError::FileNotFound)
530 .and_then(|index| self.by_index_with_options(index, ZipReadOptions::new()))
531 }
532
533 #[inline(always)]
535 pub fn index_for_path<T: AsRef<Path>>(&self, path: T) -> Option<usize> {
536 let path_as_string = &path_to_string(path).ok()?;
537 self.index_for_name(path_as_string)
538 }
539
540 #[inline(always)]
542 pub fn name_for_index(&self, index: usize) -> Option<ZipResult<Cow<'_, str>>> {
543 self.shared
544 .files
545 .get_index(index)
546 .map(|(file_name_raw, zip_file_data)| zip_file_data.name(file_name_raw))
547 }
548
549 pub fn by_name_seek(&mut self, name: &str) -> ZipResult<ZipFileSeek<'_, R>> {
551 self.by_index_seek(self.index_for_name(name).ok_or(ZipError::FileNotFound)?)
552 }
553
554 pub fn by_index_seek(&mut self, index: usize) -> ZipResult<ZipFileSeek<'_, R>> {
556 let reader = &mut self.reader;
557 self.shared
558 .files
559 .get_index(index)
560 .ok_or(ZipError::FileNotFound)
561 .and_then(move |(file_name_raw, data)| {
562 let seek_reader = match data.compression_method {
563 CompressionMethod::Stored => {
564 ZipFileSeekReader::Raw(data.find_content_seek(reader)?)
565 }
566 _ => {
567 return Err(ZipError::UnsupportedArchive(
568 "Seekable compressed files are not yet supported",
569 ));
570 }
571 };
572 Ok(ZipFileSeek {
573 file_name_raw: Cow::Borrowed(file_name_raw),
574 reader: seek_reader,
575 data: Cow::Borrowed(data),
576 })
577 })
578 }
579
580 fn by_name_with_optional_password<'a>(
581 &'a mut self,
582 name: &str,
583 password: Option<&[u8]>,
584 ) -> ZipResult<ZipFile<'a, R>> {
585 let Some(index) = self.shared.files.get_index_of(name.as_bytes()) else {
586 return Err(ZipError::FileNotFound);
587 };
588 self.by_index_with_options(index, ZipReadOptions::new().password(password))
589 }
590
591 pub fn by_index_decrypt(
605 &mut self,
606 file_number: usize,
607 password: &[u8],
608 ) -> ZipResult<ZipFile<'_, R>> {
609 self.by_index_with_options(file_number, ZipReadOptions::new().password(Some(password)))
610 }
611
612 pub fn by_index(&mut self, file_number: usize) -> ZipResult<ZipFile<'_, R>> {
614 self.by_index_with_options(file_number, ZipReadOptions::new())
615 }
616
617 pub fn by_index_data(&self, file_number: usize) -> ZipResult<ZipFileEntry<'_>> {
619 self.shared.entry(file_number)
620 }
621
622 pub fn by_index_raw(&mut self, file_number: usize) -> ZipResult<ZipFile<'_, R>> {
624 let reader = &mut self.reader;
625 let (file_name_raw, data) = self
626 .shared
627 .files
628 .get_index(file_number)
629 .ok_or(ZipError::FileNotFound)?;
630 Ok(ZipFile {
631 file_name_raw: Cow::Borrowed(file_name_raw),
632 reader: ZipFileReader::Raw(data.find_content(reader)?),
633 data: Cow::Borrowed(data),
634 })
635 }
636
637 pub fn by_index_with_options(
639 &mut self,
640 file_number: usize,
641 options: ZipReadOptions<'_>,
642 ) -> ZipResult<ZipFile<'_, R>> {
643 let (file_name_raw, data) = self
644 .shared
645 .files
646 .get_index(file_number)
647 .ok_or(ZipError::FileNotFound)?;
648 let limit_reader = data.find_content(&mut self.reader)?;
649 let reader = data.make_reader(limit_reader, options)?;
650 Ok(ZipFile {
651 file_name_raw: Cow::Borrowed(file_name_raw),
652 data: Cow::Borrowed(data),
653 reader,
654 })
655 }
656
657 pub fn root_dir(&self, filter: impl RootDirFilter) -> ZipResult<Option<PathBuf>> {
668 let mut root_dir: Option<PathBuf> = None;
669
670 for i in 0..self.len() {
671 let (file_name_raw, file) = self
672 .shared
673 .files
674 .get_index(i)
675 .ok_or(ZipError::FileNotFound)?;
676 let file_name = match file.name(file_name_raw) {
677 Ok(file_name) => file_name,
678 Err(_) => continue,
679 };
680 let path = match file.enclosed_name(&file_name) {
681 Some(path) => path,
682 None => return Ok(None),
683 };
684
685 if !filter(&path) {
686 continue;
687 }
688
689 macro_rules! replace_root_dir {
690 ($path:ident) => {
691 match &mut root_dir {
692 Some(root_dir) => {
693 if *root_dir != $path {
694 return Ok(None);
697 }
698 continue;
699 }
700
701 None => {
702 root_dir = Some($path.into());
703 continue;
704 }
705 }
706 };
707 }
708
709 if path.components().count() == 1 {
711 if file.is_dir(file_name_raw.as_ref()) {
712 replace_root_dir!(path);
714 }
715 return Ok(None);
718 }
719
720 let mut path = path.as_path();
722 while let Some(parent) = path.parent().filter(|path| *path != Path::new("")) {
723 path = parent;
724 }
725
726 replace_root_dir!(path);
727 }
728
729 Ok(root_dir)
730 }
731
732 pub fn into_inner(self) -> R {
736 self.reader
737 }
738}
739
740#[cfg(test)]
741mod tests {
742 #[test]
743 fn invalid_offset() {
744 use super::ZipArchive;
745 use std::io::Cursor;
746
747 let reader = ZipArchive::new(Cursor::new(include_bytes!(
748 "../../tests/data/invalid_offset.zip"
749 )));
750 assert!(reader.is_err());
751 }
752
753 #[test]
754 fn invalid_offset2() {
755 use super::ZipArchive;
756 use std::io::Cursor;
757
758 let reader = ZipArchive::new(Cursor::new(include_bytes!(
759 "../../tests/data/invalid_offset2.zip"
760 )));
761 assert!(reader.is_err());
762 }
763
764 #[test]
765 fn zip64_with_leading_junk() {
766 use super::ZipArchive;
767 use std::io::Cursor;
768
769 let reader = ZipArchive::new(Cursor::new(include_bytes!(
770 "../../tests/data/zip64_demo.zip"
771 )))
772 .unwrap();
773 assert_eq!(reader.len(), 1);
774 }
775
776 #[test]
777 fn zip_contents() {
778 use super::ZipArchive;
779 use std::io::Cursor;
780
781 let mut reader =
782 ZipArchive::new(Cursor::new(include_bytes!("../../tests/data/mimetype.zip"))).unwrap();
783 assert_eq!(reader.comment(), b"");
784 assert_eq!(reader.by_index(0).unwrap().central_header_start(), 77);
785 }
786
787 #[test]
788 fn metadata_entry_matches_by_index() {
789 use super::ZipArchive;
790 use crate::result::ZipError;
791 use std::io::Cursor;
792
793 let mut reader = ZipArchive::new(Cursor::new(include_bytes!(
794 "../../tests/data/files_and_dirs.zip"
795 )))
796 .unwrap();
797 let metadata = reader.metadata();
798 assert_eq!(metadata.len(), reader.len());
799 assert!(!metadata.is_empty());
800
801 for i in 0..metadata.len() {
802 let entry = metadata.entry(i).unwrap();
803 let file = reader.by_index(i).unwrap();
804 assert_eq!(entry.name_raw(), file.name_raw());
805 assert_eq!(entry.size(), file.size());
806 assert_eq!(entry.compressed_size(), file.compressed_size());
807 assert_eq!(entry.central_header_start(), file.central_header_start());
808 }
809
810 assert!(matches!(
811 metadata.entry(metadata.len()),
812 Err(ZipError::FileNotFound)
813 ));
814 }
815
816 #[test]
817 fn metadata_empty_archive() {
818 use super::ZipArchive;
819 use crate::result::ZipError;
820 use crate::write::ZipWriter;
821 use std::io::Cursor;
822
823 let writer = ZipWriter::new(Cursor::new(Vec::new()));
824 let reader = ZipArchive::new(writer.finish().unwrap()).unwrap();
825 let metadata = reader.metadata();
826 assert_eq!(metadata.len(), 0);
827 assert!(metadata.is_empty());
828 assert!(matches!(metadata.entry(0), Err(ZipError::FileNotFound)));
829 }
830
831 #[test]
832 fn zip_clone() {
833 use super::ZipArchive;
834 use std::io::Cursor;
835 use std::io::Read;
836
837 let mut reader1 =
838 ZipArchive::new(Cursor::new(include_bytes!("../../tests/data/mimetype.zip"))).unwrap();
839 let mut reader2 = reader1.clone();
840
841 let mut file1 = reader1.by_index(0).unwrap();
842 let mut file2 = reader2.by_index(0).unwrap();
843
844 let t = file1.last_modified().unwrap();
845 assert_eq!(
846 (
847 t.year(),
848 t.month(),
849 t.day(),
850 t.hour(),
851 t.minute(),
852 t.second()
853 ),
854 (1980, 1, 1, 0, 0, 0)
855 );
856
857 let mut buf1 = [0; 5];
858 let mut buf2 = [0; 5];
859 let mut buf3 = [0; 5];
860 let mut buf4 = [0; 5];
861
862 file1.read_exact(&mut buf1).unwrap();
863 file2.read_exact(&mut buf2).unwrap();
864 file1.read_exact(&mut buf3).unwrap();
865 file2.read_exact(&mut buf4).unwrap();
866
867 assert_eq!(buf1, buf2);
868 assert_eq!(buf3, buf4);
869 assert_ne!(buf1, buf3);
870 }
871
872 #[test]
873 fn file_and_dir_predicates() {
874 use super::ZipArchive;
875 use std::io::Cursor;
876
877 let mut zip = ZipArchive::new(Cursor::new(include_bytes!(
878 "../../tests/data/files_and_dirs.zip"
879 )))
880 .unwrap();
881
882 for i in 0..zip.len() {
883 let zip_file = zip.by_index(i).unwrap();
884 let full_name = zip_file.enclosed_name().unwrap();
885 let file_name = full_name.file_name().unwrap().to_str().unwrap();
886 assert!(
887 (file_name.starts_with("dir") && zip_file.is_dir())
888 || (file_name.starts_with("file") && zip_file.is_file())
889 );
890 }
891 }
892
893 #[test]
894 fn zip64_magic_in_filenames() {
895 use super::ZipArchive;
896 use std::io::Cursor;
897
898 let files = vec![
899 include_bytes!("../../tests/data/zip64_magic_in_filename_1.zip").to_vec(),
900 include_bytes!("../../tests/data/zip64_magic_in_filename_2.zip").to_vec(),
901 include_bytes!("../../tests/data/zip64_magic_in_filename_3.zip").to_vec(),
902 include_bytes!("../../tests/data/zip64_magic_in_filename_4.zip").to_vec(),
903 include_bytes!("../../tests/data/zip64_magic_in_filename_5.zip").to_vec(),
904 ];
905 for file in files {
908 ZipArchive::new(Cursor::new(file)).unwrap();
909 }
910 }
911
912 #[test]
916 fn invalid_cde_number_of_files_allocation_smaller_offset() {
917 use super::ZipArchive;
918 use std::io::Cursor;
919
920 let reader = ZipArchive::new(Cursor::new(include_bytes!(
921 "../../tests/data/invalid_cde_number_of_files_allocation_smaller_offset.zip"
922 )));
923 assert!(reader.is_err() || reader.unwrap().is_empty());
924 }
925
926 #[test]
930 fn invalid_cde_number_of_files_allocation_greater_offset() {
931 use super::ZipArchive;
932 use std::io::Cursor;
933
934 let reader = ZipArchive::new(Cursor::new(include_bytes!(
935 "../../tests/data/invalid_cde_number_of_files_allocation_greater_offset.zip"
936 )));
937 assert!(reader.is_err());
938 }
939
940 #[cfg(feature = "deflate64")]
941 #[test]
942 fn deflate64_index_out_of_bounds() {
943 use super::ZipArchive;
944 use std::io::Cursor;
945
946 let mut reader = ZipArchive::new(Cursor::new(include_bytes!(
947 "../../tests/data/raw_deflate64_index_out_of_bounds.zip"
948 )))
949 .unwrap();
950 let mut file = reader.by_index(0).unwrap();
951 std::io::copy(&mut file, &mut std::io::sink()).expect_err("Invalid file");
952 }
953
954 #[cfg(feature = "deflate64")]
955 #[test]
956 fn deflate64_not_enough_space() {
957 use super::ZipArchive;
958 use std::io::Cursor;
959
960 ZipArchive::new(Cursor::new(include_bytes!(
961 "../../tests/data/deflate64_issue_25.zip"
962 )))
963 .expect_err("Invalid file");
964 }
965
966 #[cfg(feature = "deflate-flate2")]
967 #[test]
968 fn test_read_with_data_descriptor() {
969 use super::ZipArchive;
970 use std::io::Cursor;
971 use std::io::Read;
972
973 let mut reader = ZipArchive::new(Cursor::new(include_bytes!(
974 "../../tests/data/data_descriptor.zip"
975 )))
976 .unwrap();
977 let mut decompressed = [0u8; 16];
978 let mut file = reader.by_index(0).unwrap();
979 assert_eq!(file.read(&mut decompressed).unwrap(), 12);
980 }
981
982 #[cfg(all(target_endian = "little", not(miri)))]
984 #[test]
985 fn test_central_directory_not_at_end() {
986 use super::ZipArchive;
987 use std::io::Cursor;
988 use std::io::Read;
989
990 let mut reader =
991 ZipArchive::new(Cursor::new(include_bytes!("../../tests/data/omni.ja"))).unwrap();
992 let mut file = reader.by_name("chrome.manifest").unwrap();
993 let mut contents = String::new();
994 file.read_to_string(&mut contents).unwrap(); assert!(!contents.is_empty(), "chrome.manifest should not be empty");
996 drop(file);
997 for i in 0..reader.len() {
998 let mut file = reader.by_index(i).unwrap();
999 let mut buffer = Vec::new();
1001 file.read_to_end(&mut buffer).unwrap();
1002 let file_name = file.name().unwrap();
1003 assert_eq!(
1004 buffer.len(),
1005 file.size() as usize,
1006 "File size mismatch for {}",
1007 file_name
1008 );
1009 }
1010 }
1011
1012 #[test]
1013 fn test_ignore_encryption_flag() {
1014 use super::ZipArchive;
1015 use crate::ZipReadOptions;
1016 use std::io::Cursor;
1017 use std::io::Read;
1018
1019 let mut reader = ZipArchive::new(Cursor::new(include_bytes!(
1020 "../../tests/data/ignore_encryption_flag.zip"
1021 )))
1022 .unwrap();
1023
1024 let mut file = reader
1026 .by_index_with_options(0, ZipReadOptions::new().ignore_encryption_flag(true))
1027 .unwrap();
1028 let mut contents = String::new();
1029 assert_eq!(file.name().unwrap(), "plaintext.txt");
1030
1031 assert!(file.encrypted());
1033 file.read_to_string(&mut contents).unwrap(); assert_eq!(contents, "This file is not encrypted.\n");
1035 }
1036}