Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1001,7 +1001,7 @@ impl<W: Write + Seek> ZipWriter<W> {
aes_mode,
&extra_data,
);
file.using_data_descriptor = !self.seek_possible;
file.using_data_descriptor = !self.seek_possible || matches!(options.encrypt_with, Some(EncryptWith::ZipCrypto(..)));
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)?;
Expand Down Expand Up @@ -1034,14 +1034,31 @@ impl<W: Write + Seek> ZipWriter<W> {
self.inner = Storer(MaybeEncrypted::Aes(aeswriter));
}
Some(EncryptWith::ZipCrypto(keys, ..)) => {
let file = &mut self.files[index];
// With ZipCrypto, we _need_ to use a data descriptor so that
// we can initialize the stream properly.
let mut zipwriter = crate::zipcrypto::ZipCryptoWriter {
writer: mem::replace(&mut self.inner, Closed).unwrap(),
buffer: vec![],
keys,
};
self.stats.start = zipwriter.writer.stream_position()?;
// crypto_header is counted as part of the data
let crypto_header = [0u8; 12];
let mut crypto_header = [0u8; 12];
// The last two bytes of the header should either be the CRC of
// the file _OR_ the last timepart of the last modified time if
// a data descriptor is used.
//
// However, this header is encrypted, and ZipCrypto is not a
// seekable encryption algorithm - an earlier plaintext byte
// will have an impact on the later values.
//
// This makes it impossible to write the file without first
// calculating its CRC. To avoid having to read the file twice
// or keeping the entire file in memory, we force all ZipCrypto
// files to use a data descriptor. This way, we can use the
// local_modified_time as a password check byte instead of the
// CRC.
crypto_header[10..=11].copy_from_slice(&file.last_modified_time.unwrap_or_else(DateTime::default_for_write).timepart().to_le_bytes());
let result = zipwriter.write_all(&crypto_header);
self.ok_or_abort_file(result)?;
self.inner = Storer(MaybeEncrypted::ZipCrypto(zipwriter));
Expand Down Expand Up @@ -1133,8 +1150,7 @@ impl<W: Write + Seek> ZipWriter<W> {
self.inner = Storer(MaybeEncrypted::Unencrypted(writer.finish()?));
}
Storer(MaybeEncrypted::ZipCrypto(writer)) => {
let crc32 = self.stats.hasher.clone().finalize();
self.inner = Storer(MaybeEncrypted::Unencrypted(writer.finish(crc32)?))
self.inner = Storer(MaybeEncrypted::Unencrypted(writer.finish()?))
}
Storer(MaybeEncrypted::Unencrypted(w)) => {
self.inner = Storer(MaybeEncrypted::Unencrypted(w))
Expand Down
17 changes: 7 additions & 10 deletions src/zipcrypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,28 +159,25 @@ impl<R: std::io::Read> ZipCryptoReader<R> {
#[allow(unused)]
pub(crate) struct ZipCryptoWriter<W> {
pub(crate) writer: W,
pub(crate) buffer: Vec<u8>,
pub(crate) keys: ZipCryptoKeys,
}
impl<W: std::io::Write> ZipCryptoWriter<W> {
#[allow(unused)]
pub(crate) fn finish(mut self, crc32: u32) -> std::io::Result<W> {
self.buffer[11] = (crc32 >> 24) as u8;
for byte in self.buffer.iter_mut() {
*byte = self.keys.encrypt_byte(*byte);
}
self.writer.write_all(&self.buffer)?;
self.writer.flush()?;
pub(crate) fn finish(mut self) -> std::io::Result<W> {
Ok(self.writer)
}
}
impl<W: std::io::Write> std::io::Write for ZipCryptoWriter<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.buffer.extend_from_slice(buf);
let mut buf = buf.to_vec();
for byte in buf.iter_mut() {
*byte = self.keys.encrypt_byte(*byte);
}
self.writer.write_all(&buf)?;
Comment on lines +172 to +176
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of write allocates a new Vec on every call via buf.to_vec(). This can be inefficient, especially when write is called frequently (e.g., by io::copy), as it causes a heap allocation for each call.

To improve performance, you could use a stack-allocated buffer and process the input in chunks. This avoids repeated heap allocations.

Suggested change
let mut buf = buf.to_vec();
for byte in buf.iter_mut() {
*byte = self.keys.encrypt_byte(*byte);
}
self.writer.write_all(&buf)?;
const CHUNK_SIZE: usize = 4096;
let mut temp_buf = [0u8; CHUNK_SIZE];
for chunk in buf.chunks(CHUNK_SIZE) {
let encrypted_chunk = &mut temp_buf[..chunk.len()];
for (i, &byte) in chunk.iter().enumerate() {
encrypted_chunk[i] = self.keys.encrypt_byte(byte);
}
self.writer.write_all(encrypted_chunk)?;
}

Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
self.writer.flush()
}
}

Expand Down
3 changes: 1 addition & 2 deletions tests/zip_crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@ use std::io::Cursor;
use zip::result::ZipError;

#[test]
#[cfg(feature = "deflate-flate2")]
fn encrypting_file() {
use std::io::{Read, Write};
use zip::unstable::write::FileOptionsExt;
let mut buf = vec![0; 2048];
let mut archive = zip::write::ZipWriter::new(Cursor::new(&mut buf));
let mut archive = zip::write::ZipWriter::new_stream(Cursor::new(&mut buf));
archive
.start_file(
"name",
Expand Down