use std::io::SeekFrom;
use futures_lite::AsyncBufRead;
use futures_lite::AsyncSeekExt;
use futures_lite::AsyncRead;
use futures_lite::AsyncSeek;
#[cfg(feature = "tracing")]
use tracing::{instrument, trace};
use crate::base::read1::loc::Method0;
use crate::base::read1::opts::ZipLocateMethod;
use crate::base::read1::opts::ZipOptions;
use crate::base::read1::seek::ZipArchiveInner;
use crate::error::ZipError;
use crate::spec::constructs::CEOCDR;
use crate::spec::headers1::CDRH;
use crate::spec::constructs::{CDR, LF, EOCDR};
use crate::spec::headers1::EOCDR64H;
use crate::spec::headers1::EOCDRH;
use crate::spec::KnownSize;
use crate::spec::headers1::EOCDL64H;
use crate::{error::Result, spec::headers1::{LFH, Signature}};
pub(crate) struct Ops<'o, R> {
options: &'o ZipOptions,
reader: R,
}
impl<'o, R: AsyncRead + Unpin> Ops<'o, R> {
pub fn new(reader: R, options: &'o ZipOptions) -> Self {
Self { reader, options }
}
#[cfg_attr(feature = "tracing", instrument(skip(self), level = "trace"))]
pub async fn assert_signature(&mut self, expected: Signature) -> Result<()> {
let signature = crate::spec::headers1::read::<Signature, R>(&mut self.reader).await?;
#[cfg(feature = "tracing")]
trace!("read signature: {:02X?}", signature);
if signature != expected {
return Err(crate::error::ZipError::UnexpectedHeaderError(signature.into(), expected.into()));
}
Ok(())
}
#[cfg_attr(feature = "tracing", instrument(skip(self), level = "trace"))]
pub async fn lf(&mut self) -> Result<LF> {
self.assert_signature(Signature::LFH).await?;
let options = self.options;
let lf = crate::spec::headers1::read_record::<LFH, LF, R>(&mut self.reader, |lfh| {
crate::base::read1::valid::validate_extra_field_size(lfh.extra_field_length, options)?;
Ok(usize::from(lfh.file_name_length) + usize::from(lfh.extra_field_length))
})
.await?;
crate::base::read1::valid::validate_extra_field_num(&lf.efs, options)?;
Ok(lf)
}
#[cfg_attr(feature = "tracing", instrument(skip(self), level = "trace"))]
pub async fn cdr(&mut self, assert_signature: bool) -> Result<CDR> {
if assert_signature {
self.assert_signature(Signature::CDRH).await?;
}
let options = self.options;
let cdr = crate::spec::headers1::read_record::<CDRH, CDR, R>(&mut self.reader, |cdrh| {
crate::base::read1::valid::validate_extra_field_size(cdrh.extra_field_length, options)?;
Ok(usize::from(cdrh.file_name_length)
+ usize::from(cdrh.extra_field_length)
+ usize::from(cdrh.file_comment_length))
}).await?;
crate::base::read1::valid::validate_extra_field_num(&cdr.efs, options)?;
Ok(cdr)
}
#[cfg_attr(feature = "tracing", instrument(skip(self), level = "trace"))]
pub async fn eocdr(&mut self) -> Result<EOCDR> {
crate::spec::headers1::read_record::<EOCDRH, EOCDR, R>(&mut self.reader, |eocdrh| {
Ok(usize::from(eocdrh.comment_length))
}).await
}
}
pub(crate) struct SeekOps<R> {
reader: R,
}
impl<R: AsyncBufRead + AsyncSeek + Unpin> SeekOps<R> {
pub fn new(reader: R) -> Self {
Self { reader }
}
#[cfg_attr(feature = "tracing", instrument(skip(self), level = "trace"))]
pub async fn open(&mut self, opts: ZipOptions) -> Result<ZipArchiveInner> {
let eor = self.reader.seek(SeekFrom::End(0)).await?;
let eocdr_offset = match opts.eocdr_locate_method {
ZipLocateMethod::SeekBackReadLinearly => Method0::new(&mut self.reader).locate(eor).await?,
};
let offset = crate::base::read1::valid_offset(eocdr_offset + Signature::SIZE as u64, eor)?;
self.reader.seek(SeekFrom::Start(offset)).await?;
let eocdr = Ops::new(&mut self.reader, &opts).eocdr().await?;
let mut ceocdr = CEOCDR { eocdr, eocdr64: None, eocdl64: None };
if opts.validate_eor_is_eoa {
let current = self.reader.seek(SeekFrom::Current(0)).await?;
let end = self.reader.seek(SeekFrom::End(0)).await?;
if current != end {
return Err(ZipError::EORIsNotEOA);
}
}
if let Some((locator, record)) = SeekOps::new(&mut self.reader).zip64(eocdr_offset, eor, &opts).await? {
ceocdr.eocdr64 = Some(record);
ceocdr.eocdl64 = Some(locator);
}
let offset = crate::base::read1::valid_offset(ceocdr.cd_offset()?, eor)?;
self.reader.seek(SeekFrom::Start(offset)).await?;
crate::base::read1::valid::validate_archive(&ceocdr, &opts)?;
let (loaded_cdrs, offsets) = SeekOps::new(&mut self.reader).cd(&opts, &ceocdr).await?;
let inner = ZipArchiveInner {
loaded_cdrs,
cdr_offsets: offsets,
options: opts,
ceocdr,
eor,
};
Ok(inner)
}
pub async fn zip64(&mut self, eocdr_offset: u64, eor: u64, opts: &ZipOptions) -> Result<Option<(EOCDL64H, EOCDR64H)>> {
let zip64_locator_pos = eocdr_offset.saturating_sub(Signature::SIZE as u64);
let zip64_locator_pos = zip64_locator_pos.saturating_sub(EOCDL64H::SIZE as u64);
let offset = crate::base::read1::valid_offset(zip64_locator_pos, eor)?;
self.reader.seek(SeekFrom::Start(offset)).await?;
let signature = crate::spec::headers1::read::<Signature, R>(&mut self.reader).await;
match signature {
Err(ZipError::BinaryParseError(_)) => return Ok(None),
Err(err) => return Err(err),
Ok(Signature::EOCDL64H) => {},
Ok(_) => return Ok(None),
}
let eocdl64h = crate::spec::headers1::read::<EOCDL64H, R>(&mut self.reader).await?;
let offset = crate::base::read1::valid_offset(eocdl64h.relative_offset_of_eocdr64, eor)?;
self.reader.seek(SeekFrom::Start(offset)).await?;
Ops::new(&mut self.reader, opts).assert_signature(Signature::EOCDR64H).await?;
let eocdr64h = crate::spec::headers1::read::<EOCDR64H, R>(&mut self.reader).await?;
Ok(Some((eocdl64h, eocdr64h)))
}
#[cfg_attr(feature = "tracing", instrument(skip(self), level = "trace"))]
pub async fn cd(&mut self, options: &ZipOptions, ceocdr: &CEOCDR) -> Result<(Vec<CDR>, Vec<u64>)> {
let cdrs_capacity = ceocdr.num_entries()?.min(options.max_cd_num_files_load);
let offsets_capacity = ceocdr.num_entries()?.min(options.max_cd_num_files);
let load_cdrs = ceocdr.num_entries()? <= options.max_cd_num_files_load;
let mut offsets = Vec::with_capacity(offsets_capacity as usize);
let mut cdrs = Vec::with_capacity(cdrs_capacity as usize);
let mut offset = ceocdr.cd_offset()?;
loop {
let signature = crate::spec::headers1::read::<Signature, R>(&mut self.reader).await?;
if signature != Signature::CDRH {
break;
}
offsets.push(offset);
let cdr = Ops::new(&mut self.reader, options).cdr(false).await?;
if load_cdrs {
cdrs.push(cdr);
}
offset = self.reader.seek(SeekFrom::Current(0)).await?;
}
let num_matched = offsets.len() == ceocdr.num_entries()? as usize;
if options.validate_num_cd_files && !num_matched {
return Err(crate::error::ZipError::NumFilesMismatch(offsets.len() as u64, ceocdr.num_entries()?));
}
Ok((cdrs, offsets))
}
#[cfg_attr(feature = "tracing", instrument(skip(self), level = "trace"))]
pub async fn file(&mut self, cdr: CDR, eor: u64, opts: &ZipOptions) -> Result<LF> {
let offset = crate::base::read1::valid_offset(cdr.lfh_offset()?, eor)?;
self.reader.seek(SeekFrom::Start(offset)).await?;
let mut lf = Ops::new(&mut self.reader, opts).lf().await?;
crate::base::read1::valid::validate_file(&lf, &cdr, opts)?;
if cdr.cdrh.gpf.data_descriptor() {
lf.lfh.compressed_size = cdr.cdrh.compressed_size;
lf.lfh.uncompressed_size = cdr.cdrh.uncompressed_size;
lf.lfh.crc = cdr.cdrh.crc;
lf.efs.retain(|ef| ef.efh.efid != crate::spec::extra::EFHID::EI64);
if let Some(zip64ei) = cdr.find_ef(crate::spec::extra::EFHID::EI64) {
lf.efs.push(zip64ei.clone());
}
}
Ok(lf)
}
}