Skip to content
Open
1 change: 1 addition & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[default.extend-identifiers]
ZIP64_BYTES_THR = "ZIP64_BYTES_THR"
ZIP64_BYTES_THR_U32 = "ZIP64_BYTES_THR_U32"
ZIP64_ENTRY_THR = "ZIP64_ENTRY_THR"
flate2 = "flate2"
"00ba" = "00ba"
Expand Down
2 changes: 1 addition & 1 deletion src/read/zipfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ impl<'a, R: Read + ?Sized> ZipFile<'a, R> {
/// a new zip archive.
pub fn options(&self) -> SimpleFileOptions {
let mut options = SimpleFileOptions::default()
.large_file(self.compressed_size().max(self.size()) > ZIP64_BYTES_THR)
.large_file(self.compressed_size().max(self.size()) >= ZIP64_BYTES_THR)
.compression_method(self.compression())
.unix_permissions(self.unix_mode().unwrap_or(0o644) | ffi::S_IFREG)
.last_modified_time(
Expand Down
4 changes: 4 additions & 0 deletions src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ impl Magic {
/// # }
///```
pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64;
pub const ZIP64_BYTES_THR_U32: u32 = u32::MAX;
/// The number of entries within a single zip necessary to allocate a zip64 central
/// directory record.
///
Expand Down Expand Up @@ -609,6 +610,8 @@ impl Zip64CentralDirectoryEnd {
/// Minimum size of the block
/// Block - record_size - extensible_data
const MIN_SIZE: usize = 2 * size_of::<u16>() + 2 * size_of::<u32>() + 4 * size_of::<u64>();
pub(crate) const MIN_FULL_SIZE: usize =
2 * size_of::<u16>() + 2 * size_of::<u32>() + 5 * size_of::<u64>();
/// Size of ZIP64 EOCD signature + record_size field.
const RECORD_OVERHEAD: u64 = (size_of::<Magic>() + size_of::<u64>()) as u64;

Expand Down Expand Up @@ -990,5 +993,6 @@ mod tests {
fn test_size_zip64_central_directory_end() {
use super::Zip64CentralDirectoryEnd;
assert_eq!(Zip64CentralDirectoryEnd::MIN_SIZE, 44);
assert_eq!(Zip64CentralDirectoryEnd::MIN_FULL_SIZE, 52);
}
}
29 changes: 8 additions & 21 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,13 +510,14 @@ impl ZipFileData {
}
fn clamp_size_field(&self, field: u64) -> Result<u32, std::io::Error> {
if self.large_file {
Ok(spec::ZIP64_BYTES_THR as u32)
Ok(spec::ZIP64_BYTES_THR_U32)
} else {
field.min(spec::ZIP64_BYTES_THR).try_into().map_err(|_| {
let size: u32 = field.try_into().map_err(|_| {
std::io::Error::other(format!(
"File size {field} exceeds maximum size for non-ZIP64 files"
))
})
})?;
Ok(size.min(spec::ZIP64_BYTES_THR_U32 - 1))
}
}

Expand Down Expand Up @@ -560,22 +561,8 @@ impl ZipFileData {
}

pub(crate) fn block(&self, file_name_raw: &[u8]) -> ZipResult<ZipCentralEntryBlock> {
let compressed_size = if self.large_file {
spec::ZIP64_BYTES_THR as u32
} else {
self.compressed_size
.min(spec::ZIP64_BYTES_THR)
.try_into()
.map_err(std::io::Error::other)?
};
let uncompressed_size = if self.large_file {
spec::ZIP64_BYTES_THR as u32
} else {
self.uncompressed_size
.min(spec::ZIP64_BYTES_THR)
.try_into()
.map_err(std::io::Error::other)?
};
let compressed_size = self.clamp_size_field(self.compressed_size)?;
let uncompressed_size = self.clamp_size_field(self.uncompressed_size)?;
let offset = self
.header_start
.min(spec::ZIP64_BYTES_THR)
Expand Down Expand Up @@ -636,8 +623,8 @@ impl ZipFileData {
if self.large_file {
return self.zip64_data_descriptor_block().write(writer);
}
if self.compressed_size > spec::ZIP64_BYTES_THR
|| self.uncompressed_size > spec::ZIP64_BYTES_THR
if self.compressed_size >= spec::ZIP64_BYTES_THR
|| self.uncompressed_size >= spec::ZIP64_BYTES_THR
{
if auto_large_file {
return self.zip64_data_descriptor_block().write(writer);
Expand Down
67 changes: 48 additions & 19 deletions src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use crate::extra_fields::Zip64ExtendedInformation;
use crate::format::flags::ZipFlags;
use crate::read::{Config, ZipArchive, ZipFile, parse_single_extra_field};
use crate::result::{ZipError, ZipResult, invalid};
use crate::spec::{self, FixedSizeBlock, Magic, Zip32CDEBlock, ZipLocalEntryBlock};
use crate::spec::{
self, FixedSizeBlock, Magic, Zip32CDEBlock, Zip64CentralDirectoryEnd,
Zip64CentralDirectoryEndLocator, ZipLocalEntryBlock,
};
use crate::types::EncryptWith;
use crate::types::{AesVendorVersion, MIN_VERSION, System, ZipFileData, ZipRawValues, ffi};
use core::default::Default;
Expand Down Expand Up @@ -789,7 +792,7 @@ impl<W: Write + Seek> Write for ZipWriter<W> {
if let Ok(count) = write_result {
self.stats.update(&buf[..count]);
// Only perform the expensive large-file check when we first cross the threshold.
if self.stats.bytes_written > spec::ZIP64_BYTES_THR {
if self.stats.bytes_written >= spec::ZIP64_BYTES_THR {
let is_large_file = self
.files
.last()
Expand Down Expand Up @@ -1845,7 +1848,7 @@ impl<W: Write + Seek> ZipWriter<W> {
fn finalize(&mut self) -> ZipResult<u64> {
self.finish_file()?;

let mut central_start = self.write_central_and_footer()?;
let (central_start, is_zip64) = self.write_central_and_footer()?;
let writer = self.inner.try_inner_mut()?;
let footer_end = writer.stream_position()?;
let archive_end = writer.seek(SeekFrom::End(0))?;
Expand All @@ -1855,24 +1858,48 @@ impl<W: Write + Seek> ZipWriter<W> {
// Overwrite the magic so the footer is no longer valid.
writer.seek(SeekFrom::Start(central_start))?;
writer.write_u32_le(0)?;
writer.seek(SeekFrom::Start(
footer_end
- (size_of::<Magic>() + size_of::<Zip32CDEBlock>()) as u64
- self.comment.len() as u64,
))?;
let start_zip32_cde = footer_end
- (size_of::<Magic>() + size_of::<Zip32CDEBlock>()) as u64
- self.comment.len() as u64;
writer.seek(SeekFrom::Start(start_zip32_cde))?;
writer.write_u32_le(0)?;
let zip64_extensible_len = self
.zip64_extensible_data_sector
.as_ref()
.map(|e| e.len() as u64)
.unwrap_or(0);
if is_zip64 {
let start_zip64_locator = start_zip32_cde
- (size_of::<Magic>() + size_of::<Zip64CentralDirectoryEndLocator>()) as u64;
writer.seek(SeekFrom::Start(start_zip64_locator))?;
writer.write_u32_le(0)?;
let start_zip64_cde = start_zip64_locator
- (size_of::<Magic>() + Zip64CentralDirectoryEnd::MIN_FULL_SIZE) as u64
- zip64_extensible_len;
writer.seek(SeekFrom::Start(start_zip64_cde))?;
writer.write_u32_le(0)?;
}

// Rewrite the footer at the actual end.
let central_and_footer_size = footer_end - central_start;
writer.seek(SeekFrom::End(-(central_and_footer_size as i64)))?;
central_start = self.write_central_and_footer()?;
debug_assert!(self.inner.try_inner_mut()?.stream_position()? == archive_end);
let (_, new_is_zip64) = self.write_central_and_footer()?;
if new_is_zip64 && new_is_zip64 != is_zip64 {
let new_size = (size_of::<Magic>() + Zip64CentralDirectoryEnd::MIN_FULL_SIZE)
as u64
+ zip64_extensible_len
+ (size_of::<Magic>() + size_of::<Zip64CentralDirectoryEndLocator>()) as u64;
let new_archive_end = archive_end + new_size;
debug_assert!(self.inner.try_inner_mut()?.stream_position()? == new_archive_end);
} else {
debug_assert!(self.inner.try_inner_mut()?.stream_position()? == archive_end);
}
}

Ok(central_start)
}

fn write_central_and_footer(&mut self) -> Result<u64, ZipError> {
fn write_central_and_footer(&mut self) -> Result<(u64, bool), ZipError> {
let writer = self.inner.try_inner_mut()?;

let mut version_needed = u16::from(MIN_VERSION);
Expand Down Expand Up @@ -1918,7 +1945,7 @@ impl<W: Write + Seek> ZipWriter<W> {
}

let central_directory_size = if is64 {
spec::ZIP64_BYTES_THR as u32
spec::ZIP64_BYTES_THR_U32
} else {
central_size.min(spec::ZIP64_BYTES_THR) as u32
};
Expand All @@ -1935,7 +1962,7 @@ impl<W: Write + Seek> ZipWriter<W> {
};

footer.write(writer)?;
Ok(central_start)
Ok((central_start, is64))
}

fn index_by_name(&self, name: &[u8]) -> ZipResult<usize> {
Expand Down Expand Up @@ -2425,16 +2452,13 @@ impl ZipFileData {
))?;
writer.write_u32_le(self.crc32)?;
if self.large_file {
writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?;
writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?;
writer.write_u32_le(spec::ZIP64_BYTES_THR_U32)?;
writer.write_u32_le(spec::ZIP64_BYTES_THR_U32)?;

self.update_local_zip64_extra_field(writer, file_name_raw)?;

// self.compressed_size = spec::ZIP64_BYTES_THR;
// self.uncompressed_size = spec::ZIP64_BYTES_THR;
} else {
// check compressed size as well as it can also be slightly larger than uncompressed size
if self.compressed_size > spec::ZIP64_BYTES_THR {
if self.compressed_size >= spec::ZIP64_BYTES_THR {
Comment thread
Its-Just-Nans marked this conversation as resolved.
return Err(ZipError::Io(std::io::Error::other(
"large_file(true) option has not been set",
)));
Expand Down Expand Up @@ -2466,6 +2490,11 @@ impl ZipFileData {

writer.seek(SeekFrom::Start(zip64_extra_field_start))?;
zip64_block.write(writer)?;
if let Some(extra_field) = &mut self.extra_field {
let slice = Arc::make_mut(extra_field);
let mut cursor = Cursor::new(&mut slice[0..20]);
zip64_block.write(&mut cursor)?;
}
Ok(())
}

Expand Down
Loading
Loading