refactor: Eliminate some magic numbers and unnecessary path prefixes (#225)

* refactor: eliminate a magic number for CDE block size

* refactor: Minor refactors

* refactor: Can use cde_start_pos to locate ZIP64 end locator

* chore: Fix import that can no longer be feature-gated

* chore: Fix import that can no longer be feature-gated
This commit is contained in:
Chris Hennick 2024-07-27 18:43:44 -07:00 committed by GitHub
parent a29b860395
commit 3ecd65176c
Signed by: DevComp
GPG key ID: B5690EEEBB952194
2 changed files with 97 additions and 115 deletions

View file

@ -8,7 +8,10 @@ use crate::crc32::Crc32Reader;
use crate::extra_fields::{ExtendedTimestamp, ExtraField}; use crate::extra_fields::{ExtendedTimestamp, ExtraField};
use crate::read::zip_archive::{Shared, SharedBuilder}; use crate::read::zip_archive::{Shared, SharedBuilder};
use crate::result::{ZipError, ZipResult}; use crate::result::{ZipError, ZipResult};
use crate::spec::{self, FixedSizeBlock, Pod, Zip32CentralDirectoryEnd, ZIP64_ENTRY_THR}; use crate::spec::{
self, FixedSizeBlock, Pod, Zip32CentralDirectoryEnd, Zip64CDELocatorBlock,
Zip64CentralDirectoryEnd, ZIP64_ENTRY_THR,
};
use crate::types::{ use crate::types::{
AesMode, AesVendorVersion, DateTime, System, ZipCentralEntryBlock, ZipFileData, AesMode, AesVendorVersion, DateTime, System, ZipCentralEntryBlock, ZipFileData,
ZipLocalEntryBlock, ZipLocalEntryBlock,
@ -47,7 +50,7 @@ pub(crate) mod zip_archive {
/// Extract immutable data from `ZipArchive` to make it cheap to clone /// Extract immutable data from `ZipArchive` to make it cheap to clone
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Shared { pub(crate) struct Shared {
pub(crate) files: super::IndexMap<Box<str>, super::ZipFileData>, pub(crate) files: IndexMap<Box<str>, super::ZipFileData>,
pub(super) offset: u64, pub(super) offset: u64,
pub(super) dir_start: u64, pub(super) dir_start: u64,
// This isn't yet used anywhere, but it is here for use cases in the future. // This isn't yet used anywhere, but it is here for use cases in the future.
@ -324,7 +327,7 @@ pub(crate) fn find_content<'a>(
None => find_data_start(data, reader)?, None => find_data_start(data, reader)?,
}; };
reader.seek(io::SeekFrom::Start(data_start))?; reader.seek(SeekFrom::Start(data_start))?;
Ok((reader as &mut dyn Read).take(data.compressed_size)) Ok((reader as &mut dyn Read).take(data.compressed_size))
} }
@ -334,7 +337,7 @@ fn find_content_seek<'a, R: Read + Seek>(
) -> ZipResult<SeekableTake<'a, R>> { ) -> ZipResult<SeekableTake<'a, R>> {
// Parse local header // Parse local header
let data_start = find_data_start(data, reader)?; let data_start = find_data_start(data, reader)?;
reader.seek(io::SeekFrom::Start(data_start))?; reader.seek(SeekFrom::Start(data_start))?;
// Explicit Ok and ? are needed to convert io::Error to ZipError // Explicit Ok and ? are needed to convert io::Error to ZipError
Ok(SeekableTake::new(reader, data.compressed_size)?) Ok(SeekableTake::new(reader, data.compressed_size)?)
@ -345,7 +348,7 @@ fn find_data_start(
reader: &mut (impl Read + Seek + Sized), reader: &mut (impl Read + Seek + Sized),
) -> Result<u64, ZipError> { ) -> Result<u64, ZipError> {
// Go to start of data. // Go to start of data.
reader.seek(io::SeekFrom::Start(data.header_start))?; reader.seek(SeekFrom::Start(data.header_start))?;
// Parse static-sized fields and check the magic value. // Parse static-sized fields and check the magic value.
let block = ZipLocalEntryBlock::parse(reader)?; let block = ZipLocalEntryBlock::parse(reader)?;
@ -449,7 +452,7 @@ impl<R> ZipArchive<R> {
Some((_, file)) => file.header_start, Some((_, file)) => file.header_start,
None => central_start, None => central_start,
}; };
let shared = Arc::new(zip_archive::Shared { let shared = Arc::new(Shared {
files, files,
offset: initial_offset, offset: initial_offset,
dir_start: central_start, dir_start: central_start,
@ -479,7 +482,7 @@ impl<R> ZipArchive<R> {
} }
impl<R: Read + Seek> ZipArchive<R> { impl<R: Read + Seek> ZipArchive<R> {
pub(crate) fn merge_contents<W: Write + io::Seek>( pub(crate) fn merge_contents<W: Write + Seek>(
&mut self, &mut self,
mut w: W, mut w: W,
) -> ZipResult<IndexMap<Box<str>, ZipFileData>> { ) -> ZipResult<IndexMap<Box<str>, ZipFileData>> {
@ -543,7 +546,7 @@ impl<R: Read + Seek> ZipArchive<R> {
fn get_directory_info_zip32( fn get_directory_info_zip32(
config: &Config, config: &Config,
reader: &mut R, reader: &mut R,
footer: &spec::Zip32CentralDirectoryEnd, footer: &Zip32CentralDirectoryEnd,
cde_start_pos: u64, cde_start_pos: u64,
) -> ZipResult<CentralDirectoryInfo> { ) -> ZipResult<CentralDirectoryInfo> {
let archive_offset = match config.archive_offset { let archive_offset = match config.archive_offset {
@ -556,15 +559,13 @@ impl<R: Read + Seek> ZipArchive<R> {
let mut offset = cde_start_pos let mut offset = cde_start_pos
.checked_sub(footer.central_directory_size as u64) .checked_sub(footer.central_directory_size as u64)
.and_then(|x| x.checked_sub(footer.central_directory_offset as u64)) .and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
.ok_or(ZipError::InvalidArchive( .ok_or(InvalidArchive("Invalid central directory size or offset"))?;
"Invalid central directory size or offset",
))?;
if config.archive_offset == ArchiveOffset::Detect { if config.archive_offset == ArchiveOffset::Detect {
// Check whether the archive offset makes sense by peeking at the directory start. If it // Check whether the archive offset makes sense by peeking at the directory start. If it
// doesn't, fall back to using no archive offset. This supports zips with the central // doesn't, fall back to using no archive offset. This supports zips with the central
// directory entries somewhere other than directly preceding the end of central directory. // directory entries somewhere other than directly preceding the end of central directory.
reader.seek(io::SeekFrom::Start( reader.seek(SeekFrom::Start(
offset + footer.central_directory_offset as u64, offset + footer.central_directory_offset as u64,
))?; ))?;
let mut buf = [0; 4]; let mut buf = [0; 4];
@ -593,11 +594,6 @@ impl<R: Read + Seek> ZipArchive<R> {
}) })
} }
const fn zip64_cde_len() -> usize {
mem::size_of::<spec::Zip64CentralDirectoryEnd>()
+ mem::size_of::<spec::Zip64CentralDirectoryEndLocator>()
}
const fn order_lower_upper_bounds(a: u64, b: u64) -> (u64, u64) { const fn order_lower_upper_bounds(a: u64, b: u64) -> (u64, u64) {
if a > b { if a > b {
(b, a) (b, a)
@ -609,16 +605,18 @@ impl<R: Read + Seek> ZipArchive<R> {
fn get_directory_info_zip64( fn get_directory_info_zip64(
config: &Config, config: &Config,
reader: &mut R, reader: &mut R,
footer: &spec::Zip32CentralDirectoryEnd,
cde_start_pos: u64, cde_start_pos: u64,
) -> ZipResult<Vec<ZipResult<CentralDirectoryInfo>>> { ) -> ZipResult<Vec<ZipResult<CentralDirectoryInfo>>> {
// See if there's a ZIP64 footer. The ZIP64 locator if present will // See if there's a ZIP64 footer. The ZIP64 locator if present will
// have its signature 20 bytes in front of the standard footer. The // have its signature 20 bytes in front of the standard footer. The
// standard footer, in turn, is 22+N bytes large, where N is the // standard footer, in turn, is 22+N bytes large, where N is the
// comment length. Therefore: // comment length. Therefore:
/* TODO: compute this from constant sizes and offsets! */ reader.seek(SeekFrom::Start(
reader.seek(io::SeekFrom::End( cde_start_pos
-(20 + 22 + footer.zip_file_comment.len() as i64), .checked_sub(size_of::<Zip64CDELocatorBlock>() as u64)
.ok_or(InvalidArchive(
"No room for ZIP64 locator before central directory end",
))?,
))?; ))?;
let locator64 = spec::Zip64CentralDirectoryEndLocator::parse(reader)?; let locator64 = spec::Zip64CentralDirectoryEndLocator::parse(reader)?;
@ -632,8 +630,11 @@ impl<R: Read + Seek> ZipArchive<R> {
// ZIP comment data. // ZIP comment data.
let search_upper_bound = cde_start_pos let search_upper_bound = cde_start_pos
.checked_sub(Self::zip64_cde_len() as u64) .checked_sub(
.ok_or(ZipError::InvalidArchive( (size_of::<Zip64CentralDirectoryEnd>()
+ size_of::<spec::Zip64CentralDirectoryEndLocator>()) as u64,
)
.ok_or(InvalidArchive(
"File cannot contain ZIP64 central directory end", "File cannot contain ZIP64 central directory end",
))?; ))?;
@ -642,7 +643,7 @@ impl<R: Read + Seek> ZipArchive<R> {
search_upper_bound, search_upper_bound,
); );
let search_results = spec::Zip64CentralDirectoryEnd::find_and_parse(reader, lower, upper)?; let search_results = Zip64CentralDirectoryEnd::find_and_parse(reader, lower, upper)?;
let results: Vec<ZipResult<CentralDirectoryInfo>> = let results: Vec<ZipResult<CentralDirectoryInfo>> =
search_results.into_iter().map(|(footer64, archive_offset)| { search_results.into_iter().map(|(footer64, archive_offset)| {
let archive_offset = match config.archive_offset { let archive_offset = match config.archive_offset {
@ -654,7 +655,7 @@ impl<R: Read + Seek> ZipArchive<R> {
// Check whether the archive offset makes sense by peeking at the directory start. // Check whether the archive offset makes sense by peeking at the directory start.
// //
// If any errors occur or no header signature is found, fall back to no offset to see if that works. // If any errors occur or no header signature is found, fall back to no offset to see if that works.
reader.seek(io::SeekFrom::Start(start)).ok()?; reader.seek(SeekFrom::Start(start)).ok()?;
let mut buf = [0; 4]; let mut buf = [0; 4];
reader.read_exact(&mut buf).ok()?; reader.read_exact(&mut buf).ok()?;
if spec::Magic::from_le_bytes(buf) != spec::Magic::CENTRAL_DIRECTORY_HEADER_SIGNATURE { if spec::Magic::from_le_bytes(buf) != spec::Magic::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
@ -669,19 +670,19 @@ impl<R: Read + Seek> ZipArchive<R> {
let directory_start = footer64 let directory_start = footer64
.central_directory_offset .central_directory_offset
.checked_add(archive_offset) .checked_add(archive_offset)
.ok_or(ZipError::InvalidArchive( .ok_or(InvalidArchive(
"Invalid central directory size or offset", "Invalid central directory size or offset",
))?; ))?;
if directory_start > search_upper_bound { if directory_start > search_upper_bound {
Err(ZipError::InvalidArchive( Err(InvalidArchive(
"Invalid central directory size or offset", "Invalid central directory size or offset",
)) ))
} else if footer64.number_of_files_on_this_disk > footer64.number_of_files { } else if footer64.number_of_files_on_this_disk > footer64.number_of_files {
Err(ZipError::InvalidArchive( Err(InvalidArchive(
"ZIP64 footer indicates more files on this disk than in the whole archive", "ZIP64 footer indicates more files on this disk than in the whole archive",
)) ))
} else if footer64.version_needed_to_extract > footer64.version_made_by { } else if footer64.version_needed_to_extract > footer64.version_made_by {
Err(ZipError::InvalidArchive( Err(InvalidArchive(
"ZIP64 footer indicates a new version is needed to extract this archive than the \ "ZIP64 footer indicates a new version is needed to extract this archive than the \
version that wrote it", version that wrote it",
)) ))
@ -711,7 +712,7 @@ impl<R: Read + Seek> ZipArchive<R> {
let mut invalid_errors_64 = Vec::new(); let mut invalid_errors_64 = Vec::new();
let mut unsupported_errors_64 = Vec::new(); let mut unsupported_errors_64 = Vec::new();
let mut ok_results = Vec::new(); let mut ok_results = Vec::new();
let cde_locations = spec::Zip32CentralDirectoryEnd::find_and_parse(reader)?; let cde_locations = Zip32CentralDirectoryEnd::find_and_parse(reader)?;
cde_locations cde_locations
.into_vec() .into_vec()
.into_iter() .into_iter()
@ -728,7 +729,7 @@ impl<R: Read + Seek> ZipArchive<R> {
let mut inner_results = Vec::with_capacity(1); let mut inner_results = Vec::with_capacity(1);
// Check if file has a zip64 footer // Check if file has a zip64 footer
let zip64_vec_result = let zip64_vec_result =
Self::get_directory_info_zip64(&config, reader, &footer, cde_start_pos); Self::get_directory_info_zip64(&config, reader, cde_start_pos);
Self::sort_result( Self::sort_result(
zip64_vec_result, zip64_vec_result,
&mut invalid_errors_64, &mut invalid_errors_64,
@ -798,7 +799,7 @@ impl<R: Read + Seek> ZipArchive<R> {
.next() .next()
.unwrap()); .unwrap());
}; };
reader.seek(io::SeekFrom::Start(shared.dir_start))?; reader.seek(SeekFrom::Start(shared.dir_start))?;
Ok((Rc::try_unwrap(footer).unwrap(), shared.build())) Ok((Rc::try_unwrap(footer).unwrap(), shared.build()))
} }
@ -818,7 +819,7 @@ impl<R: Read + Seek> ZipArchive<R> {
return unsupported_zip_error("Support for multi-disk files is not implemented"); return unsupported_zip_error("Support for multi-disk files is not implemented");
} }
let mut files = Vec::with_capacity(file_capacity); let mut files = Vec::with_capacity(file_capacity);
reader.seek(io::SeekFrom::Start(dir_info.directory_start))?; reader.seek(SeekFrom::Start(dir_info.directory_start))?;
for _ in 0..dir_info.number_of_files { for _ in 0..dir_info.number_of_files {
let file = central_header_to_zip_file(reader, dir_info.archive_offset)?; let file = central_header_to_zip_file(reader, dir_info.archive_offset)?;
files.push(file); files.push(file);
@ -925,7 +926,7 @@ impl<R: Read + Seek> ZipArchive<R> {
let mut file = self.by_index(i)?; let mut file = self.by_index(i)?;
let filepath = file let filepath = file
.enclosed_name() .enclosed_name()
.ok_or(ZipError::InvalidArchive("Invalid file path"))?; .ok_or(InvalidArchive("Invalid file path"))?;
let outpath = directory.as_ref().join(filepath); let outpath = directory.as_ref().join(filepath);
@ -1333,7 +1334,7 @@ fn central_header_to_zip_file_inner<R: Read>(
let aes_enabled = result.compression_method == CompressionMethod::AES; let aes_enabled = result.compression_method == CompressionMethod::AES;
if aes_enabled && result.aes_mode.is_none() { if aes_enabled && result.aes_mode.is_none() {
return Err(ZipError::InvalidArchive( return Err(InvalidArchive(
"AES encryption without AES extra data field", "AES encryption without AES extra data field",
)); ));
} }
@ -1342,7 +1343,7 @@ fn central_header_to_zip_file_inner<R: Read>(
result.header_start = result result.header_start = result
.header_start .header_start
.checked_add(archive_offset) .checked_add(archive_offset)
.ok_or(ZipError::InvalidArchive("Archive header is too large"))?; .ok_or(InvalidArchive("Archive header is too large"))?;
Ok(result) Ok(result)
} }
@ -1392,14 +1393,13 @@ pub(crate) fn parse_single_extra_field<R: Read>(
"Can't write a custom field using the ZIP64 ID", "Can't write a custom field using the ZIP64 ID",
)); ));
} }
file.large_file = true;
let mut consumed_len = 0; let mut consumed_len = 0;
if len >= 24 || file.uncompressed_size == spec::ZIP64_BYTES_THR { if len >= 24 || file.uncompressed_size == spec::ZIP64_BYTES_THR {
file.large_file = true;
file.uncompressed_size = reader.read_u64_le()?; file.uncompressed_size = reader.read_u64_le()?;
consumed_len += size_of::<u64>(); consumed_len += size_of::<u64>();
} }
if len >= 24 || file.compressed_size == spec::ZIP64_BYTES_THR { if len >= 24 || file.compressed_size == spec::ZIP64_BYTES_THR {
file.large_file = true;
file.compressed_size = reader.read_u64_le()?; file.compressed_size = reader.read_u64_le()?;
consumed_len += size_of::<u64>(); consumed_len += size_of::<u64>();
} }
@ -1428,18 +1428,18 @@ pub(crate) fn parse_single_extra_field<R: Read>(
let compression_method = CompressionMethod::parse_from_u16(reader.read_u16_le()?); let compression_method = CompressionMethod::parse_from_u16(reader.read_u16_le()?);
if vendor_id != 0x4541 { if vendor_id != 0x4541 {
return Err(ZipError::InvalidArchive("Invalid AES vendor")); return Err(InvalidArchive("Invalid AES vendor"));
} }
let vendor_version = match vendor_version { let vendor_version = match vendor_version {
0x0001 => AesVendorVersion::Ae1, 0x0001 => AesVendorVersion::Ae1,
0x0002 => AesVendorVersion::Ae2, 0x0002 => AesVendorVersion::Ae2,
_ => return Err(ZipError::InvalidArchive("Invalid AES vendor version")), _ => return Err(InvalidArchive("Invalid AES vendor version")),
}; };
match aes_mode { match aes_mode {
0x01 => file.aes_mode = Some((AesMode::Aes128, vendor_version, compression_method)), 0x01 => file.aes_mode = Some((AesMode::Aes128, vendor_version, compression_method)),
0x02 => file.aes_mode = Some((AesMode::Aes192, vendor_version, compression_method)), 0x02 => file.aes_mode = Some((AesMode::Aes192, vendor_version, compression_method)),
0x03 => file.aes_mode = Some((AesMode::Aes256, vendor_version, compression_method)), 0x03 => file.aes_mode = Some((AesMode::Aes256, vendor_version, compression_method)),
_ => return Err(ZipError::InvalidArchive("Invalid AES encryption strength")), _ => return Err(InvalidArchive("Invalid AES encryption strength")),
}; };
file.compression_method = compression_method; file.compression_method = compression_method;
file.aes_extra_data_start = bytes_already_read; file.aes_extra_data_start = bytes_already_read;
@ -1488,7 +1488,7 @@ pub trait HasZipMetadata {
/// Methods for retrieving information on zip files /// Methods for retrieving information on zip files
impl<'a> ZipFile<'a> { impl<'a> ZipFile<'a> {
pub(crate) fn take_raw_reader(&mut self) -> io::Result<io::Take<&'a mut dyn Read>> { pub(crate) fn take_raw_reader(&mut self) -> io::Result<io::Take<&'a mut dyn Read>> {
std::mem::replace(&mut self.reader, ZipFileReader::NoReader).into_inner() mem::replace(&mut self.reader, ZipFileReader::NoReader).into_inner()
} }
/// Get the version of the file /// Get the version of the file

View file

@ -50,7 +50,7 @@ use zstd::stream::write::Encoder as ZstdEncoder;
enum MaybeEncrypted<W> { enum MaybeEncrypted<W> {
Unencrypted(W), Unencrypted(W),
#[cfg(feature = "aes-crypto")] #[cfg(feature = "aes-crypto")]
Aes(crate::aes::AesWriter<W>), Aes(AesWriter<W>),
ZipCrypto(crate::zipcrypto::ZipCryptoWriter<W>), ZipCrypto(crate::zipcrypto::ZipCryptoWriter<W>),
} }
@ -173,9 +173,7 @@ pub(crate) mod zip_writer {
} }
#[doc(inline)] #[doc(inline)]
pub use self::sealed::FileOptionExtension; pub use self::sealed::FileOptionExtension;
use crate::result::ZipError::InvalidArchive; use crate::result::ZipError::{InvalidArchive, UnsupportedArchive};
#[cfg(any(feature = "lzma", feature = "xz"))]
use crate::result::ZipError::UnsupportedArchive;
use crate::unstable::path_to_string; use crate::unstable::path_to_string;
use crate::unstable::LittleEndianWriteExt; use crate::unstable::LittleEndianWriteExt;
use crate::write::GenericZipWriter::{Closed, Storer}; use crate::write::GenericZipWriter::{Closed, Storer};
@ -649,7 +647,7 @@ impl<A: Read + Write + Seek> ZipWriter<A> {
/// read previously-written files and not overwrite them. /// read previously-written files and not overwrite them.
/// ///
/// Note: when using an `inner` that cannot overwrite flushed bytes, do not wrap it in a /// Note: when using an `inner` that cannot overwrite flushed bytes, do not wrap it in a
/// [std::io::BufWriter], because that has a [Seek::seek] method that implicitly calls /// [BufWriter], because that has a [Seek::seek] method that implicitly calls
/// [BufWriter::flush], and ZipWriter needs to seek backward to update each file's header with /// [BufWriter::flush], and ZipWriter needs to seek backward to update each file's header with
/// the size and checksum after writing the body. /// the size and checksum after writing the body.
/// ///
@ -859,7 +857,7 @@ impl<W: Write + Seek> ZipWriter<W> {
"No file has been started", "No file has been started",
))); )));
} }
self.stats.hasher = crc32fast::Hasher::new_with_initial_len(crc32, length); self.stats.hasher = Hasher::new_with_initial_len(crc32, length);
self.stats.bytes_written = length; self.stats.bytes_written = length;
Ok(()) Ok(())
} }
@ -1005,11 +1003,11 @@ impl<W: Write + Seek> ZipWriter<W> {
#[cfg(feature = "aes-crypto")] #[cfg(feature = "aes-crypto")]
Some(EncryptWith::Aes { mode, password }) => { Some(EncryptWith::Aes { mode, password }) => {
let aeswriter = AesWriter::new( let aeswriter = AesWriter::new(
mem::replace(&mut self.inner, GenericZipWriter::Closed).unwrap(), mem::replace(&mut self.inner, Closed).unwrap(),
mode, mode,
password.as_bytes(), password.as_bytes(),
)?; )?;
self.inner = GenericZipWriter::Storer(MaybeEncrypted::Aes(aeswriter)); self.inner = Storer(MaybeEncrypted::Aes(aeswriter));
} }
Some(EncryptWith::ZipCrypto(keys, ..)) => { Some(EncryptWith::ZipCrypto(keys, ..)) => {
let mut zipwriter = crate::zipcrypto::ZipCryptoWriter { let mut zipwriter = crate::zipcrypto::ZipCryptoWriter {
@ -1220,7 +1218,7 @@ impl<W: Write + Seek> ZipWriter<W> {
///``` ///```
pub fn merge_archive<R>(&mut self, mut source: ZipArchive<R>) -> ZipResult<()> pub fn merge_archive<R>(&mut self, mut source: ZipArchive<R>) -> ZipResult<()>
where where
R: Read + io::Seek, R: Read + Seek,
{ {
self.finish_file()?; self.finish_file()?;
@ -1615,9 +1613,7 @@ impl<W: Write + Seek> GenericZipWriter<W> {
match compression { match compression {
Stored => { Stored => {
if compression_level.is_some() { if compression_level.is_some() {
Err(ZipError::UnsupportedArchive( Err(UnsupportedArchive("Unsupported compression level"))
"Unsupported compression level",
))
} else { } else {
Ok(Box::new(|bare| Storer(bare))) Ok(Box::new(|bare| Storer(bare)))
} }
@ -1637,9 +1633,8 @@ impl<W: Write + Seek> GenericZipWriter<W> {
compression_level.unwrap_or(default), compression_level.unwrap_or(default),
deflate_compression_level_range(), deflate_compression_level_range(),
) )
.ok_or(ZipError::UnsupportedArchive( .ok_or(UnsupportedArchive("Unsupported compression level"))?
"Unsupported compression level", as u32;
))? as u32;
#[cfg(feature = "deflate-zopfli")] #[cfg(feature = "deflate-zopfli")]
{ {
@ -1681,18 +1676,17 @@ impl<W: Write + Seek> GenericZipWriter<W> {
} }
} }
#[cfg(feature = "deflate64")] #[cfg(feature = "deflate64")]
CompressionMethod::Deflate64 => Err(ZipError::UnsupportedArchive( CompressionMethod::Deflate64 => {
"Compressing Deflate64 is not supported", Err(UnsupportedArchive("Compressing Deflate64 is not supported"))
)), }
#[cfg(feature = "bzip2")] #[cfg(feature = "bzip2")]
CompressionMethod::Bzip2 => { CompressionMethod::Bzip2 => {
let level = clamp_opt( let level = clamp_opt(
compression_level.unwrap_or(bzip2::Compression::default().level() as i64), compression_level.unwrap_or(bzip2::Compression::default().level() as i64),
bzip2_compression_level_range(), bzip2_compression_level_range(),
) )
.ok_or(ZipError::UnsupportedArchive( .ok_or(UnsupportedArchive("Unsupported compression level"))?
"Unsupported compression level", as u32;
))? as u32;
Ok(Box::new(move |bare| { Ok(Box::new(move |bare| {
GenericZipWriter::Bzip2(BzEncoder::new( GenericZipWriter::Bzip2(BzEncoder::new(
bare, bare,
@ -1700,7 +1694,7 @@ impl<W: Write + Seek> GenericZipWriter<W> {
)) ))
})) }))
} }
CompressionMethod::AES => Err(ZipError::UnsupportedArchive( CompressionMethod::AES => Err(UnsupportedArchive(
"AES encryption is enabled through FileOptions::with_aes_encryption", "AES encryption is enabled through FileOptions::with_aes_encryption",
)), )),
#[cfg(feature = "zstd")] #[cfg(feature = "zstd")]
@ -1709,9 +1703,7 @@ impl<W: Write + Seek> GenericZipWriter<W> {
compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL as i64), compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL as i64),
zstd::compression_level_range(), zstd::compression_level_range(),
) )
.ok_or(ZipError::UnsupportedArchive( .ok_or(UnsupportedArchive("Unsupported compression level"))?;
"Unsupported compression level",
))?;
Ok(Box::new(move |bare| { Ok(Box::new(move |bare| {
GenericZipWriter::Zstd(ZstdEncoder::new(bare, level as i32).unwrap()) GenericZipWriter::Zstd(ZstdEncoder::new(bare, level as i32).unwrap())
})) }))
@ -1725,7 +1717,7 @@ impl<W: Write + Seek> GenericZipWriter<W> {
Err(UnsupportedArchive("XZ isn't supported for compression")) Err(UnsupportedArchive("XZ isn't supported for compression"))
} }
CompressionMethod::Unsupported(..) => { CompressionMethod::Unsupported(..) => {
Err(ZipError::UnsupportedArchive("Unsupported compression")) Err(UnsupportedArchive("Unsupported compression"))
} }
} }
} }
@ -1777,7 +1769,7 @@ impl<W: Write + Seek> GenericZipWriter<W> {
} }
const fn is_closed(&self) -> bool { const fn is_closed(&self) -> bool {
matches!(*self, GenericZipWriter::Closed) matches!(*self, Closed)
} }
fn get_plain(&mut self) -> &mut W { fn get_plain(&mut self) -> &mut W {
@ -1832,17 +1824,14 @@ fn clamp_opt<T: Ord + Copy, U: Ord + Copy + TryFrom<T>>(
} }
} }
fn update_aes_extra_data<W: Write + io::Seek>( fn update_aes_extra_data<W: Write + Seek>(writer: &mut W, file: &mut ZipFileData) -> ZipResult<()> {
writer: &mut W,
file: &mut ZipFileData,
) -> ZipResult<()> {
let Some((aes_mode, version, compression_method)) = file.aes_mode else { let Some((aes_mode, version, compression_method)) = file.aes_mode else {
return Ok(()); return Ok(());
}; };
let extra_data_start = file.extra_data_start.unwrap(); let extra_data_start = file.extra_data_start.unwrap();
writer.seek(io::SeekFrom::Start( writer.seek(SeekFrom::Start(
extra_data_start + file.aes_extra_data_start, extra_data_start + file.aes_extra_data_start,
))?; ))?;
@ -1925,7 +1914,7 @@ fn write_local_zip64_extra_field<T: Write>(writer: &mut T, file: &ZipFileData) -
// This entry in the Local header MUST include BOTH original // This entry in the Local header MUST include BOTH original
// and compressed file size fields. // and compressed file size fields.
let Some(block) = file.zip64_extra_field_block() else { let Some(block) = file.zip64_extra_field_block() else {
return Err(ZipError::InvalidArchive( return Err(InvalidArchive(
"Attempted to write a ZIP64 extra field for a file that's within zip32 limits", "Attempted to write a ZIP64 extra field for a file that's within zip32 limits",
)); ));
}; };
@ -1938,19 +1927,15 @@ fn update_local_zip64_extra_field<T: Write + Seek>(
writer: &mut T, writer: &mut T,
file: &ZipFileData, file: &ZipFileData,
) -> ZipResult<()> { ) -> ZipResult<()> {
if !file.large_file { let block = file.zip64_extra_field_block().ok_or(InvalidArchive(
return Err(ZipError::InvalidArchive( "Attempted to update a nonexistent ZIP64 extra field",
"Attempted to update a nonexistent ZIP64 extra field", ))?;
));
}
let zip64_extra_field = file.header_start let zip64_extra_field_start = file.header_start
+ mem::size_of::<ZipLocalEntryBlock>() as u64 + size_of::<ZipLocalEntryBlock>() as u64
+ file.file_name_raw.len() as u64; + file.file_name_raw.len() as u64;
writer.seek(SeekFrom::Start(zip64_extra_field))?; writer.seek(SeekFrom::Start(zip64_extra_field_start))?;
let block = file.zip64_extra_field_block().unwrap();
let block = block.serialize(); let block = block.serialize();
writer.write_all(&block)?; writer.write_all(&block)?;
Ok(()) Ok(())
@ -1994,14 +1979,13 @@ mod test {
use crate::zipcrypto::ZipCryptoKeys; use crate::zipcrypto::ZipCryptoKeys;
use crate::CompressionMethod::Stored; use crate::CompressionMethod::Stored;
use crate::ZipArchive; use crate::ZipArchive;
use std::io;
use std::io::{Cursor, Read, Write}; use std::io::{Cursor, Read, Write};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::path::PathBuf; use std::path::PathBuf;
#[test] #[test]
fn write_empty_zip() { fn write_empty_zip() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_comment("ZIP"); writer.set_comment("ZIP");
let result = writer.finish().unwrap(); let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 25); assert_eq!(result.get_ref().len(), 25);
@ -2020,7 +2004,7 @@ mod test {
#[test] #[test]
fn write_zip_dir() { fn write_zip_dir() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.add_directory( .add_directory(
"test", "test",
@ -2048,7 +2032,7 @@ mod test {
#[test] #[test]
fn write_symlink_simple() { fn write_symlink_simple() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.add_symlink( .add_symlink(
"name", "name",
@ -2083,7 +2067,7 @@ mod test {
path.push(".."); path.push("..");
path.push("."); path.push(".");
path.push("example.txt"); path.push("example.txt");
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.start_file_from_path(path, SimpleFileOptions::default()) .start_file_from_path(path, SimpleFileOptions::default())
.unwrap(); .unwrap();
@ -2093,7 +2077,7 @@ mod test {
#[test] #[test]
fn write_symlink_wonky_paths() { fn write_symlink_wonky_paths() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.add_symlink( .add_symlink(
"directory\\link", "directory\\link",
@ -2125,9 +2109,9 @@ mod test {
#[test] #[test]
fn write_mimetype_zip() { fn write_mimetype_zip() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = FileOptions { let options = FileOptions {
compression_method: CompressionMethod::Stored, compression_method: Stored,
compression_level: None, compression_level: None,
last_modified_time: DateTime::default(), last_modified_time: DateTime::default(),
permissions: Some(33188), permissions: Some(33188),
@ -2162,9 +2146,9 @@ mod test {
#[test] #[test]
fn write_non_utf8() { fn write_non_utf8() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = FileOptions { let options = FileOptions {
compression_method: CompressionMethod::Stored, compression_method: Stored,
compression_level: None, compression_level: None,
last_modified_time: DateTime::default(), last_modified_time: DateTime::default(),
permissions: Some(33188), permissions: Some(33188),
@ -2199,7 +2183,7 @@ mod test {
#[test] #[test]
fn path_to_string() { fn path_to_string() {
let mut path = std::path::PathBuf::new(); let mut path = PathBuf::new();
#[cfg(windows)] #[cfg(windows)]
path.push(r"C:\"); path.push(r"C:\");
#[cfg(unix)] #[cfg(unix)]
@ -2214,7 +2198,7 @@ mod test {
#[test] #[test]
fn test_shallow_copy() { fn test_shallow_copy() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = FileOptions { let options = FileOptions {
compression_method: CompressionMethod::default(), compression_method: CompressionMethod::default(),
compression_level: None, compression_level: None,
@ -2264,7 +2248,7 @@ mod test {
#[test] #[test]
fn test_deep_copy() { fn test_deep_copy() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = FileOptions { let options = FileOptions {
compression_method: CompressionMethod::default(), compression_method: CompressionMethod::default(),
compression_level: None, compression_level: None,
@ -2312,7 +2296,7 @@ mod test {
#[test] #[test]
fn duplicate_filenames() { fn duplicate_filenames() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.start_file("foo/bar/test", SimpleFileOptions::default()) .start_file("foo/bar/test", SimpleFileOptions::default())
.unwrap(); .unwrap();
@ -2326,7 +2310,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator() { fn test_filename_looks_like_zip64_locator() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.start_file( .start_file(
"PK\u{6}\u{7}\0\0\0\u{11}\0\0\0\0\0\0\0\0\0\0\0\0", "PK\u{6}\u{7}\0\0\0\u{11}\0\0\0\0\0\0\0\0\0\0\0\0",
@ -2339,7 +2323,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator_2() { fn test_filename_looks_like_zip64_locator_2() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.start_file( .start_file(
"PK\u{6}\u{6}\0\0\0\0\0\0\0\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", "PK\u{6}\u{6}\0\0\0\0\0\0\0\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
@ -2352,7 +2336,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator_2a() { fn test_filename_looks_like_zip64_locator_2a() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.start_file( .start_file(
"PK\u{6}\u{6}PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", "PK\u{6}\u{6}PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
@ -2365,7 +2349,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator_3() { fn test_filename_looks_like_zip64_locator_3() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.start_file("\0PK\u{6}\u{6}", SimpleFileOptions::default()) .start_file("\0PK\u{6}\u{6}", SimpleFileOptions::default())
.unwrap(); .unwrap();
@ -2381,7 +2365,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator_4() { fn test_filename_looks_like_zip64_locator_4() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.start_file("PK\u{6}\u{6}", SimpleFileOptions::default()) .start_file("PK\u{6}\u{6}", SimpleFileOptions::default())
.unwrap(); .unwrap();
@ -2407,7 +2391,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator_5() -> ZipResult<()> { fn test_filename_looks_like_zip64_locator_5() -> ZipResult<()> {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.add_directory("", SimpleFileOptions::default().with_alignment(21)) .add_directory("", SimpleFileOptions::default().with_alignment(21))
.unwrap(); .unwrap();
@ -2433,7 +2417,7 @@ mod test {
#[test] #[test]
fn remove_shallow_copy_keeps_original() -> ZipResult<()> { fn remove_shallow_copy_keeps_original() -> ZipResult<()> {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer writer
.start_file("original", SimpleFileOptions::default()) .start_file("original", SimpleFileOptions::default())
.unwrap(); .unwrap();
@ -2452,7 +2436,7 @@ mod test {
#[test] #[test]
fn remove_encrypted_file() -> ZipResult<()> { fn remove_encrypted_file() -> ZipResult<()> {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let first_file_options = SimpleFileOptions::default() let first_file_options = SimpleFileOptions::default()
.with_alignment(65535) .with_alignment(65535)
.with_deprecated_encryption(b"Password"); .with_deprecated_encryption(b"Password");
@ -2469,7 +2453,7 @@ mod test {
let mut options = SimpleFileOptions::default(); let mut options = SimpleFileOptions::default();
options = options.with_deprecated_encryption(b"Password"); options = options.with_deprecated_encryption(b"Password");
options.alignment = 65535; options.alignment = 65535;
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.add_symlink("", "s\t\0\0ggggg\0\0", options).unwrap(); writer.add_symlink("", "s\t\0\0ggggg\0\0", options).unwrap();
writer.abort_file().unwrap(); writer.abort_file().unwrap();
let zip = writer.finish().unwrap(); let zip = writer.finish().unwrap();
@ -2485,7 +2469,7 @@ mod test {
options = options options = options
.compression_method(CompressionMethod::default()) .compression_method(CompressionMethod::default())
.compression_level(Some(264)); .compression_level(Some(264));
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.start_file("", options).unwrap(); writer.start_file("", options).unwrap();
writer.write_all(&[]).unwrap(); writer.write_all(&[]).unwrap();
writer.write_all(&[]).unwrap(); writer.write_all(&[]).unwrap();
@ -2495,11 +2479,9 @@ mod test {
#[test] #[test]
fn crash_with_no_features() -> ZipResult<()> { fn crash_with_no_features() -> ZipResult<()> {
const ORIGINAL_FILE_NAME: &str = "PK\u{6}\u{6}\0\0\0\0\0\0\0\0\0\u{2}g\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\u{7}\0\t'"; const ORIGINAL_FILE_NAME: &str = "PK\u{6}\u{6}\0\0\0\0\0\0\0\0\0\u{2}g\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\u{7}\0\t'";
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let mut options = SimpleFileOptions::default(); let mut options = SimpleFileOptions::default();
options = options options = options.with_alignment(3584).compression_method(Stored);
.with_alignment(3584)
.compression_method(CompressionMethod::Stored);
writer.start_file(ORIGINAL_FILE_NAME, options)?; writer.start_file(ORIGINAL_FILE_NAME, options)?;
let archive = writer.finish()?; let archive = writer.finish()?;
let mut writer = ZipWriter::new_append(archive)?; let mut writer = ZipWriter::new_append(archive)?;
@ -2512,9 +2494,9 @@ mod test {
fn test_alignment() { fn test_alignment() {
let page_size = 4096; let page_size = 4096;
let options = SimpleFileOptions::default() let options = SimpleFileOptions::default()
.compression_method(CompressionMethod::Stored) .compression_method(Stored)
.with_alignment(page_size); .with_alignment(page_size);
let mut zip = ZipWriter::new(io::Cursor::new(Vec::new())); let mut zip = ZipWriter::new(Cursor::new(Vec::new()));
let contents = b"sleeping"; let contents = b"sleeping";
let () = zip.start_file("sleep", options).unwrap(); let () = zip.start_file("sleep", options).unwrap();
let _count = zip.write(&contents[..]).unwrap(); let _count = zip.write(&contents[..]).unwrap();