use binrw::binrw;
use crate::{error::{Result, ZipError}, spec::{extra::{EF, EFD, EFHID, EI64, efs}, headers1::{CDRH, EOCDL64H, EOCDR64H, EOCDRH, LFH}, string::ZipString}};
#[binrw]
#[brw(little)]
#[derive(Debug)]
pub struct LF {
pub lfh: LFH,
#[br(count = lfh.file_name_length, args { utf8: lfh.gpf.language_encoding_flag() })]
pub insecure_file_name: ZipString,
#[br(parse_with = efs, args(lfh.extra_field_length.into()))]
pub efs: Vec<EF>,
}
impl LF {
pub fn uncompressed_size(&self) -> Result<u64> {
combined_accessor(self.lfh.uncompressed_size, &self.efs, |ei_data| ei_data.uncompressed_size)
}
pub fn compressed_size(&self) -> Result<u64> {
combined_accessor(self.lfh.compressed_size, &self.efs, |ei_data| ei_data.compressed_size)
}
}
fn combined_accessor(zip32: u32, extra_fields: &[EF], accessor: impl Fn(&EI64) -> u64) -> Result<u64> {
if zip32 != u32::MAX {
return Ok(zip32.into());
}
let zip64ei = extra_fields.iter().find(|field| {
matches!(field.efh.efid, EFHID::EI64)
});
if let Some(EF { efh: _, efd: EFD::EI64(data) }) = zip64ei {
return Ok(accessor(data));
}
return Err(ZipError::NoZip64ExtendedInformation);
}
fn combined_accessor_ecodr_u16(zip16: u16, ceocdr: &CEOCDR, accessor: impl Fn(&EOCDR64H) -> u64) -> Result<u64> {
if zip16 != u16::MAX {
return Ok(zip16.into());
}
if let Some(record) = &ceocdr.eocdr64 {
return Ok(accessor(record));
}
return Err(ZipError::NoZip64EOCDR);
}
fn combined_accessor_ecodr_u32(zip32: u32, ceocdr: &CEOCDR, accessor: impl Fn(&EOCDR64H) -> u64) -> Result<u64> {
if zip32 != u32::MAX {
return Ok(zip32.into());
}
if let Some(record) = &ceocdr.eocdr64 {
return Ok(accessor(record));
}
return Err(ZipError::NoZip64EOCDR);
}
fn combined_accessor_ecodr_disk(zip32: u16, ceocdr: &CEOCDR, accessor: impl Fn(&EOCDR64H) -> u32) -> Result<u32> {
if zip32 != u16::MAX {
return Ok(zip32.into());
}
if let Some(record) = &ceocdr.eocdr64 {
return Ok(accessor(record));
}
return Err(ZipError::NoZip64EOCDR);
}
#[binrw]
#[brw(little)]
#[derive(Clone, Debug)]
pub struct CDR {
pub cdrh: CDRH,
#[br(count = cdrh.file_name_length, args { utf8: cdrh.gpf.language_encoding_flag() })]
pub insecure_file_name: ZipString,
#[br(parse_with = efs, args(cdrh.extra_field_length.into()))]
pub efs: Vec<EF>,
#[br(count = cdrh.file_comment_length, args { utf8: cdrh.gpf.language_encoding_flag() })]
pub file_comment: ZipString,
}
impl CDR {
pub fn lfh_offset(&self) -> Result<u64> {
combined_accessor(self.cdrh.lfh_offset, &self.efs, |ei_data| ei_data.relative_offset.unwrap())
}
pub fn uncompressed_size(&self) -> Result<u64> {
combined_accessor(self.cdrh.uncompressed_size, &self.efs, |ei_data| ei_data.uncompressed_size)
}
pub fn compressed_size(&self) -> Result<u64> {
combined_accessor(self.cdrh.compressed_size, &self.efs, |ei_data| ei_data.compressed_size)
}
pub fn find_ef(&self, efid: EFHID) -> Option<&EF> {
self.efs.iter().find(|field| field.efh.efid == efid)
}
}
#[binrw]
#[brw(little)]
#[derive(Debug, Clone)]
pub struct EOCDR {
pub eocdrh: EOCDRH,
#[br(count = eocdrh.comment_length)]
pub file_comment: ZipString,
}
#[derive(Clone, Debug)]
pub struct CEOCDR {
pub eocdr: EOCDR,
pub eocdr64: Option<EOCDR64H>,
pub eocdl64: Option<EOCDL64H>,
}
impl CEOCDR {
pub fn is_zip64(&self) -> bool {
let xor1 = self.eocdl64.is_some() && self.eocdr64.is_none();
let xor2 = self.eocdl64.is_none() && self.eocdr64.is_some();
if xor1 || xor2 {
unreachable!("we should have returned an Err previously if we had an XOR situation");
}
self.eocdr64.is_some() && self.eocdl64.is_some()
}
pub fn cd_offset(&self) -> Result<u64> {
combined_accessor_ecodr_u32(self.eocdr.eocdrh.cd_offset, self, |record| record.cd_offset)
}
pub fn num_entries(&self) -> Result<u64> {
combined_accessor_ecodr_u16(self.eocdr.eocdrh.num_of_entries, self, |record| record.num_entries)
}
pub fn num_entries_on_disk(&self) -> Result<u64> {
combined_accessor_ecodr_u16(self.eocdr.eocdrh.num_of_entries_this_disk, self, |record| record.num_entries_this_disk)
}
pub fn cd_size(&self) -> Result<u64> {
combined_accessor_ecodr_u32(self.eocdr.eocdrh.cd_size, self, |record| record.cd_size)
}
pub fn disk_num(&self) -> Result<u32> {
combined_accessor_ecodr_disk(self.eocdr.eocdrh.disk_num, self, |record| record.disk_num)
}
pub fn disk_num_start(&self) -> Result<u32> {
combined_accessor_ecodr_disk(self.eocdr.eocdrh.disk_num_start_of_cd, self, |record| record.disk_num_start_of_cd)
}
}