use core::mem;
use std::io::{ErrorKind, Read, Write, copy, sink};
use crate::unstable::LittleEndianReadExt;
use crate::{
ZIP64_BYTES_THR,
extra_fields::UsedExtraField,
result::{ZipResult, invalid},
};
#[derive(Copy, Clone, Debug)]
pub(crate) struct Zip64ExtendedInformation {
is_local_header: bool,
uncompressed_size: Option<u64>,
compressed_size: Option<u64>,
header_start: Option<u64>,
}
impl Zip64ExtendedInformation {
const MAGIC: UsedExtraField = UsedExtraField::Zip64ExtendedInfo;
pub(crate) fn new_local(is_large_file: bool) -> Option<Self> {
if is_large_file {
Self::local_header(true, u64::MAX, u64::MAX)
} else {
None
}
}
pub(crate) fn local_header(
is_large_file: bool,
uncompressed_size: u64,
compressed_size: u64,
) -> Option<Self> {
let should_add_size = is_large_file
|| uncompressed_size >= ZIP64_BYTES_THR
|| compressed_size >= ZIP64_BYTES_THR;
if !should_add_size {
return None;
}
let uncompressed_size = Some(uncompressed_size);
let compressed_size = Some(compressed_size);
Some(Self {
is_local_header: true,
uncompressed_size,
compressed_size,
header_start: None,
})
}
pub(crate) fn central_header(
is_large_file: bool,
uncompressed_size: u64,
compressed_size: u64,
header_start: u64,
) -> Option<Self> {
let mut size: u16 = 0;
let uncompressed_size = if is_large_file || uncompressed_size >= ZIP64_BYTES_THR {
size += mem::size_of::<u64>() as u16;
Some(uncompressed_size)
} else {
None
};
let compressed_size = if is_large_file || compressed_size >= ZIP64_BYTES_THR {
size += mem::size_of::<u64>() as u16;
Some(compressed_size)
} else {
None
};
let header_start = if header_start != 0 && header_start >= ZIP64_BYTES_THR {
size += mem::size_of::<u64>() as u16;
Some(header_start)
} else {
None
};
if size == 0 {
return None;
}
Some(Self {
is_local_header: false,
uncompressed_size,
compressed_size,
header_start,
})
}
pub(crate) fn full_size(&self) -> usize {
self.size() + mem::size_of::<UsedExtraField>() + mem::size_of::<u16>()
}
pub(crate) fn size(&self) -> usize {
let mut size = 0;
if self.uncompressed_size.is_some() {
size += mem::size_of::<u64>();
}
if self.compressed_size.is_some() {
size += mem::size_of::<u64>();
}
if self.header_start.is_some() {
size += mem::size_of::<u64>();
}
size
}
pub fn write<T: Write>(self, writer: &mut T) -> ZipResult<()> {
writer.write_all(&Self::MAGIC.to_le_bytes())?;
if self.is_local_header {
if let (Some(uncompressed_size), Some(compressed_size)) =
(self.uncompressed_size, self.compressed_size)
{
let size = (mem::size_of::<u64>() + mem::size_of::<u64>()) as u16;
writer.write_all(&size.to_le_bytes())?;
writer.write_all(&u64::to_le_bytes(uncompressed_size))?;
writer.write_all(&u64::to_le_bytes(compressed_size))?;
}
} else {
let size = self.size() as u16;
writer.write_all(&size.to_le_bytes())?;
if let Some(uncompressed_size) = self.uncompressed_size {
writer.write_all(&u64::to_le_bytes(uncompressed_size))?;
}
if let Some(compressed_size) = self.compressed_size {
writer.write_all(&u64::to_le_bytes(compressed_size))?;
}
if let Some(header_start) = self.header_start {
writer.write_all(&u64::to_le_bytes(header_start))?;
}
}
Ok(())
}
#[inline]
pub(crate) fn parse<R: Read>(
reader: &mut R,
len: u16,
uncompressed_size: &mut u64,
compressed_size: &mut u64,
header_start: &mut u64,
) -> ZipResult<()> {
let mut consumed_len = 0;
if len >= 24 || *uncompressed_size == ZIP64_BYTES_THR {
*uncompressed_size = match reader.read_u64_le() {
Ok(v) => v,
Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
return Err(invalid!("ZIP64 extra field truncated"));
}
Err(e) => return Err(e.into()),
};
consumed_len += mem::size_of::<u64>();
}
if len >= 24 || *compressed_size == ZIP64_BYTES_THR {
*compressed_size = match reader.read_u64_le() {
Ok(v) => v,
Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
return Err(invalid!("ZIP64 extra field truncated"));
}
Err(e) => return Err(e.into()),
};
consumed_len += mem::size_of::<u64>();
}
if len >= 24 || *header_start == ZIP64_BYTES_THR {
*header_start = match reader.read_u64_le() {
Ok(v) => v,
Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
return Err(invalid!("ZIP64 extra field truncated"));
}
Err(e) => return Err(e.into()),
};
consumed_len += mem::size_of::<u64>();
}
let Some(leftover_len) = (len as usize).checked_sub(consumed_len) else {
return Err(invalid!("ZIP64 extra-data field is the wrong length"));
};
let mut limited = reader.take(leftover_len as u64);
if let Err(e) = copy(&mut limited, &mut sink()) {
if e.kind() == ErrorKind::UnexpectedEof {
return Err(invalid!("ZIP64 extra field truncated"));
}
return Err(e.into());
}
Ok(())
}
}