diff --git a/src/types.rs b/src/types.rs index ac534caff..30fcb32fc 100644 --- a/src/types.rs +++ b/src/types.rs @@ -849,9 +849,16 @@ impl ZipFileData { } else { 0 }; + + let using_data_descriptor_bit = if self.using_data_descriptor { + 1u16 << 3 + } else { + 0 + }; + let encrypted_bit: u16 = if self.encrypted { 1u16 << 0 } else { 0 }; - utf8_bit | encrypted_bit + utf8_bit | using_data_descriptor_bit | encrypted_bit } fn clamp_size_field(&self, field: u64) -> u32 { @@ -863,8 +870,14 @@ impl ZipFileData { } pub(crate) fn local_block(&self) -> ZipResult { - let compressed_size: u32 = self.clamp_size_field(self.compressed_size); - let uncompressed_size: u32 = self.clamp_size_field(self.uncompressed_size); + let (compressed_size, uncompressed_size) = if self.using_data_descriptor { + (0, 0) + } else { + ( + self.clamp_size_field(self.compressed_size), + self.clamp_size_field(self.uncompressed_size), + ) + }; let extra_field_length: u16 = self .extra_field_len() .try_into() diff --git a/src/write.rs b/src/write.rs index 0afd1402c..ea835c47f 100644 --- a/src/write.rs +++ b/src/write.rs @@ -164,6 +164,7 @@ pub(crate) mod zip_writer { pub(super) comment: Box<[u8]>, pub(super) zip64_comment: Option>, pub(super) flush_on_finish_file: bool, + pub(super) seek_possible: bool, } impl Debug for ZipWriter { @@ -634,7 +635,6 @@ impl ZipWriter { /// This uses the given read configuration to initially read the archive. pub fn new_append_with_config(config: Config, mut readwriter: A) -> ZipResult> { readwriter.seek(SeekFrom::Start(0))?; - let shared = ZipArchive::get_metadata(config, &mut readwriter)?; Ok(ZipWriter { @@ -646,6 +646,7 @@ impl ZipWriter { zip64_comment: shared.zip64_comment, writing_raw: true, // avoid recomputing the last file's header flush_on_finish_file: false, + seek_possible: true, }) } @@ -800,6 +801,7 @@ impl ZipWriter { comment: Box::new([]), zip64_comment: None, flush_on_finish_file: false, + seek_possible: true, } } @@ -989,6 +991,7 @@ impl ZipWriter { aes_mode, &extra_data, ); + file.using_data_descriptor = !self.seek_possible; 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)?; @@ -1097,8 +1100,12 @@ impl ZipWriter { 0 }; update_aes_extra_data(writer, file)?; - update_local_file_header(writer, file)?; - writer.seek(SeekFrom::Start(file_end))?; + if file.using_data_descriptor { + write_data_descriptor(writer, file)?; + } else { + update_local_file_header(writer, file)?; + writer.seek(SeekFrom::Start(file_end))?; + } } if self.flush_on_finish_file { let result = writer.flush(); @@ -1622,6 +1629,21 @@ impl ZipWriter { } } +impl ZipWriter> { + pub fn new_stream(inner: W) -> ZipWriter> { + ZipWriter { + inner: Storer(MaybeEncrypted::Unencrypted(StreamWriter::new(inner))), + files: IndexMap::new(), + stats: Default::default(), + writing_to_file: false, + writing_raw: false, + comment: Box::new([]), + flush_on_finish_file: false, + seek_possible: false, + } + } +} + impl Drop for ZipWriter { fn drop(&mut self) { if !self.inner.is_closed() { @@ -1909,6 +1931,26 @@ fn update_aes_extra_data(writer: &mut W, file: &mut ZipFileData Ok(()) } +fn write_data_descriptor(writer: &mut T, file: &ZipFileData) -> ZipResult<()> { + writer.write_u32_le(file.crc32)?; + if file.large_file { + writer.write_u64_le(file.compressed_size)?; + writer.write_u64_le(file.uncompressed_size)?; + } else { + // check compressed size as well as it can also be slightly larger than uncompressed size + if file.compressed_size > spec::ZIP64_BYTES_THR { + return Err(ZipError::Io(io::Error::new( + io::ErrorKind::Other, + "Large file option has not been set", + ))); + } + + writer.write_u32_le(file.compressed_size as u32)?; + writer.write_u32_le(file.uncompressed_size as u32)?; + } + Ok(()) +} + fn update_local_file_header( writer: &mut T, file: &mut ZipFileData, @@ -1979,6 +2021,41 @@ fn update_local_zip64_extra_field( Ok(()) } +pub struct StreamWriter { + inner: W, + bytes_written: u64, +} + +impl StreamWriter { + pub fn new(inner: W) -> StreamWriter { + Self { + inner, + bytes_written: 0, + } + } +} + +impl Write for StreamWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let bytes_written = self.inner.write(buf)?; + self.bytes_written += bytes_written as u64; + Ok(bytes_written) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl Seek for StreamWriter { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + match pos { + SeekFrom::Current(0) | SeekFrom::End(0) => Ok(self.bytes_written), + _ => panic!("Seek is not supported, trying to seek to {:?}", pos), + } + } +} + #[cfg(not(feature = "unreserved"))] const EXTRA_FIELD_MAPPING: [u16; 43] = [ 0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016, 0x0017,