Skip to content

Commit

Permalink
interpose ZipRawValues into ZipFileData
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmicexplorer committed May 24, 2024
1 parent 0b31d98 commit 4a784b5
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 57 deletions.
42 changes: 22 additions & 20 deletions src/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::result::{ZipError, ZipResult};
use crate::spec::{self, Block};
use crate::types::{
AesMode, AesVendorVersion, DateTime, System, ZipCentralEntryBlock, ZipFileData,
ZipLocalEntryBlock,
ZipLocalEntryBlock, ZipRawValues,
};
use crate::zipcrypto::{ZipCryptoReader, ZipCryptoReaderValid, ZipCryptoValidator};
use indexmap::IndexMap;
Expand Down Expand Up @@ -252,7 +252,7 @@ pub(crate) fn find_content<'a>(
};

reader.seek(io::SeekFrom::Start(data_start))?;
Ok((reader as &mut dyn Read).take(data.compressed_size))
Ok((reader as &mut dyn Read).take(data.compressed_size()))
}

#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -404,7 +404,7 @@ impl<R> ZipArchive<R> {
if file.using_data_descriptor {
return None;
}
total = total.checked_add(file.uncompressed_size as u128)?;
total = total.checked_add(file.uncompressed_size() as u128)?;
}
Some(total)
}
Expand Down Expand Up @@ -700,7 +700,7 @@ impl<R: Read + Seek> ZipArchive<R> {
None => Ok(None),
Some((aes_mode, _, _)) => {
let (verification_value, salt) =
AesReader::new(limit_reader, aes_mode, data.compressed_size)
AesReader::new(limit_reader, aes_mode, data.compressed_size())
.get_verification_value_and_salt()?;
let aes_info = AesInfo {
aes_mode,
Expand Down Expand Up @@ -980,14 +980,14 @@ impl<R: Read + Seek> ZipArchive<R> {

let crypto_reader = make_crypto_reader(
data.compression_method,
data.crc32,
data.crc32(),
data.last_modified_time,
data.using_data_descriptor,
limit_reader,
password,
data.aes_mode,
#[cfg(feature = "aes-crypto")]
data.compressed_size,
data.compressed_size(),
)?;
Ok(ZipFile {
crypto_reader: Some(crypto_reader),
Expand Down Expand Up @@ -1094,9 +1094,11 @@ fn central_header_to_zip_file_inner<R: Read>(
compression_method: CompressionMethod::parse_from_u16(compression_method),
compression_level: None,
last_modified_time: DateTime::try_from_msdos(last_mod_date, last_mod_time).ok(),
crc32,
compressed_size: compressed_size as u64,
uncompressed_size: uncompressed_size as u64,
raw_values: ZipRawValues {
crc32,
compressed_size: compressed_size.into(),
uncompressed_size: uncompressed_size.into(),
},
file_name,
file_name_raw,
extra_field: Some(Arc::new(extra_field.to_vec())),
Expand Down Expand Up @@ -1147,14 +1149,14 @@ fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> {
match kind {
// Zip64 extended information extra field
0x0001 => {
if file.uncompressed_size == spec::ZIP64_BYTES_THR {
if file.uncompressed_size() == spec::ZIP64_BYTES_THR {
file.large_file = true;
file.uncompressed_size = reader.read_u64_le()?;
file.raw_values.uncompressed_size = reader.read_u64_le()?;
len_left -= 8;
}
if file.compressed_size == spec::ZIP64_BYTES_THR {
if file.compressed_size() == spec::ZIP64_BYTES_THR {
file.large_file = true;
file.compressed_size = reader.read_u64_le()?;
file.raw_values.compressed_size = reader.read_u64_le()?;
len_left -= 8;
}
if file.header_start == spec::ZIP64_BYTES_THR {
Expand Down Expand Up @@ -1228,7 +1230,7 @@ impl<'a> ZipFile<'a> {
if let ZipFileReader::NoReader = self.reader {
let data = &self.data;
let crypto_reader = self.crypto_reader.take().expect("Invalid reader state");
self.reader = make_reader(data.compression_method, data.crc32, crypto_reader)?;
self.reader = make_reader(data.compression_method, data.crc32(), crypto_reader)?;
}
Ok(&mut self.reader)
}
Expand Down Expand Up @@ -1325,12 +1327,12 @@ impl<'a> ZipFile<'a> {

/// Get the size of the file, in bytes, in the archive
pub fn compressed_size(&self) -> u64 {
self.data.compressed_size
self.data.compressed_size()
}

/// Get the size of the file, in bytes, when uncompressed
pub fn size(&self) -> u64 {
self.data.uncompressed_size
self.data.uncompressed_size()
}

/// Get the time the file was last modified
Expand Down Expand Up @@ -1360,7 +1362,7 @@ impl<'a> ZipFile<'a> {

/// Get the CRC32 hash of the original file
pub fn crc32(&self) -> u32 {
self.data.crc32
self.data.crc32()
}

/// Get the extra data of the zip header for this file
Expand Down Expand Up @@ -1459,9 +1461,9 @@ pub fn read_zipfile_from_stream<'a, R: Read>(reader: &'a mut R) -> ZipResult<Opt
Err(e) => return Err(e),
}

let limit_reader = (reader as &'a mut dyn Read).take(result.compressed_size);
let limit_reader = (reader as &'a mut dyn Read).take(result.compressed_size());

let result_crc32 = result.crc32;
let result_crc32 = result.crc32();
let result_compression_method = result.compression_method;
let crypto_reader = make_crypto_reader(
result_compression_method,
Expand All @@ -1472,7 +1474,7 @@ pub fn read_zipfile_from_stream<'a, R: Read>(reader: &'a mut R) -> ZipResult<Opt
None,
None,
#[cfg(feature = "aes-crypto")]
result.compressed_size,
result.compressed_size(),
)?;

Ok(Some(ZipFile {
Expand Down
Empty file modified src/spec.rs
100755 → 100644
Empty file.
70 changes: 44 additions & 26 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@ use crate::CompressionMethod;
#[cfg(feature = "time")]
use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};

/// The components of [`ZipFileData`] which are updated as data is written to a new file entry.
#[derive(Debug, Copy, Clone)]
pub(crate) struct ZipRawValues {
pub(crate) crc32: u32,
pub(crate) compressed_size: u64,
pub(crate) uncompressed_size: u64,
/// CRC32 checksum
pub crc32: u32,
/// Size of the file in the ZIP
pub compressed_size: u64,
/// Size of the file when extracted
pub uncompressed_size: u64,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -391,12 +396,8 @@ pub struct ZipFileData {
pub compression_level: Option<i64>,
/// Last modified time. This will only have a 2 second precision.
pub last_modified_time: Option<DateTime>,
/// CRC32 checksum
pub crc32: u32,
/// Size of the file in the ZIP
pub compressed_size: u64,
/// Size of the file when extracted
pub uncompressed_size: u64,
/// Checksum and data extents
pub(crate) raw_values: ZipRawValues,
/// Name of the file
pub file_name: Box<str>,
/// Raw file name. To be used when file_name was incorrectly decoded.
Expand Down Expand Up @@ -431,6 +432,21 @@ pub struct ZipFileData {
}

impl ZipFileData {
#[inline(always)]
pub fn crc32(&self) -> u32 {
self.raw_values.crc32
}

#[inline(always)]
pub fn compressed_size(&self) -> u64 {
self.raw_values.compressed_size
}

#[inline(always)]
pub fn uncompressed_size(&self) -> u64 {
self.raw_values.uncompressed_size
}

#[allow(dead_code)]
pub fn is_dir(&self) -> bool {
is_dir(&self.file_name)
Expand Down Expand Up @@ -583,9 +599,7 @@ impl ZipFileData {
compression_method,
compression_level: options.compression_level,
last_modified_time: Some(options.last_modified_time),
crc32: raw_values.crc32,
compressed_size: raw_values.compressed_size,
uncompressed_size: raw_values.uncompressed_size,
raw_values,
file_name, // Never used for saving, but used as map key in insert_file_data()
file_name_raw,
extra_field,
Expand Down Expand Up @@ -664,9 +678,11 @@ impl ZipFileData {
compression_method,
compression_level: None,
last_modified_time: DateTime::try_from_msdos(last_mod_date, last_mod_time).ok(),
crc32,
compressed_size: compressed_size.into(),
uncompressed_size: uncompressed_size.into(),
raw_values: ZipRawValues {
crc32,
compressed_size: compressed_size.into(),
uncompressed_size: uncompressed_size.into(),
},
file_name,
file_name_raw: file_name_raw.into(),
extra_field: Some(Arc::new(extra_field)),
Expand Down Expand Up @@ -717,8 +733,8 @@ impl ZipFileData {
}

pub(crate) fn local_block(&self) -> ZipResult<ZipLocalEntryBlock> {
let compressed_size: u32 = self.clamp_size_field(self.compressed_size);
let uncompressed_size: u32 = self.clamp_size_field(self.uncompressed_size);
let compressed_size: u32 = self.clamp_size_field(self.compressed_size());
let uncompressed_size: u32 = self.clamp_size_field(self.uncompressed_size());

let extra_block_len: usize = self
.zip64_extra_field_block()
Expand All @@ -738,7 +754,7 @@ impl ZipFileData {
compression_method: self.compression_method.serialize_to_u16(),
last_mod_time: last_modified_time.timepart(),
last_mod_date: last_modified_time.datepart(),
crc32: self.crc32,
crc32: self.crc32(),
compressed_size,
uncompressed_size,
file_name_length: self.file_name_raw.len().try_into().unwrap(),
Expand All @@ -760,14 +776,14 @@ impl ZipFileData {
compression_method: self.compression_method.serialize_to_u16(),
last_mod_time: last_modified_time.timepart(),
last_mod_date: last_modified_time.datepart(),
crc32: self.crc32,
crc32: self.crc32(),
compressed_size: self
.compressed_size
.compressed_size()
.min(spec::ZIP64_BYTES_THR)
.try_into()
.unwrap(),
uncompressed_size: self
.uncompressed_size
.uncompressed_size()
.min(spec::ZIP64_BYTES_THR)
.try_into()
.unwrap(),
Expand All @@ -789,13 +805,13 @@ impl ZipFileData {

pub(crate) fn zip64_extra_field_block(&self) -> Option<Zip64ExtraFieldBlock> {
let uncompressed_size: Option<u64> =
if self.uncompressed_size > spec::ZIP64_BYTES_THR || self.large_file {
if self.uncompressed_size() > spec::ZIP64_BYTES_THR || self.large_file {
Some(spec::ZIP64_BYTES_THR)
} else {
None
};
let compressed_size: Option<u64> =
if self.compressed_size > spec::ZIP64_BYTES_THR || self.large_file {
if self.compressed_size() > spec::ZIP64_BYTES_THR || self.large_file {
Some(spec::ZIP64_BYTES_THR)
} else {
None
Expand Down Expand Up @@ -1039,9 +1055,11 @@ mod test {
compression_method: crate::compression::CompressionMethod::Stored,
compression_level: None,
last_modified_time: None,
crc32: 0,
compressed_size: 0,
uncompressed_size: 0,
raw_values: ZipRawValues {
crc32: 0,
compressed_size: 0,
uncompressed_size: 0,
},
file_name: file_name.clone().into_boxed_str(),
file_name_raw: file_name.into_bytes().into_boxed_slice(),
extra_field: None,
Expand Down
22 changes: 11 additions & 11 deletions src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,12 +548,12 @@ impl<A: Read + Write + Seek> ZipWriter<A> {
let src_index = self.index_by_name(src_name)?;
let src_data = &self.files[src_index];
let data_start = *src_data.data_start.get().unwrap_or(&0);
let compressed_size = src_data.compressed_size;
let compressed_size = src_data.compressed_size();
debug_assert!(compressed_size <= write_position - data_start);
let uncompressed_size = src_data.uncompressed_size;
let uncompressed_size = src_data.uncompressed_size();

let raw_values = ZipRawValues {
crc32: src_data.crc32,
crc32: src_data.crc32(),
compressed_size,
uncompressed_size,
};
Expand Down Expand Up @@ -925,21 +925,21 @@ impl<W: Write + Seek> ZipWriter<W> {
None => return Ok(()),
Some((_, f)) => f,
};
file.uncompressed_size = self.stats.bytes_written;
file.raw_values.uncompressed_size = self.stats.bytes_written;

let file_end = writer.stream_position()?;
debug_assert!(file_end >= self.stats.start);
file.compressed_size = file_end - self.stats.start;
file.raw_values.compressed_size = file_end - self.stats.start;

file.crc32 = self.stats.hasher.clone().finalize();
file.raw_values.crc32 = self.stats.hasher.clone().finalize();
if let Some(aes_mode) = &mut file.aes_mode {
// We prefer using AE-1 which provides an extra CRC check, but for small files we
// switch to AE-2 to prevent being able to use the CRC value to to reconstruct the
// unencrypted contents.
//
// C.f. https://www.winzip.com/en/support/aes-encryption/#crc-faq
aes_mode.1 = if self.stats.bytes_written < 20 {
file.crc32 = 0;
file.raw_values.crc32 = 0;
AesVendorVersion::Ae2
} else {
AesVendorVersion::Ae1
Expand Down Expand Up @@ -1740,20 +1740,20 @@ fn update_aes_extra_data<W: Write + io::Seek>(
fn update_local_file_header<T: Write + Seek>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
const CRC32_OFFSET: u64 = 14;
writer.seek(SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
writer.write_u32_le(file.crc32)?;
writer.write_u32_le(file.crc32())?;
if file.large_file {
update_local_zip64_extra_field(writer, file)?;
} else {
// check compressed size as well as it can also be slightly larger than uncompressed size
if file.compressed_size > spec::ZIP64_BYTES_THR {
if file.compressed_size() > spec::ZIP64_BYTES_THR {
return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other,
"Large file option has not been set",
)));
}
writer.write_u32_le(file.compressed_size as u32)?;
writer.write_u32_le(file.compressed_size() as u32)?;
// uncompressed size is already checked on write to catch it as soon as possible
writer.write_u32_le(file.uncompressed_size as u32)?;
writer.write_u32_le(file.uncompressed_size() as u32)?;
}
Ok(())
}
Expand Down

0 comments on commit 4a784b5

Please sign in to comment.