use crate::format::MagicPositionCache;
use crate::format::blocks::{FixedSizeBlock, Zip32CentralDirectoryEnd, ZipCentralEntryBlock};
use crate::format::find_central_directory_end;
use crate::format::magic::Magic;
use crate::read::magic_finder::{Backwards, MagicFinder};
use crate::read::zipfile::ZipFileEntry;
use crate::read::{CentralDirectoryInfo, Config, central_header_to_zip_file_inner};
use crate::result::{ZipError, ZipResult, invalid};
use std::borrow::Cow;
use std::io::{Read, Seek, SeekFrom};
use std::mem::size_of;
#[derive(Debug)]
pub struct ZipIterable<R> {
files: ZipIterableFiles<R>,
}
impl<R: Read + Seek> ZipIterable<R> {
pub fn try_new(mut reader: R, config: Config) -> ZipResult<Self> {
let file_len = reader.seek(SeekFrom::End(0))?;
let min_eocd_position = file_len.saturating_sub(
(size_of::<Zip32CentralDirectoryEnd>() + size_of::<Magic>() + (1 << 16)) as u64,
);
const EOCD_SIG_BYTES: [u8; size_of::<Magic>()] =
Magic::CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes();
const EOCD64_SIG_BYTES: [u8; size_of::<Magic>()] =
Magic::ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes();
const CDFH_SIG_BYTES: [u8; size_of::<Magic>()] =
Magic::CENTRAL_DIRECTORY_HEADER_SIGNATURE.to_le_bytes();
let mut eocd_finder: MagicFinder<Backwards<'static>> =
MagicFinder::new(&EOCD_SIG_BYTES, min_eocd_position, file_len);
let mut cdfh_cache = MagicPositionCache::new(CDFH_SIG_BYTES);
let mut eocd64_cache = MagicPositionCache::new(EOCD64_SIG_BYTES);
let mut last_err = None;
let central_directory = loop {
let cde = match find_central_directory_end(
&mut reader,
config.archive_offset,
file_len,
&mut eocd_finder,
&mut cdfh_cache,
&mut eocd64_cache,
) {
Ok(cde) => cde,
Err(e) => return Err(last_err.unwrap_or(e)),
};
match CentralDirectoryInfo::try_from(&cde) {
Ok(info) => break info,
Err(e) => {
if cde.central_directory_abuts_end() {
return Err(e);
}
last_err = Some(e);
}
}
};
if central_directory.disk_number != central_directory.disk_with_central_directory {
return Err(ZipError::UnsupportedArchive(
"Support for multi-disk files is not implemented",
));
}
let max_entries =
usize::try_from(central_directory.directory_size / 46).unwrap_or(usize::MAX);
if central_directory.number_of_files > max_entries {
return Err(invalid!(
"Central directory declares more entries than its size allows"
));
}
Ok(Self {
files: ZipIterableFiles::new(reader, central_directory),
})
}
#[must_use]
pub fn len(&self) -> usize {
self.files.central_directory.number_of_files
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn files(&mut self) -> ZipResult<&mut ZipIterableFiles<R>> {
self.files.reset()?;
Ok(&mut self.files)
}
#[must_use]
pub fn enforce_header_consistency(mut self, enforce: bool) -> Self {
self.files.enforce_header_consistency = enforce;
self
}
}
#[derive(Debug)]
pub struct ZipIterableFiles<R> {
reader: R,
central_directory: CentralDirectoryInfo,
next_header_start: u64,
remaining: usize,
enforce_header_consistency: bool,
}
impl<R: Read + Seek> ZipIterableFiles<R> {
fn new(reader: R, central_directory: CentralDirectoryInfo) -> Self {
let next_header_start = central_directory.directory_start;
let remaining = central_directory.number_of_files;
Self {
reader,
central_directory,
next_header_start,
remaining,
enforce_header_consistency: false,
}
}
fn reset(&mut self) -> ZipResult<()> {
self.next_header_start = self.central_directory.directory_start;
self.remaining = self.central_directory.number_of_files;
self.reader.seek(SeekFrom::Start(self.next_header_start))?;
Ok(())
}
#[must_use]
pub fn len(&self) -> usize {
self.remaining
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.remaining == 0
}
pub fn reader_mut(&mut self) -> &mut R {
&mut self.reader
}
fn parse_entry(
&mut self,
central_header_start: u64,
) -> ZipResult<(ZipFileEntry<'static>, u64)> {
self.reader.seek(SeekFrom::Start(central_header_start))?;
let block = ZipCentralEntryBlock::parse(&mut self.reader)?;
let next = central_header_start
.checked_add((size_of::<Magic>() + size_of::<ZipCentralEntryBlock>()) as u64)
.and_then(|next| next.checked_add(u64::from(block.file_name_length)))
.and_then(|next| next.checked_add(u64::from(block.extra_field_length)))
.and_then(|next| next.checked_add(u64::from(block.file_comment_length)))
.ok_or_else(|| invalid!("Central directory entry offset overflows u64"))?;
let (data, file_name) = central_header_to_zip_file_inner(
&mut self.reader,
self.central_directory.archive_offset,
central_header_start,
block,
)?;
if self.enforce_header_consistency {
data.validate_header_consistency(&mut self.reader, &file_name)?;
}
Ok((
ZipFileEntry {
file_name_raw: Cow::Owned(file_name),
data: Cow::Owned(data),
},
next,
))
}
}
impl<R: Read + Seek> Iterator for ZipIterableFiles<R> {
type Item = ZipResult<ZipFileEntry<'static>>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
match self.parse_entry(self.next_header_start) {
Ok((entry, next)) => {
self.next_header_start = next;
self.remaining -= 1;
Some(Ok(entry))
}
Err(e) => {
self.remaining = 0;
Some(Err(e))
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}