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 72a15e087..5e806e67e 100644 --- a/src/write.rs +++ b/src/write.rs @@ -22,8 +22,8 @@ use std::default::Default; use std::fmt::{Debug, Formatter}; use std::io; use std::io::prelude::*; -use std::io::Cursor; use std::io::{BufReader, SeekFrom}; +use std::io::{Cursor, ErrorKind}; use std::marker::PhantomData; use std::mem; use std::str::{from_utf8, Utf8Error}; @@ -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 { @@ -625,7 +626,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 { @@ -637,6 +637,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, }) } @@ -794,6 +795,7 @@ impl ZipWriter { comment: Box::new([]), zip64_comment: None, flush_on_finish_file: false, + seek_possible: true, } } @@ -980,6 +982,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)?; @@ -1088,8 +1091,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(); @@ -1617,6 +1624,25 @@ impl ZipWriter { } } +impl ZipWriter> { + /// Creates a writer that doesn't require the inner writer to implement [Seek], but where + /// operations that would overwrite previously-written bytes or cause subsequent operations to + /// do so (such as `abort_file`) will always return an error. + 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([]), + zip64_comment: None, + flush_on_finish_file: false, + seek_possible: false, + } + } +} + impl Drop for ZipWriter { fn drop(&mut self) { if !self.inner.is_closed() { @@ -1911,6 +1937,25 @@ 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::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, @@ -1980,6 +2025,53 @@ fn update_local_zip64_extra_field( Ok(()) } +/// Wrapper around a [Write] implementation that implements the [Seek] trait, but where seeking +/// returns an error unless it's a no-op. +pub struct StreamWriter { + inner: W, + bytes_written: u64, +} + +impl StreamWriter { + /// Creates an instance wrapping the provided inner writer. + 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) => return Ok(self.bytes_written), + SeekFrom::Start(x) => { + if x == self.bytes_written { + return Ok(self.bytes_written); + } + } + _ => {} + } + Err(io::Error::new( + ErrorKind::Unsupported, + "seek is not supported", + )) + } +} + #[cfg(not(feature = "unreserved"))] const EXTRA_FIELD_MAPPING: [u16; 43] = [ 0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016, 0x0017,