diff --git a/fuzz_write/src/main.rs b/fuzz_write/src/main.rs index 69f915f41..438c7d207 100644 --- a/fuzz_write/src/main.rs +++ b/fuzz_write/src/main.rs @@ -7,6 +7,7 @@ use std::io::Write as IoWrite; use std::io::{Cursor, Seek, SeekFrom}; use std::path::PathBuf; use tikv_jemallocator::Jemalloc; +use zip::read::read_zipfile_from_stream; use zip::result::{ZipError, ZipResult}; use zip::unstable::path_to_string; use zip::write::FullFileOptions; @@ -61,6 +62,28 @@ impl FileOperation<'_> { _ => Some(self.path.to_owned()), } } + + fn is_streamable(&self) -> bool { + match &self.basic { + BasicFileOperation::WriteNormalFile { options, .. } + | BasicFileOperation::WriteDirectory(options) + | BasicFileOperation::WriteSymlinkWithTarget { options, .. } => { + !options.has_encryption() + } + BasicFileOperation::ShallowCopy(base) => base.is_streamable(), + BasicFileOperation::DeepCopy(base) => base.is_streamable(), + BasicFileOperation::MergeWithOtherFile { + operations, + initial_junk, + } => { + if !initial_junk.is_empty() { + return false; + } + operations.iter().all(|(op, _)| op.is_streamable()) + } + _ => true, + } + } } #[derive(Arbitrary, Clone)] @@ -293,6 +316,9 @@ fn do_operation( impl FuzzTestCase<'_> { fn execute(self, stringifier: &mut impl Write, panic_on_error: bool) -> ZipResult<()> { + // Indicates the starting position if we use read_zipfile_from_stream at the end. + let junk_len = self.initial_junk.len(); + let mut initial_junk = Cursor::new(self.initial_junk.into_vec()); initial_junk.seek(SeekFrom::End(0))?; let mut writer = zip::ZipWriter::new(initial_junk); @@ -303,6 +329,7 @@ impl FuzzTestCase<'_> { final_reopen = true; } } + let streamable = self.operations.iter().all(|(op, _)| op.is_streamable()); #[allow(unknown_lints)] #[allow(boxed_slice_into_iter)] for (operation, abort) in self.operations.into_vec().into_iter() { @@ -316,7 +343,18 @@ impl FuzzTestCase<'_> { panic_on_error, ); } - if final_reopen { + if streamable { + writeln!( + stringifier, + "let mut stream = writer.finish()?;\n\ + stream.seek(SeekFrom::Start({junk_len}))?;\n\ + while read_zipfile_from_stream(&mut stream)?.is_some() {{}}" + ) + .map_err(|_| ZipError::InvalidArchive("Failed to read from stream".into()))?; + let mut stream = writer.finish()?; + stream.seek(SeekFrom::Start(junk_len as u64))?; + while read_zipfile_from_stream(&mut stream)?.is_some() {} + } else if final_reopen { writeln!(stringifier, "let _ = writer.finish_into_readable()?;") .map_err(|_| ZipError::InvalidArchive("".into()))?; let _ = writer.finish_into_readable()?; diff --git a/src/write.rs b/src/write.rs index 9c324e460..0c92603a9 100644 --- a/src/write.rs +++ b/src/write.rs @@ -425,6 +425,18 @@ impl FileOptions<'_, T> { *self.permissions.get_or_insert(0o644) |= ffi::S_IFREG; } + /// Indicates whether this file will be encrypted (whether with AES or ZipCrypto). + pub const fn has_encryption(&self) -> bool { + #[cfg(feature = "aes-crypto")] + { + self.encrypt_with.is_some() || self.aes_mode.is_some() + } + #[cfg(not(feature = "aes-crypto"))] + { + self.encrypt_with.is_some() + } + } + /// Set the compression method for the new file /// /// The default is `CompressionMethod::Deflated` if it is enabled. If not,