Skip to content

Commit cffb488

Browse files
committed
Write ZIP file to stream
1 parent 6d39456 commit cffb488

File tree

2 files changed

+96
-5
lines changed

2 files changed

+96
-5
lines changed

src/types.rs

+16-3
Original file line numberDiff line numberDiff line change
@@ -762,9 +762,16 @@ impl ZipFileData {
762762
} else {
763763
0
764764
};
765+
766+
let using_data_descriptor_bit = if self.using_data_descriptor {
767+
1u16 << 3
768+
} else {
769+
0
770+
};
771+
765772
let encrypted_bit: u16 = if self.encrypted { 1u16 << 0 } else { 0 };
766773

767-
utf8_bit | encrypted_bit
774+
utf8_bit | using_data_descriptor_bit | encrypted_bit
768775
}
769776

770777
fn clamp_size_field(&self, field: u64) -> u32 {
@@ -776,8 +783,14 @@ impl ZipFileData {
776783
}
777784

778785
pub(crate) fn local_block(&self) -> ZipResult<ZipLocalEntryBlock> {
779-
let compressed_size: u32 = self.clamp_size_field(self.compressed_size);
780-
let uncompressed_size: u32 = self.clamp_size_field(self.uncompressed_size);
786+
let (compressed_size, uncompressed_size) = if self.using_data_descriptor {
787+
(0, 0)
788+
} else {
789+
(
790+
self.clamp_size_field(self.compressed_size),
791+
self.clamp_size_field(self.uncompressed_size),
792+
)
793+
};
781794
let extra_field_length: u16 = self
782795
.extra_field_len()
783796
.try_into()

src/write.rs

+80-2
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ pub(crate) mod zip_writer {
161161
pub(super) writing_raw: bool,
162162
pub(super) comment: Box<[u8]>,
163163
pub(super) flush_on_finish_file: bool,
164+
pub(super) seek_possible: bool,
164165
}
165166

166167
impl<W: Write + Seek> Debug for ZipWriter<W> {
@@ -637,6 +638,7 @@ impl<A: Read + Write + Seek> ZipWriter<A> {
637638
comment: footer.zip_file_comment,
638639
writing_raw: true, // avoid recomputing the last file's header
639640
flush_on_finish_file: false,
641+
seek_possible: true,
640642
})
641643
} else {
642644
Err(InvalidArchive("No central-directory end header found"))
@@ -795,6 +797,7 @@ impl<W: Write + Seek> ZipWriter<W> {
795797
writing_raw: false,
796798
comment: Box::new([]),
797799
flush_on_finish_file: false,
800+
seek_possible: true,
798801
}
799802
}
800803

@@ -955,6 +958,7 @@ impl<W: Write + Seek> ZipWriter<W> {
955958
aes_mode,
956959
&extra_data,
957960
);
961+
file.using_data_descriptor = !self.seek_possible;
958962
file.version_made_by = file.version_made_by.max(file.version_needed() as u8);
959963
file.extra_data_start = Some(header_end);
960964
let index = self.insert_file_data(file)?;
@@ -1063,8 +1067,12 @@ impl<W: Write + Seek> ZipWriter<W> {
10631067
0
10641068
};
10651069
update_aes_extra_data(writer, file)?;
1066-
update_local_file_header(writer, file)?;
1067-
writer.seek(SeekFrom::Start(file_end))?;
1070+
if file.using_data_descriptor {
1071+
write_data_descriptor(writer, file)?;
1072+
} else {
1073+
update_local_file_header(writer, file)?;
1074+
writer.seek(SeekFrom::Start(file_end))?;
1075+
}
10681076
}
10691077
if self.flush_on_finish_file {
10701078
let result = writer.flush();
@@ -1550,6 +1558,21 @@ impl<W: Write + Seek> ZipWriter<W> {
15501558
}
15511559
}
15521560

1561+
impl<W: Write> ZipWriter<StreamWriter<W>> {
1562+
pub fn new_stream(inner: W) -> ZipWriter<StreamWriter<W>> {
1563+
ZipWriter {
1564+
inner: Storer(MaybeEncrypted::Unencrypted(StreamWriter::new(inner))),
1565+
files: IndexMap::new(),
1566+
stats: Default::default(),
1567+
writing_to_file: false,
1568+
writing_raw: false,
1569+
comment: Box::new([]),
1570+
flush_on_finish_file: false,
1571+
seek_possible: false,
1572+
}
1573+
}
1574+
}
1575+
15531576
impl<W: Write + Seek> Drop for ZipWriter<W> {
15541577
fn drop(&mut self) {
15551578
if !self.inner.is_closed() {
@@ -1828,6 +1851,26 @@ fn update_aes_extra_data<W: Write + Seek>(writer: &mut W, file: &mut ZipFileData
18281851
Ok(())
18291852
}
18301853

1854+
fn write_data_descriptor<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
1855+
writer.write_u32_le(file.crc32)?;
1856+
if file.large_file {
1857+
writer.write_u64_le(file.compressed_size)?;
1858+
writer.write_u64_le(file.uncompressed_size)?;
1859+
} else {
1860+
// check compressed size as well as it can also be slightly larger than uncompressed size
1861+
if file.compressed_size > spec::ZIP64_BYTES_THR {
1862+
return Err(ZipError::Io(io::Error::new(
1863+
io::ErrorKind::Other,
1864+
"Large file option has not been set",
1865+
)));
1866+
}
1867+
1868+
writer.write_u32_le(file.compressed_size as u32)?;
1869+
writer.write_u32_le(file.uncompressed_size as u32)?;
1870+
}
1871+
Ok(())
1872+
}
1873+
18311874
fn update_local_file_header<T: Write + Seek>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
18321875
const CRC32_OFFSET: u64 = 14;
18331876
writer.seek(SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
@@ -1885,6 +1928,41 @@ fn update_local_zip64_extra_field<T: Write + Seek>(
18851928
Ok(())
18861929
}
18871930

1931+
pub struct StreamWriter<W: Write> {
1932+
inner: W,
1933+
bytes_written: u64,
1934+
}
1935+
1936+
impl<W: Write> StreamWriter<W> {
1937+
pub fn new(inner: W) -> StreamWriter<W> {
1938+
Self {
1939+
inner,
1940+
bytes_written: 0,
1941+
}
1942+
}
1943+
}
1944+
1945+
impl<W: Write> Write for StreamWriter<W> {
1946+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1947+
let bytes_written = self.inner.write(buf)?;
1948+
self.bytes_written += bytes_written as u64;
1949+
Ok(bytes_written)
1950+
}
1951+
1952+
fn flush(&mut self) -> io::Result<()> {
1953+
self.inner.flush()
1954+
}
1955+
}
1956+
1957+
impl<W: Write> Seek for StreamWriter<W> {
1958+
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
1959+
match pos {
1960+
SeekFrom::Current(0) | SeekFrom::End(0) => Ok(self.bytes_written),
1961+
_ => panic!("Seek is not supported, trying to seek to {:?}", pos),
1962+
}
1963+
}
1964+
}
1965+
18881966
#[cfg(not(feature = "unreserved"))]
18891967
const EXTRA_FIELD_MAPPING: [u16; 43] = [
18901968
0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016, 0x0017,

0 commit comments

Comments
 (0)