fix: (#33) Rare combination of settings could lead to writing a corrupt archive with overlength extra data, and data_start locations when reading the archive back were also wrong (#221)
* fix: Rare combination of settings could lead to writing a corrupt archive with overlength extra data * fix: Previous fix was breaking alignment * style: cargo fmt --all * fix: ZIP64 header was being written twice * style: cargo fmt --all * ci(fuzz): Add check that file-creation options are individually valid * fix: Need to update extra_data_start in deep_copy_file * style: cargo fmt --all * test(fuzz): fix bug in Arbitrary impl * fix: Cursor-position bugs when merging archives or opening for append * fix: unintended feature dependency * style: cargo fmt --all * fix: merge_contents was miscalculating new start positions for absorbed archive's files * fix: shallow_copy_file needs to reset CDE location since the CDE is copied * fix: ZIP64 header was being written after AES header location was already calculated * fix: ZIP64 header was being counted twice when writing extra-field length * fix: deep_copy_file was positioning cursor incorrectly * test(fuzz): Reimplement Debug so that it prints the method calls actually made * test(fuzz): Fix issues with `Option<&mut Formatter>` * chore: Partial debug * chore: Revert: `merge_contents` already adjusts header_start and data_start * chore: Revert unused `mut` * style: cargo fmt --all * refactor: eliminate a magic number for CDE block size * chore: WIP: fix bugs * refactor: Minor refactors * 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 * refactor: Confusing variable name * style: cargo fmt --all and fix Clippy warnings * style: fix another Clippy warning --------- Signed-off-by: Chris Hennick <4961925+Pr0methean@users.noreply.github.com>
This commit is contained in:
parent
fd5f804072
commit
6d8ab6224b
3 changed files with 436 additions and 309 deletions
25
src/read.rs
25
src/read.rs
|
@ -359,7 +359,7 @@ fn find_data_start(
|
||||||
// easily overflow a u16.
|
// easily overflow a u16.
|
||||||
block.file_name_length as u64 + block.extra_field_length as u64;
|
block.file_name_length as u64 + block.extra_field_length as u64;
|
||||||
let data_start =
|
let data_start =
|
||||||
data.header_start + mem::size_of::<ZipLocalEntryBlock>() as u64 + variable_fields_len;
|
data.header_start + size_of::<ZipLocalEntryBlock>() as u64 + variable_fields_len;
|
||||||
// Set the value so we don't have to read it again.
|
// Set the value so we don't have to read it again.
|
||||||
match data.data_start.set(data_start) {
|
match data.data_start.set(data_start) {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
|
@ -497,22 +497,28 @@ impl<R: Read + Seek> ZipArchive<R> {
|
||||||
* assert_eq!(0, new_files[0].header_start); // Avoid this.
|
* assert_eq!(0, new_files[0].header_start); // Avoid this.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let new_initial_header_start = w.stream_position()?;
|
let first_new_file_header_start = w.stream_position()?;
|
||||||
|
|
||||||
/* Push back file header starts for all entries in the covered files. */
|
/* Push back file header starts for all entries in the covered files. */
|
||||||
new_files.values_mut().try_for_each(|f| {
|
new_files.values_mut().try_for_each(|f| {
|
||||||
/* This is probably the only really important thing to change. */
|
/* This is probably the only really important thing to change. */
|
||||||
f.header_start = f.header_start.checked_add(new_initial_header_start).ok_or(
|
f.header_start = f
|
||||||
ZipError::InvalidArchive("new header start from merge would have been too large"),
|
.header_start
|
||||||
)?;
|
.checked_add(first_new_file_header_start)
|
||||||
|
.ok_or(InvalidArchive(
|
||||||
|
"new header start from merge would have been too large",
|
||||||
|
))?;
|
||||||
/* This is only ever used internally to cache metadata lookups (it's not part of the
|
/* This is only ever used internally to cache metadata lookups (it's not part of the
|
||||||
* zip spec), and 0 is the sentinel value. */
|
* zip spec), and 0 is the sentinel value. */
|
||||||
// f.central_header_start = 0;
|
f.central_header_start = 0;
|
||||||
/* This is an atomic variable so it can be updated from another thread in the
|
/* This is an atomic variable so it can be updated from another thread in the
|
||||||
* implementation (which is good!). */
|
* implementation (which is good!). */
|
||||||
if let Some(old_data_start) = f.data_start.take() {
|
if let Some(old_data_start) = f.data_start.take() {
|
||||||
let new_data_start = old_data_start.checked_add(new_initial_header_start).ok_or(
|
let new_data_start = old_data_start
|
||||||
ZipError::InvalidArchive("new data start from merge would have been too large"),
|
.checked_add(first_new_file_header_start)
|
||||||
)?;
|
.ok_or(InvalidArchive(
|
||||||
|
"new data start from merge would have been too large",
|
||||||
|
))?;
|
||||||
f.data_start.get_or_init(|| new_data_start);
|
f.data_start.get_or_init(|| new_data_start);
|
||||||
}
|
}
|
||||||
Ok::<_, ZipError>(())
|
Ok::<_, ZipError>(())
|
||||||
|
@ -1239,7 +1245,6 @@ pub(crate) fn central_header_to_zip_file<R: Read + Seek>(
|
||||||
"A file can't start after its central-directory header",
|
"A file can't start after its central-directory header",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
file.data_start.get_or_init(|| data_start);
|
|
||||||
reader.seek(SeekFrom::Start(central_header_end))?;
|
reader.seek(SeekFrom::Start(central_header_end))?;
|
||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
109
src/types.rs
109
src/types.rs
|
@ -24,7 +24,7 @@ use crate::extra_fields::ExtraField;
|
||||||
use crate::result::DateTimeRangeError;
|
use crate::result::DateTimeRangeError;
|
||||||
use crate::spec::is_dir;
|
use crate::spec::is_dir;
|
||||||
use crate::types::ffi::S_IFDIR;
|
use crate::types::ffi::S_IFDIR;
|
||||||
use crate::CompressionMethod;
|
use crate::{CompressionMethod, ZIP64_BYTES_THR};
|
||||||
#[cfg(feature = "time")]
|
#[cfg(feature = "time")]
|
||||||
use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
|
use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
|
||||||
|
|
||||||
|
@ -625,10 +625,10 @@ impl ZipFileData {
|
||||||
extra_field: &[u8],
|
extra_field: &[u8],
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
S: Into<Box<str>>,
|
S: ToString,
|
||||||
{
|
{
|
||||||
let permissions = options.permissions.unwrap_or(0o100644);
|
let permissions = options.permissions.unwrap_or(0o100644);
|
||||||
let file_name: Box<str> = name.into();
|
let file_name: Box<str> = name.to_string().into_boxed_str();
|
||||||
let file_name_raw: Box<[u8]> = file_name.bytes().collect();
|
let file_name_raw: Box<[u8]> = file_name.bytes().collect();
|
||||||
let mut local_block = ZipFileData {
|
let mut local_block = ZipFileData {
|
||||||
system: System::Unix,
|
system: System::Unix,
|
||||||
|
@ -778,12 +778,8 @@ impl ZipFileData {
|
||||||
pub(crate) fn local_block(&self) -> ZipResult<ZipLocalEntryBlock> {
|
pub(crate) fn local_block(&self) -> ZipResult<ZipLocalEntryBlock> {
|
||||||
let compressed_size: u32 = self.clamp_size_field(self.compressed_size);
|
let compressed_size: u32 = self.clamp_size_field(self.compressed_size);
|
||||||
let uncompressed_size: u32 = self.clamp_size_field(self.uncompressed_size);
|
let uncompressed_size: u32 = self.clamp_size_field(self.uncompressed_size);
|
||||||
|
let extra_field_length: u16 = self
|
||||||
let extra_block_len: usize = self
|
.extra_field_len()
|
||||||
.zip64_extra_field_block()
|
|
||||||
.map(|block| block.full_size())
|
|
||||||
.unwrap_or(0);
|
|
||||||
let extra_field_length: u16 = (self.extra_field_len() + extra_block_len)
|
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| ZipError::InvalidArchive("Extra data field is too large"))?;
|
.map_err(|_| ZipError::InvalidArchive("Extra data field is too large"))?;
|
||||||
|
|
||||||
|
@ -805,7 +801,7 @@ impl ZipFileData {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn block(&self, zip64_extra_field_length: u16) -> ZipResult<ZipCentralEntryBlock> {
|
pub(crate) fn block(&self) -> ZipResult<ZipCentralEntryBlock> {
|
||||||
let extra_field_len: u16 = self.extra_field_len().try_into().unwrap();
|
let extra_field_len: u16 = self.extra_field_len().try_into().unwrap();
|
||||||
let central_extra_field_len: u16 = self.central_extra_field_len().try_into().unwrap();
|
let central_extra_field_len: u16 = self.central_extra_field_len().try_into().unwrap();
|
||||||
let last_modified_time = self
|
let last_modified_time = self
|
||||||
|
@ -832,11 +828,9 @@ impl ZipFileData {
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
file_name_length: self.file_name_raw.len().try_into().unwrap(),
|
file_name_length: self.file_name_raw.len().try_into().unwrap(),
|
||||||
extra_field_length: zip64_extra_field_length
|
extra_field_length: extra_field_len.checked_add(central_extra_field_len).ok_or(
|
||||||
.checked_add(extra_field_len + central_extra_field_len)
|
ZipError::InvalidArchive("Extra field length in central directory exceeds 64KiB"),
|
||||||
.ok_or(ZipError::InvalidArchive(
|
)?,
|
||||||
"Extra field length in central directory exceeds 64KiB",
|
|
||||||
))?,
|
|
||||||
file_comment_length: self.file_comment.as_bytes().len().try_into().unwrap(),
|
file_comment_length: self.file_comment.as_bytes().len().try_into().unwrap(),
|
||||||
disk_number: 0,
|
disk_number: 0,
|
||||||
internal_file_attributes: 0,
|
internal_file_attributes: 0,
|
||||||
|
@ -850,45 +844,12 @@ impl ZipFileData {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn zip64_extra_field_block(&self) -> Option<Zip64ExtraFieldBlock> {
|
pub(crate) fn zip64_extra_field_block(&self) -> Option<Zip64ExtraFieldBlock> {
|
||||||
let uncompressed_size: Option<u64> =
|
Zip64ExtraFieldBlock::maybe_new(
|
||||||
if self.uncompressed_size >= spec::ZIP64_BYTES_THR || self.large_file {
|
self.large_file,
|
||||||
Some(spec::ZIP64_BYTES_THR)
|
self.uncompressed_size,
|
||||||
} else {
|
self.compressed_size,
|
||||||
None
|
self.header_start,
|
||||||
};
|
)
|
||||||
let compressed_size: Option<u64> =
|
|
||||||
if self.compressed_size >= spec::ZIP64_BYTES_THR || self.large_file {
|
|
||||||
Some(spec::ZIP64_BYTES_THR)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let header_start: Option<u64> = if self.header_start >= spec::ZIP64_BYTES_THR {
|
|
||||||
Some(spec::ZIP64_BYTES_THR)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut size: u16 = 0;
|
|
||||||
if uncompressed_size.is_some() {
|
|
||||||
size += mem::size_of::<u64>() as u16;
|
|
||||||
}
|
|
||||||
if compressed_size.is_some() {
|
|
||||||
size += mem::size_of::<u64>() as u16;
|
|
||||||
}
|
|
||||||
if header_start.is_some() {
|
|
||||||
size += mem::size_of::<u64>() as u16;
|
|
||||||
}
|
|
||||||
if size == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Zip64ExtraFieldBlock {
|
|
||||||
magic: spec::ExtraFieldMagic::ZIP64_EXTRA_FIELD_TAG,
|
|
||||||
size,
|
|
||||||
uncompressed_size,
|
|
||||||
compressed_size,
|
|
||||||
header_start,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1002,6 +963,46 @@ pub(crate) struct Zip64ExtraFieldBlock {
|
||||||
// u32: disk start number
|
// u32: disk start number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Zip64ExtraFieldBlock {
|
||||||
|
pub(crate) fn maybe_new(
|
||||||
|
large_file: bool,
|
||||||
|
uncompressed_size: u64,
|
||||||
|
compressed_size: u64,
|
||||||
|
header_start: u64,
|
||||||
|
) -> Option<Zip64ExtraFieldBlock> {
|
||||||
|
let mut size: u16 = 0;
|
||||||
|
let uncompressed_size = if uncompressed_size >= ZIP64_BYTES_THR || large_file {
|
||||||
|
size += mem::size_of::<u64>() as u16;
|
||||||
|
Some(uncompressed_size)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let compressed_size = if compressed_size >= ZIP64_BYTES_THR || large_file {
|
||||||
|
size += mem::size_of::<u64>() as u16;
|
||||||
|
Some(compressed_size)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let header_start = if header_start >= ZIP64_BYTES_THR {
|
||||||
|
size += mem::size_of::<u64>() as u16;
|
||||||
|
Some(header_start)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if size == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Zip64ExtraFieldBlock {
|
||||||
|
magic: spec::ExtraFieldMagic::ZIP64_EXTRA_FIELD_TAG,
|
||||||
|
size,
|
||||||
|
uncompressed_size,
|
||||||
|
compressed_size,
|
||||||
|
header_start,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Zip64ExtraFieldBlock {
|
impl Zip64ExtraFieldBlock {
|
||||||
pub fn full_size(&self) -> usize {
|
pub fn full_size(&self) -> usize {
|
||||||
assert!(self.size > 0);
|
assert!(self.size > 0);
|
||||||
|
|
511
src/write.rs
511
src/write.rs
|
@ -7,11 +7,12 @@ use crate::read::{
|
||||||
find_content, parse_single_extra_field, Config, ZipArchive, ZipFile, ZipFileReader,
|
find_content, parse_single_extra_field, Config, ZipArchive, ZipFile, ZipFileReader,
|
||||||
};
|
};
|
||||||
use crate::result::{ZipError, ZipResult};
|
use crate::result::{ZipError, ZipResult};
|
||||||
use crate::spec::{self, FixedSizeBlock, Zip32CDEBlock};
|
use crate::spec::{self, FixedSizeBlock, Pod, Zip32CDEBlock};
|
||||||
#[cfg(feature = "aes-crypto")]
|
#[cfg(feature = "aes-crypto")]
|
||||||
use crate::types::AesMode;
|
use crate::types::AesMode;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ffi, AesVendorVersion, DateTime, ZipFileData, ZipLocalEntryBlock, ZipRawValues, MIN_VERSION,
|
ffi, AesVendorVersion, DateTime, Zip64ExtraFieldBlock, ZipFileData, ZipLocalEntryBlock,
|
||||||
|
ZipRawValues, MIN_VERSION,
|
||||||
};
|
};
|
||||||
use crate::write::ffi::S_IFLNK;
|
use crate::write::ffi::S_IFLNK;
|
||||||
#[cfg(any(feature = "_deflate-any", feature = "bzip2", feature = "zstd",))]
|
#[cfg(any(feature = "_deflate-any", feature = "bzip2", feature = "zstd",))]
|
||||||
|
@ -304,7 +305,7 @@ impl ExtendedFileOptions {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Self::add_extra_data_unchecked(vec, header_id, data)?;
|
Self::add_extra_data_unchecked(vec, header_id, data)?;
|
||||||
Self::validate_extra_data(vec, 0)?;
|
Self::validate_extra_data(vec, true)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,12 +322,12 @@ impl ExtendedFileOptions {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_extra_data(data: &[u8], reserved: u64) -> ZipResult<()> {
|
fn validate_extra_data(data: &[u8], disallow_zip64: bool) -> ZipResult<()> {
|
||||||
let len = data.len() as u64;
|
let len = data.len() as u64;
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if len + reserved > u16::MAX as u64 {
|
if len > u16::MAX as u64 {
|
||||||
return Err(ZipError::Io(io::Error::new(
|
return Err(ZipError::Io(io::Error::new(
|
||||||
io::ErrorKind::Other,
|
io::ErrorKind::Other,
|
||||||
"Extra-data field can't exceed u16::MAX bytes",
|
"Extra-data field can't exceed u16::MAX bytes",
|
||||||
|
@ -358,7 +359,7 @@ impl ExtendedFileOptions {
|
||||||
}
|
}
|
||||||
data.seek(SeekFrom::Current(-2))?;
|
data.seek(SeekFrom::Current(-2))?;
|
||||||
}
|
}
|
||||||
parse_single_extra_field(&mut ZipFileData::default(), &mut data, pos, true)?;
|
parse_single_extra_field(&mut ZipFileData::default(), &mut data, pos, disallow_zip64)?;
|
||||||
pos = data.position();
|
pos = data.position();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -406,6 +407,9 @@ impl<'a> arbitrary::Arbitrary<'a> for FileOptions<'a, ExtendedFileOptions> {
|
||||||
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
||||||
Ok(core::ops::ControlFlow::Continue(()))
|
Ok(core::ops::ControlFlow::Continue(()))
|
||||||
})?;
|
})?;
|
||||||
|
ZipWriter::new(Cursor::new(Vec::new()))
|
||||||
|
.start_file("", options.clone())
|
||||||
|
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
|
||||||
Ok(options)
|
Ok(options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -668,19 +672,13 @@ impl<A: Read + Write + Seek> ZipWriter<A> {
|
||||||
let write_position = self.inner.get_plain().stream_position()?;
|
let write_position = self.inner.get_plain().stream_position()?;
|
||||||
let src_index = self.index_by_name(src_name)?;
|
let src_index = self.index_by_name(src_name)?;
|
||||||
let src_data = &mut self.files[src_index];
|
let src_data = &mut self.files[src_index];
|
||||||
let data_start = src_data.data_start();
|
let src_data_start = src_data.data_start();
|
||||||
|
debug_assert!(src_data_start <= write_position);
|
||||||
let mut compressed_size = src_data.compressed_size;
|
let mut compressed_size = src_data.compressed_size;
|
||||||
if compressed_size > (write_position - data_start) {
|
if compressed_size > (write_position - src_data_start) {
|
||||||
compressed_size = write_position - data_start;
|
compressed_size = write_position - src_data_start;
|
||||||
src_data.compressed_size = compressed_size;
|
src_data.compressed_size = compressed_size;
|
||||||
}
|
}
|
||||||
let uncompressed_size = src_data.uncompressed_size;
|
|
||||||
|
|
||||||
let raw_values = ZipRawValues {
|
|
||||||
crc32: src_data.crc32,
|
|
||||||
compressed_size,
|
|
||||||
uncompressed_size,
|
|
||||||
};
|
|
||||||
let mut reader = BufReader::new(ZipFileReader::Raw(find_content(
|
let mut reader = BufReader::new(ZipFileReader::Raw(find_content(
|
||||||
src_data,
|
src_data,
|
||||||
self.inner.get_plain(),
|
self.inner.get_plain(),
|
||||||
|
@ -691,56 +689,46 @@ impl<A: Read + Write + Seek> ZipWriter<A> {
|
||||||
self.inner
|
self.inner
|
||||||
.get_plain()
|
.get_plain()
|
||||||
.seek(SeekFrom::Start(write_position))?;
|
.seek(SeekFrom::Start(write_position))?;
|
||||||
if src_data.extra_field.is_some() || src_data.central_extra_field.is_some() {
|
let mut new_data = src_data.clone();
|
||||||
let mut options = FileOptions::<ExtendedFileOptions> {
|
let dest_name_raw = dest_name.as_bytes();
|
||||||
compression_method: src_data.compression_method,
|
new_data.file_name = dest_name.into();
|
||||||
compression_level: src_data.compression_level,
|
new_data.file_name_raw = dest_name_raw.into();
|
||||||
last_modified_time: src_data
|
new_data.is_utf8 = !dest_name.is_ascii();
|
||||||
.last_modified_time
|
new_data.header_start = write_position;
|
||||||
.unwrap_or_else(DateTime::default_for_write),
|
let extra_data_start = write_position
|
||||||
permissions: src_data.unix_mode(),
|
+ size_of::<ZipLocalEntryBlock>() as u64
|
||||||
large_file: src_data.large_file,
|
+ new_data.file_name_raw.len() as u64;
|
||||||
encrypt_with: None,
|
new_data.extra_data_start = Some(extra_data_start);
|
||||||
extended_options: ExtendedFileOptions {
|
let mut data_start = extra_data_start;
|
||||||
extra_data: src_data.extra_field.clone().unwrap_or_default(),
|
if let Some(extra) = &src_data.extra_field {
|
||||||
central_extra_data: src_data.central_extra_field.clone().unwrap_or_default(),
|
data_start += extra.len() as u64;
|
||||||
},
|
|
||||||
alignment: 1,
|
|
||||||
#[cfg(feature = "deflate-zopfli")]
|
|
||||||
zopfli_buffer_size: None,
|
|
||||||
};
|
|
||||||
if let Some(perms) = src_data.unix_mode() {
|
|
||||||
options = options.unix_permissions(perms);
|
|
||||||
}
|
}
|
||||||
Self::normalize_options(&mut options);
|
new_data.data_start.take();
|
||||||
self.start_entry(dest_name, options, Some(raw_values))?;
|
new_data.data_start.get_or_init(|| data_start);
|
||||||
} else {
|
new_data.central_header_start = 0;
|
||||||
let mut options = FileOptions::<()> {
|
let block = new_data.local_block()?;
|
||||||
compression_method: src_data.compression_method,
|
let index = self.insert_file_data(new_data)?;
|
||||||
compression_level: src_data.compression_level,
|
let result = (|| {
|
||||||
last_modified_time: src_data
|
let plain_writer = self.inner.get_plain();
|
||||||
.last_modified_time
|
plain_writer.write_all(block.as_bytes())?;
|
||||||
.unwrap_or_else(DateTime::default_for_write),
|
plain_writer.write_all(dest_name_raw)?;
|
||||||
permissions: src_data.unix_mode(),
|
let new_data = &self.files[index];
|
||||||
large_file: src_data.large_file,
|
if let Some(data) = &new_data.extra_field {
|
||||||
encrypt_with: None,
|
plain_writer.write_all(data)?;
|
||||||
extended_options: (),
|
|
||||||
alignment: 1,
|
|
||||||
#[cfg(feature = "deflate-zopfli")]
|
|
||||||
zopfli_buffer_size: None,
|
|
||||||
};
|
|
||||||
if let Some(perms) = src_data.unix_mode() {
|
|
||||||
options = options.unix_permissions(perms);
|
|
||||||
}
|
}
|
||||||
Self::normalize_options(&mut options);
|
debug_assert_eq!(data_start, plain_writer.stream_position()?);
|
||||||
self.start_entry(dest_name, options, Some(raw_values))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.writing_to_file = true;
|
self.writing_to_file = true;
|
||||||
self.writing_raw = true;
|
plain_writer.write_all(©)
|
||||||
let result = self.write_all(©);
|
})();
|
||||||
self.ok_or_abort_file(result)?;
|
self.ok_or_abort_file(result)?;
|
||||||
self.finish_file()
|
|
||||||
|
// Copying will overwrite the central header
|
||||||
|
self.files
|
||||||
|
.values_mut()
|
||||||
|
.for_each(|file| file.central_header_start = 0);
|
||||||
|
|
||||||
|
self.writing_to_file = false;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like `deep_copy_file`, but uses Path arguments.
|
/// Like `deep_copy_file`, but uses Path arguments.
|
||||||
|
@ -873,18 +861,15 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a new file for with the requested options.
|
/// Start a new file for with the requested options.
|
||||||
fn start_entry<S, SToOwned, T: FileOptionExtension>(
|
fn start_entry<S: ToString, T: FileOptionExtension>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: S,
|
name: S,
|
||||||
options: FileOptions<T>,
|
options: FileOptions<T>,
|
||||||
raw_values: Option<ZipRawValues>,
|
raw_values: Option<ZipRawValues>,
|
||||||
) -> ZipResult<()>
|
) -> ZipResult<()> {
|
||||||
where
|
|
||||||
S: Into<Box<str>> + ToOwned<Owned = SToOwned>,
|
|
||||||
SToOwned: Into<Box<str>>,
|
|
||||||
{
|
|
||||||
self.finish_file()?;
|
self.finish_file()?;
|
||||||
|
|
||||||
|
let header_start = self.inner.get_plain().stream_position()?;
|
||||||
let raw_values = raw_values.unwrap_or(ZipRawValues {
|
let raw_values = raw_values.unwrap_or(ZipRawValues {
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
compressed_size: 0,
|
compressed_size: 0,
|
||||||
|
@ -896,7 +881,13 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
None => vec![],
|
None => vec![],
|
||||||
};
|
};
|
||||||
let central_extra_data = options.extended_options.central_extra_data();
|
let central_extra_data = options.extended_options.central_extra_data();
|
||||||
|
if let Some(zip64_block) =
|
||||||
|
Zip64ExtraFieldBlock::maybe_new(options.large_file, 0, 0, header_start)
|
||||||
|
{
|
||||||
|
let mut new_extra_data = zip64_block.serialize().into_vec();
|
||||||
|
new_extra_data.append(&mut extra_data);
|
||||||
|
extra_data = new_extra_data;
|
||||||
|
}
|
||||||
// Write AES encryption extra data.
|
// Write AES encryption extra data.
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut aes_extra_data_start = 0;
|
let mut aes_extra_data_start = 0;
|
||||||
|
@ -911,8 +902,6 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
aes_dummy_extra_data,
|
aes_dummy_extra_data,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
{
|
|
||||||
let header_start = self.inner.get_plain().stream_position()?;
|
|
||||||
|
|
||||||
let (compression_method, aes_mode) = match options.encrypt_with {
|
let (compression_method, aes_mode) = match options.encrypt_with {
|
||||||
#[cfg(feature = "aes-crypto")]
|
#[cfg(feature = "aes-crypto")]
|
||||||
|
@ -922,35 +911,11 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
),
|
),
|
||||||
_ => (options.compression_method, None),
|
_ => (options.compression_method, None),
|
||||||
};
|
};
|
||||||
let mut file = ZipFileData::initialize_local_block(
|
let header_end = header_start
|
||||||
name,
|
+ size_of::<ZipLocalEntryBlock>() as u64
|
||||||
&options,
|
+ name.to_string().as_bytes().len() as u64;
|
||||||
raw_values,
|
|
||||||
header_start,
|
|
||||||
None,
|
|
||||||
aes_extra_data_start,
|
|
||||||
compression_method,
|
|
||||||
aes_mode,
|
|
||||||
&extra_data,
|
|
||||||
);
|
|
||||||
file.version_made_by = file.version_made_by.max(file.version_needed() as u8);
|
|
||||||
let block = file.local_block();
|
|
||||||
let index = self.insert_file_data(file)?;
|
|
||||||
let writer = self.inner.get_plain();
|
|
||||||
let result = block?.write(writer);
|
|
||||||
self.ok_or_abort_file(result)?;
|
|
||||||
let writer = self.inner.get_plain();
|
|
||||||
let file = &mut self.files[index];
|
|
||||||
// file name
|
|
||||||
writer.write_all(&file.file_name_raw)?;
|
|
||||||
let zip64_start = writer.stream_position()?;
|
|
||||||
if file.large_file {
|
|
||||||
write_local_zip64_extra_field(writer, file)?;
|
|
||||||
}
|
|
||||||
let header_end = writer.stream_position()?;
|
|
||||||
file.extra_data_start = Some(header_end);
|
|
||||||
let mut extra_data_end = header_end + extra_data.len() as u64;
|
|
||||||
if options.alignment > 1 {
|
if options.alignment > 1 {
|
||||||
|
let extra_data_end = header_end + extra_data.len() as u64;
|
||||||
let align = options.alignment as u64;
|
let align = options.alignment as u64;
|
||||||
let unaligned_header_bytes = extra_data_end % align;
|
let unaligned_header_bytes = extra_data_end % align;
|
||||||
if unaligned_header_bytes != 0 {
|
if unaligned_header_bytes != 0 {
|
||||||
|
@ -967,38 +932,50 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
0xa11e,
|
0xa11e,
|
||||||
pad_body.into_boxed_slice(),
|
pad_body.into_boxed_slice(),
|
||||||
)?;
|
)?;
|
||||||
|
debug_assert_eq!((extra_data.len() as u64 + header_end) % align, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let extra_data_len = extra_data.len();
|
let extra_data_len = extra_data.len();
|
||||||
if extra_data_len > 0 {
|
if let Some(data) = central_extra_data {
|
||||||
let result = (|| {
|
if extra_data_len + data.len() > u16::MAX as usize {
|
||||||
ExtendedFileOptions::validate_extra_data(
|
return Err(InvalidArchive(
|
||||||
|
"Extra data and central extra data must be less than 64KiB when combined",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
ExtendedFileOptions::validate_extra_data(data, true)?;
|
||||||
|
}
|
||||||
|
let mut file = ZipFileData::initialize_local_block(
|
||||||
|
name,
|
||||||
|
&options,
|
||||||
|
raw_values,
|
||||||
|
header_start,
|
||||||
|
None,
|
||||||
|
aes_extra_data_start,
|
||||||
|
compression_method,
|
||||||
|
aes_mode,
|
||||||
&extra_data,
|
&extra_data,
|
||||||
header_end - zip64_start,
|
);
|
||||||
)?;
|
file.version_made_by = file.version_made_by.max(file.version_needed() as u8);
|
||||||
|
file.extra_data_start = Some(header_end);
|
||||||
|
let index = self.insert_file_data(file)?;
|
||||||
|
self.writing_to_file = true;
|
||||||
|
let result: ZipResult<()> = (|| {
|
||||||
|
ExtendedFileOptions::validate_extra_data(&extra_data, false)?;
|
||||||
|
let file = &mut self.files[index];
|
||||||
|
let block = file.local_block()?;
|
||||||
|
let writer = self.inner.get_plain();
|
||||||
|
block.write(writer)?;
|
||||||
|
// file name
|
||||||
|
writer.write_all(&file.file_name_raw)?;
|
||||||
|
if extra_data_len > 0 {
|
||||||
writer.write_all(&extra_data)?;
|
writer.write_all(&extra_data)?;
|
||||||
extra_data_end = writer.stream_position()?;
|
file.extra_field = Some(extra_data.into());
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})();
|
})();
|
||||||
if let Err(e) = result {
|
self.ok_or_abort_file(result)?;
|
||||||
let _ = self.abort_file();
|
let writer = self.inner.get_plain();
|
||||||
return Err(e);
|
self.stats.start = writer.stream_position()?;
|
||||||
}
|
|
||||||
debug_assert_eq!(extra_data_end % (options.alignment.max(1) as u64), 0);
|
|
||||||
self.stats.start = extra_data_end;
|
|
||||||
file.extra_field = Some(extra_data.into());
|
|
||||||
} else {
|
|
||||||
self.stats.start = extra_data_end;
|
|
||||||
}
|
|
||||||
if let Some(data) = central_extra_data {
|
|
||||||
let validation_result =
|
|
||||||
ExtendedFileOptions::validate_extra_data(data, extra_data_end - zip64_start);
|
|
||||||
if let Err(e) = validation_result {
|
|
||||||
let _ = self.abort_file();
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
file.central_extra_field = Some(data.clone());
|
|
||||||
}
|
|
||||||
match options.encrypt_with {
|
match options.encrypt_with {
|
||||||
#[cfg(feature = "aes-crypto")]
|
#[cfg(feature = "aes-crypto")]
|
||||||
Some(EncryptWith::Aes { mode, password }) => {
|
Some(EncryptWith::Aes { mode, password }) => {
|
||||||
|
@ -1015,21 +992,20 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
buffer: vec![],
|
buffer: vec![],
|
||||||
keys,
|
keys,
|
||||||
};
|
};
|
||||||
let crypto_header = [0u8; 12];
|
|
||||||
|
|
||||||
zipwriter.write_all(&crypto_header)?;
|
|
||||||
self.stats.start = zipwriter.writer.stream_position()?;
|
self.stats.start = zipwriter.writer.stream_position()?;
|
||||||
|
// crypto_header is counted as part of the data
|
||||||
|
let crypto_header = [0u8; 12];
|
||||||
|
let result = zipwriter.write_all(&crypto_header);
|
||||||
|
self.ok_or_abort_file(result)?;
|
||||||
self.inner = Storer(MaybeEncrypted::ZipCrypto(zipwriter));
|
self.inner = Storer(MaybeEncrypted::ZipCrypto(zipwriter));
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
let file = &mut self.files[index];
|
||||||
debug_assert!(file.data_start.get().is_none());
|
debug_assert!(file.data_start.get().is_none());
|
||||||
file.data_start.get_or_init(|| self.stats.start);
|
file.data_start.get_or_init(|| self.stats.start);
|
||||||
self.writing_to_file = true;
|
|
||||||
self.stats.bytes_written = 0;
|
self.stats.bytes_written = 0;
|
||||||
self.stats.hasher = Hasher::new();
|
self.stats.hasher = Hasher::new();
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1152,15 +1128,11 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
/// same name as a file already in the archive.
|
/// same name as a file already in the archive.
|
||||||
///
|
///
|
||||||
/// The data should be written using the [`Write`] implementation on this [`ZipWriter`]
|
/// The data should be written using the [`Write`] implementation on this [`ZipWriter`]
|
||||||
pub fn start_file<S, T: FileOptionExtension, SToOwned>(
|
pub fn start_file<S: ToString, T: FileOptionExtension>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: S,
|
name: S,
|
||||||
mut options: FileOptions<T>,
|
mut options: FileOptions<T>,
|
||||||
) -> ZipResult<()>
|
) -> ZipResult<()> {
|
||||||
where
|
|
||||||
S: Into<Box<str>> + ToOwned<Owned = SToOwned>,
|
|
||||||
SToOwned: Into<Box<str>>,
|
|
||||||
{
|
|
||||||
Self::normalize_options(&mut options);
|
Self::normalize_options(&mut options);
|
||||||
let make_new_self = self.inner.prepare_next_writer(
|
let make_new_self = self.inner.prepare_next_writer(
|
||||||
options.compression_method,
|
options.compression_method,
|
||||||
|
@ -1286,11 +1258,11 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn raw_copy_file_rename<S, SToOwned>(&mut self, mut file: ZipFile, name: S) -> ZipResult<()>
|
pub fn raw_copy_file_rename<S: ToString>(
|
||||||
where
|
&mut self,
|
||||||
S: Into<Box<str>> + ToOwned<Owned = SToOwned>,
|
mut file: ZipFile,
|
||||||
SToOwned: Into<Box<str>>,
|
name: S,
|
||||||
{
|
) -> ZipResult<()> {
|
||||||
let mut options = SimpleFileOptions::default()
|
let mut options = SimpleFileOptions::default()
|
||||||
.large_file(file.compressed_size().max(file.size()) > spec::ZIP64_BYTES_THR)
|
.large_file(file.compressed_size().max(file.size()) > spec::ZIP64_BYTES_THR)
|
||||||
.last_modified_time(
|
.last_modified_time(
|
||||||
|
@ -1314,8 +1286,7 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
self.writing_raw = true;
|
self.writing_raw = true;
|
||||||
|
|
||||||
io::copy(&mut file.take_raw_reader()?, self)?;
|
io::copy(&mut file.take_raw_reader()?, self)?;
|
||||||
|
self.finish_file()
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like `raw_copy_file_to_path`, but uses Path arguments.
|
/// Like `raw_copy_file_to_path`, but uses Path arguments.
|
||||||
|
@ -1425,17 +1396,12 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
/// implementations may materialize a symlink as a regular file, possibly with the
|
/// implementations may materialize a symlink as a regular file, possibly with the
|
||||||
/// content incorrectly set to the symlink target. For maximum portability, consider
|
/// content incorrectly set to the symlink target. For maximum portability, consider
|
||||||
/// storing a regular file instead.
|
/// storing a regular file instead.
|
||||||
pub fn add_symlink<N, NToOwned, T, E: FileOptionExtension>(
|
pub fn add_symlink<N: ToString, T: ToString, E: FileOptionExtension>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: N,
|
name: N,
|
||||||
target: T,
|
target: T,
|
||||||
mut options: FileOptions<E>,
|
mut options: FileOptions<E>,
|
||||||
) -> ZipResult<()>
|
) -> ZipResult<()> {
|
||||||
where
|
|
||||||
N: Into<Box<str>> + ToOwned<Owned = NToOwned>,
|
|
||||||
NToOwned: Into<Box<str>>,
|
|
||||||
T: Into<Box<str>>,
|
|
||||||
{
|
|
||||||
if options.permissions.is_none() {
|
if options.permissions.is_none() {
|
||||||
options.permissions = Some(0o777);
|
options.permissions = Some(0o777);
|
||||||
}
|
}
|
||||||
|
@ -1446,7 +1412,7 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
|
|
||||||
self.start_entry(name, options, None)?;
|
self.start_entry(name, options, None)?;
|
||||||
self.writing_to_file = true;
|
self.writing_to_file = true;
|
||||||
let result = self.write_all(target.into().as_bytes());
|
let result = self.write_all(target.to_string().as_bytes());
|
||||||
self.ok_or_abort_file(result)?;
|
self.ok_or_abort_file(result)?;
|
||||||
self.writing_raw = false;
|
self.writing_raw = false;
|
||||||
self.finish_file()?;
|
self.finish_file()?;
|
||||||
|
@ -1474,8 +1440,8 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
let mut central_start = self.write_central_and_footer()?;
|
let mut central_start = self.write_central_and_footer()?;
|
||||||
let writer = self.inner.get_plain();
|
let writer = self.inner.get_plain();
|
||||||
let footer_end = writer.stream_position()?;
|
let footer_end = writer.stream_position()?;
|
||||||
let file_end = writer.seek(SeekFrom::End(0))?;
|
let archive_end = writer.seek(SeekFrom::End(0))?;
|
||||||
if footer_end < file_end {
|
if footer_end < archive_end {
|
||||||
// Data from an aborted file is past the end of the footer.
|
// Data from an aborted file is past the end of the footer.
|
||||||
|
|
||||||
// Overwrite the magic so the footer is no longer valid.
|
// Overwrite the magic so the footer is no longer valid.
|
||||||
|
@ -1564,7 +1530,9 @@ impl<W: Write + Seek> ZipWriter<W> {
|
||||||
let mut dest_data = self.files[src_index].to_owned();
|
let mut dest_data = self.files[src_index].to_owned();
|
||||||
dest_data.file_name = dest_name.to_string().into();
|
dest_data.file_name = dest_name.to_string().into();
|
||||||
dest_data.file_name_raw = dest_name.to_string().into_bytes().into();
|
dest_data.file_name_raw = dest_name.to_string().into_bytes().into();
|
||||||
|
dest_data.central_header_start = 0;
|
||||||
self.insert_file_data(dest_data)?;
|
self.insert_file_data(dest_data)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1855,12 +1823,7 @@ fn update_aes_extra_data<W: Write + Seek>(writer: &mut W, file: &mut ZipFileData
|
||||||
|
|
||||||
let aes_extra_data_start = file.aes_extra_data_start as usize;
|
let aes_extra_data_start = file.aes_extra_data_start as usize;
|
||||||
let extra_field = Arc::get_mut(file.extra_field.as_mut().unwrap()).unwrap();
|
let extra_field = Arc::get_mut(file.extra_field.as_mut().unwrap()).unwrap();
|
||||||
extra_field
|
extra_field[aes_extra_data_start..aes_extra_data_start + buf.len()].copy_from_slice(&buf);
|
||||||
.splice(
|
|
||||||
aes_extra_data_start..(aes_extra_data_start + buf.len()),
|
|
||||||
buf,
|
|
||||||
)
|
|
||||||
.count();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1887,16 +1850,10 @@ fn update_local_file_header<T: Write + Seek>(writer: &mut T, file: &ZipFileData)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
|
fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
|
||||||
// buffer zip64 extra field to determine its variable length
|
let block = file.block()?;
|
||||||
let mut zip64_extra_field = [0; 28];
|
|
||||||
let zip64_extra_field_length =
|
|
||||||
write_central_zip64_extra_field(&mut zip64_extra_field.as_mut(), file)?;
|
|
||||||
let block = file.block(zip64_extra_field_length)?;
|
|
||||||
block.write(writer)?;
|
block.write(writer)?;
|
||||||
// file name
|
// file name
|
||||||
writer.write_all(&file.file_name_raw)?;
|
writer.write_all(&file.file_name_raw)?;
|
||||||
// zip64 extra field
|
|
||||||
writer.write_all(&zip64_extra_field[..zip64_extra_field_length as usize])?;
|
|
||||||
// extra field
|
// extra field
|
||||||
if let Some(extra_field) = &file.extra_field {
|
if let Some(extra_field) = &file.extra_field {
|
||||||
writer.write_all(extra_field)?;
|
writer.write_all(extra_field)?;
|
||||||
|
@ -1910,19 +1867,6 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_local_zip64_extra_field<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
|
|
||||||
// This entry in the Local header MUST include BOTH original
|
|
||||||
// and compressed file size fields.
|
|
||||||
let Some(block) = file.zip64_extra_field_block() else {
|
|
||||||
return Err(InvalidArchive(
|
|
||||||
"Attempted to write a ZIP64 extra field for a file that's within zip32 limits",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let block = block.serialize();
|
|
||||||
writer.write_all(&block)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_local_zip64_extra_field<T: Write + Seek>(
|
fn update_local_zip64_extra_field<T: Write + Seek>(
|
||||||
writer: &mut T,
|
writer: &mut T,
|
||||||
file: &ZipFileData,
|
file: &ZipFileData,
|
||||||
|
@ -1941,22 +1885,6 @@ fn update_local_zip64_extra_field<T: Write + Seek>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_central_zip64_extra_field<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<u16> {
|
|
||||||
// The order of the fields in the zip64 extended
|
|
||||||
// information record is fixed, but the fields MUST
|
|
||||||
// only appear if the corresponding Local or Central
|
|
||||||
// directory record field is set to 0xFFFF or 0xFFFFFFFF.
|
|
||||||
match file.zip64_extra_field_block() {
|
|
||||||
None => Ok(0),
|
|
||||||
Some(block) => {
|
|
||||||
let block = block.serialize();
|
|
||||||
writer.write_all(&block)?;
|
|
||||||
let len: u16 = block.len().try_into().unwrap();
|
|
||||||
Ok(len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "unreserved"))]
|
#[cfg(not(feature = "unreserved"))]
|
||||||
const EXTRA_FIELD_MAPPING: [u16; 43] = [
|
const EXTRA_FIELD_MAPPING: [u16; 43] = [
|
||||||
0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016, 0x0017,
|
0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016, 0x0017,
|
||||||
|
@ -3498,4 +3426,197 @@ mod test {
|
||||||
assert!(archive.comment().starts_with(&[33]));
|
assert!(archive.comment().starts_with(&[33]));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "bzip2")]
|
||||||
|
fn fuzz_crash_2024_07_17() -> ZipResult<()> {
|
||||||
|
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
|
||||||
|
writer.set_flush_on_finish_file(false);
|
||||||
|
let options = FileOptions {
|
||||||
|
compression_method: CompressionMethod::Bzip2,
|
||||||
|
compression_level: None,
|
||||||
|
last_modified_time: DateTime::from_date_and_time(2095, 2, 16, 21, 0, 1)?,
|
||||||
|
permissions: Some(84238341),
|
||||||
|
large_file: true,
|
||||||
|
encrypt_with: None,
|
||||||
|
extended_options: ExtendedFileOptions {
|
||||||
|
extra_data: vec![117, 99, 6, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 2, 0, 0, 0].into(),
|
||||||
|
central_extra_data: vec![].into(),
|
||||||
|
},
|
||||||
|
alignment: 65535,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
writer.start_file_from_path("", options)?;
|
||||||
|
//writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
|
||||||
|
writer.deep_copy_file_from_path("", "copy")?;
|
||||||
|
let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzz_crash_2024_07_19() -> ZipResult<()> {
|
||||||
|
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
|
||||||
|
writer.set_flush_on_finish_file(false);
|
||||||
|
let options = FileOptions {
|
||||||
|
compression_method: Stored,
|
||||||
|
compression_level: None,
|
||||||
|
last_modified_time: DateTime::from_date_and_time(1980, 6, 1, 0, 34, 47)?,
|
||||||
|
permissions: None,
|
||||||
|
large_file: true,
|
||||||
|
encrypt_with: None,
|
||||||
|
extended_options: ExtendedFileOptions {
|
||||||
|
extra_data: vec![].into(),
|
||||||
|
central_extra_data: vec![].into(),
|
||||||
|
},
|
||||||
|
alignment: 45232,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
writer.add_directory_from_path("", options)?;
|
||||||
|
writer.deep_copy_file_from_path("/", "")?;
|
||||||
|
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
|
||||||
|
writer.deep_copy_file_from_path("", "copy")?;
|
||||||
|
let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "aes-crypto")]
|
||||||
|
fn fuzz_crash_2024_07_19a() -> ZipResult<()> {
|
||||||
|
use crate::write::EncryptWith::Aes;
|
||||||
|
use crate::AesMode::Aes128;
|
||||||
|
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
|
||||||
|
writer.set_flush_on_finish_file(false);
|
||||||
|
let options = FileOptions {
|
||||||
|
compression_method: Stored,
|
||||||
|
compression_level: None,
|
||||||
|
last_modified_time: DateTime::from_date_and_time(2107, 6, 5, 13, 0, 21)?,
|
||||||
|
permissions: None,
|
||||||
|
large_file: true,
|
||||||
|
encrypt_with: Some(Aes {
|
||||||
|
mode: Aes128,
|
||||||
|
password: "",
|
||||||
|
}),
|
||||||
|
extended_options: ExtendedFileOptions {
|
||||||
|
extra_data: vec![3, 0, 4, 0, 209, 53, 53, 8, 2, 61, 0, 0].into(),
|
||||||
|
central_extra_data: vec![].into(),
|
||||||
|
},
|
||||||
|
alignment: 65535,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
writer.start_file_from_path("", options)?;
|
||||||
|
let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzz_crash_2024_07_20() -> ZipResult<()> {
|
||||||
|
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
|
||||||
|
writer.set_flush_on_finish_file(true);
|
||||||
|
let options = FileOptions {
|
||||||
|
compression_method: Stored,
|
||||||
|
compression_level: None,
|
||||||
|
last_modified_time: DateTime::from_date_and_time(2041, 8, 2, 19, 38, 0)?,
|
||||||
|
permissions: None,
|
||||||
|
large_file: false,
|
||||||
|
encrypt_with: None,
|
||||||
|
extended_options: ExtendedFileOptions {
|
||||||
|
extra_data: vec![].into(),
|
||||||
|
central_extra_data: vec![].into(),
|
||||||
|
},
|
||||||
|
alignment: 0,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
writer.add_directory_from_path("\0\0\0\0\0\0\07黻", options)?;
|
||||||
|
let sub_writer = {
|
||||||
|
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
|
||||||
|
writer.set_flush_on_finish_file(false);
|
||||||
|
let options = FileOptions {
|
||||||
|
compression_method: Stored,
|
||||||
|
compression_level: None,
|
||||||
|
last_modified_time: DateTime::default(),
|
||||||
|
permissions: None,
|
||||||
|
large_file: false,
|
||||||
|
encrypt_with: None,
|
||||||
|
extended_options: ExtendedFileOptions {
|
||||||
|
extra_data: vec![].into(),
|
||||||
|
central_extra_data: vec![].into(),
|
||||||
|
},
|
||||||
|
alignment: 4,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
writer.add_directory_from_path("\0\0\0黻", options)?;
|
||||||
|
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
|
||||||
|
writer.abort_file()?;
|
||||||
|
let options = FileOptions {
|
||||||
|
compression_method: Stored,
|
||||||
|
compression_level: None,
|
||||||
|
last_modified_time: DateTime::from_date_and_time(1980, 1, 1, 0, 7, 0)?,
|
||||||
|
permissions: Some(2663103419),
|
||||||
|
large_file: false,
|
||||||
|
encrypt_with: None,
|
||||||
|
extended_options: ExtendedFileOptions {
|
||||||
|
extra_data: vec![].into(),
|
||||||
|
central_extra_data: vec![].into(),
|
||||||
|
},
|
||||||
|
alignment: 32256,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
writer.add_directory_from_path("\0", options)?;
|
||||||
|
writer = ZipWriter::new_append(writer.finish()?)?;
|
||||||
|
writer
|
||||||
|
};
|
||||||
|
writer.merge_archive(sub_writer.finish_into_readable()?)?;
|
||||||
|
let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzz_crash_2024_07_21() -> ZipResult<()> {
|
||||||
|
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
|
||||||
|
let sub_writer = {
|
||||||
|
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
|
||||||
|
writer.add_directory_from_path(
|
||||||
|
"",
|
||||||
|
FileOptions {
|
||||||
|
compression_method: Stored,
|
||||||
|
compression_level: None,
|
||||||
|
last_modified_time: DateTime::from_date_and_time(2105, 8, 1, 15, 0, 0)?,
|
||||||
|
permissions: None,
|
||||||
|
large_file: false,
|
||||||
|
encrypt_with: None,
|
||||||
|
extended_options: ExtendedFileOptions {
|
||||||
|
extra_data: vec![].into(),
|
||||||
|
central_extra_data: vec![].into(),
|
||||||
|
},
|
||||||
|
alignment: 0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
writer.abort_file()?;
|
||||||
|
let mut writer = ZipWriter::new_append(writer.finish()?)?;
|
||||||
|
writer.add_directory_from_path(
|
||||||
|
"",
|
||||||
|
FileOptions {
|
||||||
|
compression_method: Stored,
|
||||||
|
compression_level: None,
|
||||||
|
last_modified_time: DateTime::default(),
|
||||||
|
permissions: None,
|
||||||
|
large_file: false,
|
||||||
|
encrypt_with: None,
|
||||||
|
extended_options: ExtendedFileOptions {
|
||||||
|
extra_data: vec![].into(),
|
||||||
|
central_extra_data: vec![].into(),
|
||||||
|
},
|
||||||
|
alignment: 16,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
ZipWriter::new_append(writer.finish()?)?
|
||||||
|
};
|
||||||
|
writer.merge_archive(sub_writer.finish_into_readable()?)?;
|
||||||
|
let writer = ZipWriter::new_append(writer.finish()?)?;
|
||||||
|
let _ = writer.finish_into_readable()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue