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,
pub(crate) uncompressed_size: Option<u64>,
pub(crate) compressed_size: Option<u64>,
pub(crate) header_start: Option<u64>,
}
impl Zip64ExtendedInformation {
pub(crate) const MAGIC: UsedExtraField = UsedExtraField::Zip64ExtendedInfo;
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 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: u32,
compressed_size: u32,
header_start: Option<u32>,
) -> ZipResult<(u64, u64, u64)> {
let mut consumed_len = 0;
let new_uncompressed_size = if len >= 24 || u64::from(uncompressed_size) == ZIP64_BYTES_THR
{
let new_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>();
new_uncompressed_size
} else {
uncompressed_size.into()
};
let new_compressed_size = if len >= 24 || u64::from(compressed_size) == ZIP64_BYTES_THR {
let new_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>();
new_compressed_size
} else {
compressed_size.into()
};
let new_header_start = if len >= 24 {
let new_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>();
new_header_start
} else {
if let Some(header_start) = header_start {
if u64::from(header_start) == ZIP64_BYTES_THR {
let new_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>();
new_header_start
} else {
header_start.into()
}
} else {
0
}
};
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((new_uncompressed_size, new_compressed_size, new_header_start))
}
}