1#[cfg(feature = "aes-crypto")]
4use crate::aes::AesWriter;
5use crate::compression::CompressionMethod;
6use crate::read::{parse_single_extra_field, Config, ZipArchive, ZipFile};
7use crate::result::{invalid, ZipError, ZipResult};
8use crate::spec::{self, FixedSizeBlock, Zip32CDEBlock};
9#[cfg(feature = "aes-crypto")]
10use crate::types::AesMode;
11use crate::types::{
12 ffi, AesVendorVersion, DateTime, Zip64ExtraFieldBlock, ZipFileData, ZipLocalEntryBlock,
13 ZipRawValues, MIN_VERSION,
14};
15use crate::write::ffi::S_IFLNK;
16#[cfg(feature = "deflate-zopfli")]
17use core::num::NonZeroU64;
18use crc32fast::Hasher;
19use indexmap::IndexMap;
20use std::borrow::ToOwned;
21use std::default::Default;
22use std::fmt::{Debug, Formatter};
23use std::io;
24use std::io::prelude::*;
25use std::io::{BufReader, SeekFrom};
26use std::io::{Cursor, ErrorKind};
27use std::marker::PhantomData;
28use std::mem;
29use std::str::{from_utf8, Utf8Error};
30use std::sync::Arc;
31
32#[cfg(feature = "deflate-flate2")]
33use flate2::{write::DeflateEncoder, Compression};
34
35#[cfg(feature = "bzip2")]
36use bzip2::write::BzEncoder;
37
38#[cfg(feature = "deflate-zopfli")]
39use zopfli::Options;
40
41#[cfg(feature = "deflate-zopfli")]
42use std::io::BufWriter;
43use std::mem::size_of;
44use std::path::Path;
45
46#[cfg(feature = "zstd")]
47use zstd::stream::write::Encoder as ZstdEncoder;
48
49enum MaybeEncrypted<W> {
50 Unencrypted(W),
51 #[cfg(feature = "aes-crypto")]
52 Aes(AesWriter<W>),
53 ZipCrypto(crate::zipcrypto::ZipCryptoWriter<W>),
54}
55
56impl<W> Debug for MaybeEncrypted<W> {
57 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
58 f.write_str(match self {
60 MaybeEncrypted::Unencrypted(_) => "Unencrypted",
61 #[cfg(feature = "aes-crypto")]
62 MaybeEncrypted::Aes(_) => "AES",
63 MaybeEncrypted::ZipCrypto(_) => "ZipCrypto",
64 })
65 }
66}
67
68impl<W: Write> Write for MaybeEncrypted<W> {
69 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
70 match self {
71 MaybeEncrypted::Unencrypted(w) => w.write(buf),
72 #[cfg(feature = "aes-crypto")]
73 MaybeEncrypted::Aes(w) => w.write(buf),
74 MaybeEncrypted::ZipCrypto(w) => w.write(buf),
75 }
76 }
77 fn flush(&mut self) -> io::Result<()> {
78 match self {
79 MaybeEncrypted::Unencrypted(w) => w.flush(),
80 #[cfg(feature = "aes-crypto")]
81 MaybeEncrypted::Aes(w) => w.flush(),
82 MaybeEncrypted::ZipCrypto(w) => w.flush(),
83 }
84 }
85}
86
87enum GenericZipWriter<W: Write + Seek> {
88 Closed,
89 Storer(MaybeEncrypted<W>),
90 #[cfg(feature = "deflate-flate2")]
91 Deflater(DeflateEncoder<MaybeEncrypted<W>>),
92 #[cfg(feature = "deflate-zopfli")]
93 ZopfliDeflater(zopfli::DeflateEncoder<MaybeEncrypted<W>>),
94 #[cfg(feature = "deflate-zopfli")]
95 BufferedZopfliDeflater(BufWriter<zopfli::DeflateEncoder<MaybeEncrypted<W>>>),
96 #[cfg(feature = "bzip2")]
97 Bzip2(BzEncoder<MaybeEncrypted<W>>),
98 #[cfg(feature = "zstd")]
99 Zstd(ZstdEncoder<'static, MaybeEncrypted<W>>),
100 #[cfg(feature = "xz")]
101 Xz(Box<lzma_rust2::XzWriter<MaybeEncrypted<W>>>),
102 #[cfg(feature = "ppmd")]
103 Ppmd(Box<ppmd_rust::Ppmd8Encoder<MaybeEncrypted<W>>>),
104}
105
106impl<W: Write + Seek> Debug for GenericZipWriter<W> {
107 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
108 match self {
109 Closed => f.write_str("Closed"),
110 Storer(w) => f.write_fmt(format_args!("Storer({w:?})")),
111 #[cfg(feature = "deflate-flate2")]
112 GenericZipWriter::Deflater(w) => {
113 f.write_fmt(format_args!("Deflater({:?})", w.get_ref()))
114 }
115 #[cfg(feature = "deflate-zopfli")]
116 GenericZipWriter::ZopfliDeflater(_) => f.write_str("ZopfliDeflater"),
117 #[cfg(feature = "deflate-zopfli")]
118 GenericZipWriter::BufferedZopfliDeflater(_) => f.write_str("BufferedZopfliDeflater"),
119 #[cfg(feature = "bzip2")]
120 GenericZipWriter::Bzip2(w) => f.write_fmt(format_args!("Bzip2({:?})", w.get_ref())),
121 #[cfg(feature = "zstd")]
122 GenericZipWriter::Zstd(w) => f.write_fmt(format_args!("Zstd({:?})", w.get_ref())),
123 #[cfg(feature = "xz")]
124 GenericZipWriter::Xz(w) => f.write_fmt(format_args!("Xz({:?})", w.inner())),
125 #[cfg(feature = "ppmd")]
126 GenericZipWriter::Ppmd(_) => f.write_fmt(format_args!("Ppmd8Encoder")),
127 }
128 }
129}
130
131pub(crate) mod zip_writer {
133 use super::*;
134 pub struct ZipWriter<W: Write + Seek> {
166 pub(super) inner: GenericZipWriter<W>,
167 pub(super) files: IndexMap<Box<str>, ZipFileData>,
168 pub(super) stats: ZipWriterStats,
169 pub(super) writing_to_file: bool,
170 pub(super) writing_raw: bool,
171 pub(super) comment: Box<[u8]>,
172 pub(super) zip64_comment: Option<Box<[u8]>>,
173 pub(super) flush_on_finish_file: bool,
174 pub(super) seek_possible: bool,
175 }
176
177 impl<W: Write + Seek> Debug for ZipWriter<W> {
178 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
179 f.write_fmt(format_args!(
180 "ZipWriter {{files: {:?}, stats: {:?}, writing_to_file: {}, writing_raw: {}, comment: {:?}, flush_on_finish_file: {}}}",
181 self.files, self.stats, self.writing_to_file, self.writing_raw,
182 self.comment, self.flush_on_finish_file))
183 }
184 }
185}
186#[doc(inline)]
187pub use self::sealed::FileOptionExtension;
188use crate::result::ZipError::UnsupportedArchive;
189use crate::unstable::path_to_string;
190use crate::unstable::LittleEndianWriteExt;
191use crate::write::GenericZipWriter::{Closed, Storer};
192use crate::zipcrypto::ZipCryptoKeys;
193use crate::CompressionMethod::Stored;
194pub use zip_writer::ZipWriter;
195
196#[derive(Default, Debug)]
197struct ZipWriterStats {
198 hasher: Hasher,
199 start: u64,
200 bytes_written: u64,
201}
202
203mod sealed {
204 use std::sync::Arc;
205
206 use super::ExtendedFileOptions;
207
208 pub trait Sealed {}
209 #[doc(hidden)]
211 pub trait FileOptionExtension: Default + Sealed {
212 fn extra_data(&self) -> Option<&Arc<Vec<u8>>>;
214 fn central_extra_data(&self) -> Option<&Arc<Vec<u8>>>;
216 }
217 impl Sealed for () {}
218 impl FileOptionExtension for () {
219 fn extra_data(&self) -> Option<&Arc<Vec<u8>>> {
220 None
221 }
222 fn central_extra_data(&self) -> Option<&Arc<Vec<u8>>> {
223 None
224 }
225 }
226 impl Sealed for ExtendedFileOptions {}
227
228 impl FileOptionExtension for ExtendedFileOptions {
229 fn extra_data(&self) -> Option<&Arc<Vec<u8>>> {
230 Some(&self.extra_data)
231 }
232 fn central_extra_data(&self) -> Option<&Arc<Vec<u8>>> {
233 Some(&self.central_extra_data)
234 }
235 }
236}
237
238#[derive(Copy, Clone, Debug, Eq, PartialEq)]
239pub(crate) enum EncryptWith<'k> {
240 #[cfg(feature = "aes-crypto")]
241 Aes {
242 mode: AesMode,
243 password: &'k str,
244 },
245 ZipCrypto(ZipCryptoKeys, PhantomData<&'k ()>),
246}
247
248#[cfg(fuzzing)]
249impl<'a> arbitrary::Arbitrary<'a> for EncryptWith<'a> {
250 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
251 #[cfg(feature = "aes-crypto")]
252 if bool::arbitrary(u)? {
253 return Ok(EncryptWith::Aes {
254 mode: AesMode::arbitrary(u)?,
255 password: u.arbitrary::<&str>()?,
256 });
257 }
258
259 Ok(EncryptWith::ZipCrypto(
260 ZipCryptoKeys::arbitrary(u)?,
261 PhantomData,
262 ))
263 }
264}
265
266#[derive(Clone, Debug, Copy, Eq, PartialEq)]
268pub struct FileOptions<'k, T: FileOptionExtension> {
269 pub(crate) compression_method: CompressionMethod,
270 pub(crate) compression_level: Option<i64>,
271 pub(crate) last_modified_time: DateTime,
272 pub(crate) permissions: Option<u32>,
273 pub(crate) large_file: bool,
274 pub(crate) encrypt_with: Option<EncryptWith<'k>>,
275 pub(crate) extended_options: T,
276 pub(crate) alignment: u16,
277 #[cfg(feature = "deflate-zopfli")]
278 pub(super) zopfli_buffer_size: Option<usize>,
279 #[cfg(feature = "aes-crypto")]
280 pub(crate) aes_mode: Option<(AesMode, AesVendorVersion, CompressionMethod)>,
281}
282pub type SimpleFileOptions = FileOptions<'static, ()>;
284pub type FullFileOptions<'k> = FileOptions<'k, ExtendedFileOptions>;
286#[derive(Clone, Default, Eq, PartialEq)]
288pub struct ExtendedFileOptions {
289 extra_data: Arc<Vec<u8>>,
290 central_extra_data: Arc<Vec<u8>>,
291}
292
293impl ExtendedFileOptions {
294 pub fn add_extra_data<D: AsRef<[u8]>>(
296 &mut self,
297 header_id: u16,
298 data: D,
299 central_only: bool,
300 ) -> ZipResult<()> {
301 let data = data.as_ref();
302 let len = data.len() + 4;
303 if self.extra_data.len() + self.central_extra_data.len() + len > u16::MAX as usize {
304 Err(invalid!("Extra data field would be longer than allowed"))
305 } else {
306 let field = if central_only {
307 &mut self.central_extra_data
308 } else {
309 &mut self.extra_data
310 };
311 let vec = Arc::get_mut(field);
312 let vec = match vec {
313 Some(exclusive) => exclusive,
314 None => {
315 *field = Arc::new(field.to_vec());
316 Arc::get_mut(field).unwrap()
317 }
318 };
319 Self::add_extra_data_unchecked(vec, header_id, data)?;
320 Self::validate_extra_data(vec, true)?;
321 Ok(())
322 }
323 }
324
325 pub(crate) fn add_extra_data_unchecked(
326 vec: &mut Vec<u8>,
327 header_id: u16,
328 data: &[u8],
329 ) -> Result<(), ZipError> {
330 vec.reserve_exact(data.len() + 4);
331 vec.write_u16_le(header_id)?;
332 vec.write_u16_le(data.len() as u16)?;
333 vec.write_all(data)?;
334 Ok(())
335 }
336
337 fn validate_extra_data(data: &[u8], disallow_zip64: bool) -> ZipResult<()> {
338 let len = data.len() as u64;
339 if len == 0 {
340 return Ok(());
341 }
342 if len > u16::MAX as u64 {
343 return Err(ZipError::Io(io::Error::other(
344 "Extra-data field can't exceed u16::MAX bytes",
345 )));
346 }
347 let mut data = Cursor::new(data);
348 let mut pos = data.position();
349 while pos < len {
350 if len - data.position() < 4 {
351 return Err(ZipError::Io(io::Error::other(
352 "Extra-data field doesn't have room for ID and length",
353 )));
354 }
355 #[cfg(not(feature = "unreserved"))]
356 {
357 use crate::unstable::LittleEndianReadExt;
358 let header_id = data.read_u16_le()?;
359 if EXTRA_FIELD_MAPPING.contains(&header_id) {
360 return Err(ZipError::Io(io::Error::other(
361 format!(
362 "Extra data header ID {header_id:#06} requires crate feature \"unreserved\"",
363 ),
364 )));
365 }
366 data.seek(SeekFrom::Current(-2))?;
367 }
368 parse_single_extra_field(&mut ZipFileData::default(), &mut data, pos, disallow_zip64)?;
369 pos = data.position();
370 }
371 Ok(())
372 }
373}
374
375impl Debug for ExtendedFileOptions {
376 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
377 f.write_fmt(format_args!("ExtendedFileOptions {{extra_data: vec!{:?}.into(), central_extra_data: vec!{:?}.into()}}",
378 self.extra_data, self.central_extra_data))
379 }
380}
381
382#[cfg(fuzzing)]
383impl<'a> arbitrary::Arbitrary<'a> for FileOptions<'a, ExtendedFileOptions> {
384 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
385 let mut options = FullFileOptions {
386 compression_method: CompressionMethod::arbitrary(u)?,
387 compression_level: if bool::arbitrary(u)? {
388 Some(u.int_in_range(0..=24)?)
389 } else {
390 None
391 },
392 last_modified_time: DateTime::arbitrary(u)?,
393 permissions: Option::<u32>::arbitrary(u)?,
394 large_file: bool::arbitrary(u)?,
395 encrypt_with: Option::<EncryptWith>::arbitrary(u)?,
396 alignment: u16::arbitrary(u)?,
397 #[cfg(feature = "deflate-zopfli")]
398 zopfli_buffer_size: None,
399 ..Default::default()
400 };
401 #[cfg(feature = "deflate-zopfli")]
402 if options.compression_method == CompressionMethod::Deflated && bool::arbitrary(u)? {
403 options.zopfli_buffer_size =
404 Some(if bool::arbitrary(u)? { 2 } else { 3 } << u.int_in_range(8..=20)?);
405 }
406 u.arbitrary_loop(Some(0), Some(10), |u| {
407 options
408 .add_extra_data(
409 u.int_in_range(2..=u16::MAX)?,
410 Box::<[u8]>::arbitrary(u)?,
411 bool::arbitrary(u)?,
412 )
413 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
414 Ok(core::ops::ControlFlow::Continue(()))
415 })?;
416 ZipWriter::new(Cursor::new(Vec::new()))
417 .start_file("", options.clone())
418 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
419 Ok(options)
420 }
421}
422
423impl<T: FileOptionExtension> FileOptions<'_, T> {
424 pub(crate) fn normalize(&mut self) {
425 if !self.last_modified_time.is_valid() {
426 self.last_modified_time = FileOptions::<T>::default().last_modified_time;
427 }
428
429 *self.permissions.get_or_insert(0o644) |= ffi::S_IFREG;
430 }
431
432 #[must_use]
439 pub const fn compression_method(mut self, method: CompressionMethod) -> Self {
440 self.compression_method = method;
441 self
442 }
443
444 #[must_use]
455 pub const fn compression_level(mut self, level: Option<i64>) -> Self {
456 self.compression_level = level;
457 self
458 }
459
460 #[must_use]
465 pub const fn last_modified_time(mut self, mod_time: DateTime) -> Self {
466 self.last_modified_time = mod_time;
467 self
468 }
469
470 #[must_use]
480 pub const fn unix_permissions(mut self, mode: u32) -> Self {
481 self.permissions = Some(mode & 0o777);
482 self
483 }
484
485 #[must_use]
491 pub const fn large_file(mut self, large: bool) -> Self {
492 self.large_file = large;
493 self
494 }
495
496 pub(crate) fn with_deprecated_encryption(self, password: &[u8]) -> FileOptions<'static, T> {
497 FileOptions {
498 encrypt_with: Some(EncryptWith::ZipCrypto(
499 ZipCryptoKeys::derive(password),
500 PhantomData,
501 )),
502 ..self
503 }
504 }
505
506 #[cfg(feature = "aes-crypto")]
508 pub fn with_aes_encryption(self, mode: AesMode, password: &str) -> FileOptions<'_, T> {
509 FileOptions {
510 encrypt_with: Some(EncryptWith::Aes { mode, password }),
511 ..self
512 }
513 }
514
515 #[must_use]
520 #[cfg(feature = "deflate-zopfli")]
521 pub const fn with_zopfli_buffer(mut self, size: Option<usize>) -> Self {
522 self.zopfli_buffer_size = size;
523 self
524 }
525
526 pub const fn get_compression_level(&self) -> Option<i64> {
528 self.compression_level
529 }
530 #[must_use]
532 pub const fn with_alignment(mut self, alignment: u16) -> Self {
533 self.alignment = alignment;
534 self
535 }
536}
537impl FileOptions<'_, ExtendedFileOptions> {
538 pub fn add_extra_data<D: AsRef<[u8]>>(
540 &mut self,
541 header_id: u16,
542 data: D,
543 central_only: bool,
544 ) -> ZipResult<()> {
545 self.extended_options
546 .add_extra_data(header_id, data, central_only)
547 }
548
549 #[must_use]
551 pub fn clear_extra_data(mut self) -> Self {
552 if !self.extended_options.extra_data.is_empty() {
553 self.extended_options.extra_data = Arc::new(vec![]);
554 }
555 if !self.extended_options.central_extra_data.is_empty() {
556 self.extended_options.central_extra_data = Arc::new(vec![]);
557 }
558 self
559 }
560}
561impl<T: FileOptionExtension> Default for FileOptions<'_, T> {
562 fn default() -> Self {
564 Self {
565 compression_method: Default::default(),
566 compression_level: None,
567 last_modified_time: DateTime::default_for_write(),
568 permissions: None,
569 large_file: false,
570 encrypt_with: None,
571 extended_options: T::default(),
572 alignment: 1,
573 #[cfg(feature = "deflate-zopfli")]
574 zopfli_buffer_size: Some(1 << 15),
575 #[cfg(feature = "aes-crypto")]
576 aes_mode: None,
577 }
578 }
579}
580
581impl<W: Write + Seek> Write for ZipWriter<W> {
582 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
583 if !self.writing_to_file {
584 return Err(io::Error::other("No file has been started"));
585 }
586 if buf.is_empty() {
587 return Ok(0);
588 }
589 match self.inner.ref_mut() {
590 Some(ref mut w) => {
591 let write_result = w.write(buf);
592 if let Ok(count) = write_result {
593 self.stats.update(&buf[0..count]);
594 if self.stats.bytes_written > spec::ZIP64_BYTES_THR
595 && !self.files.last_mut().unwrap().1.large_file
596 {
597 let _ = self.abort_file();
598 return Err(io::Error::other("Large file option has not been set"));
599 }
600 }
601 write_result
602 }
603 None => Err(io::Error::new(
604 io::ErrorKind::BrokenPipe,
605 "write(): ZipWriter was already closed",
606 )),
607 }
608 }
609
610 fn flush(&mut self) -> io::Result<()> {
611 match self.inner.ref_mut() {
612 Some(ref mut w) => w.flush(),
613 None => Err(io::Error::new(
614 io::ErrorKind::BrokenPipe,
615 "flush(): ZipWriter was already closed",
616 )),
617 }
618 }
619}
620
621impl ZipWriterStats {
622 fn update(&mut self, buf: &[u8]) {
623 self.hasher.update(buf);
624 self.bytes_written += buf.len() as u64;
625 }
626}
627
628impl<A: Read + Write + Seek> ZipWriter<A> {
629 pub fn new_append(readwriter: A) -> ZipResult<ZipWriter<A>> {
633 Self::new_append_with_config(Default::default(), readwriter)
634 }
635
636 pub fn new_append_with_config(config: Config, mut readwriter: A) -> ZipResult<ZipWriter<A>> {
640 readwriter.seek(SeekFrom::Start(0))?;
641 let shared = ZipArchive::get_metadata(config, &mut readwriter)?;
642
643 Ok(ZipWriter {
644 inner: Storer(MaybeEncrypted::Unencrypted(readwriter)),
645 files: shared.files,
646 stats: Default::default(),
647 writing_to_file: false,
648 comment: shared.comment,
649 zip64_comment: shared.zip64_comment,
650 writing_raw: true, flush_on_finish_file: false,
652 seek_possible: true,
653 })
654 }
655
656 pub fn set_flush_on_finish_file(&mut self, flush_on_finish_file: bool) {
670 self.flush_on_finish_file = flush_on_finish_file;
671 }
672}
673
674impl<A: Read + Write + Seek> ZipWriter<A> {
675 pub fn deep_copy_file(&mut self, src_name: &str, dest_name: &str) -> ZipResult<()> {
678 self.finish_file()?;
679 if src_name == dest_name || self.files.contains_key(dest_name) {
680 return Err(invalid!("That file already exists"));
681 }
682 let write_position = self.inner.get_plain().stream_position()?;
683 let src_index = self.index_by_name(src_name)?;
684 let src_data = &mut self.files[src_index];
685 let src_data_start = src_data.data_start(self.inner.get_plain())?;
686 debug_assert!(src_data_start <= write_position);
687 let mut compressed_size = src_data.compressed_size;
688 if compressed_size > (write_position - src_data_start) {
689 compressed_size = write_position - src_data_start;
690 src_data.compressed_size = compressed_size;
691 }
692 let mut reader = BufReader::new(self.inner.get_plain());
693 reader.seek(SeekFrom::Start(src_data_start))?;
694 let mut copy = vec![0; compressed_size as usize];
695 reader.take(compressed_size).read_exact(&mut copy)?;
696 self.inner
697 .get_plain()
698 .seek(SeekFrom::Start(write_position))?;
699 let mut new_data = src_data.clone();
700 let dest_name_raw = dest_name.as_bytes();
701 new_data.file_name = dest_name.into();
702 new_data.file_name_raw = dest_name_raw.into();
703 new_data.is_utf8 = !dest_name.is_ascii();
704 new_data.header_start = write_position;
705 let extra_data_start = write_position
706 + size_of::<ZipLocalEntryBlock>() as u64
707 + new_data.file_name_raw.len() as u64;
708 new_data.extra_data_start = Some(extra_data_start);
709 let mut data_start = extra_data_start;
710 if let Some(extra) = &src_data.extra_field {
711 data_start += extra.len() as u64;
712 }
713 new_data.data_start.take();
714 new_data.data_start.get_or_init(|| data_start);
715 new_data.central_header_start = 0;
716 let block = new_data.local_block()?;
717 let index = self.insert_file_data(new_data)?;
718 let new_data = &self.files[index];
719 let result: io::Result<()> = (|| {
720 let plain_writer = self.inner.get_plain();
721 block.write(plain_writer)?;
722 plain_writer.write_all(&new_data.file_name_raw)?;
723 if let Some(data) = &new_data.extra_field {
724 plain_writer.write_all(data)?;
725 }
726 debug_assert_eq!(data_start, plain_writer.stream_position()?);
727 self.writing_to_file = true;
728 plain_writer.write_all(©)?;
729 if self.flush_on_finish_file {
730 plain_writer.flush()?;
731 }
732 Ok(())
733 })();
734 self.ok_or_abort_file(result)?;
735 self.writing_to_file = false;
736 Ok(())
737 }
738
739 pub fn deep_copy_file_from_path<T: AsRef<Path>, U: AsRef<Path>>(
745 &mut self,
746 src_path: T,
747 dest_path: U,
748 ) -> ZipResult<()> {
749 let src = path_to_string(src_path);
750 let dest = path_to_string(dest_path);
751 self.deep_copy_file(&src, &dest)
752 }
753
754 pub fn finish_into_readable(mut self) -> ZipResult<ZipArchive<A>> {
782 let central_start = self.finalize()?;
783 let inner = mem::replace(&mut self.inner, Closed).unwrap();
784 let comment = mem::take(&mut self.comment);
785 let zip64_comment = mem::take(&mut self.zip64_comment);
786 let files = mem::take(&mut self.files);
787
788 let archive =
789 ZipArchive::from_finalized_writer(files, comment, zip64_comment, inner, central_start)?;
790 Ok(archive)
791 }
792}
793
794impl<W: Write + Seek> ZipWriter<W> {
795 pub fn new(inner: W) -> ZipWriter<W> {
801 ZipWriter {
802 inner: Storer(MaybeEncrypted::Unencrypted(inner)),
803 files: IndexMap::new(),
804 stats: Default::default(),
805 writing_to_file: false,
806 writing_raw: false,
807 comment: Box::new([]),
808 zip64_comment: None,
809 flush_on_finish_file: false,
810 seek_possible: true,
811 }
812 }
813
814 pub const fn is_writing_file(&self) -> bool {
816 self.writing_to_file && !self.inner.is_closed()
817 }
818
819 pub fn set_comment<S>(&mut self, comment: S)
821 where
822 S: Into<Box<str>>,
823 {
824 self.set_raw_comment(comment.into().into_boxed_bytes())
825 }
826
827 pub fn set_raw_comment(&mut self, comment: Box<[u8]>) {
832 self.comment = comment;
833 }
834
835 pub fn get_comment(&mut self) -> Result<&str, Utf8Error> {
837 from_utf8(self.get_raw_comment())
838 }
839
840 pub const fn get_raw_comment(&self) -> &[u8] {
845 &self.comment
846 }
847
848 pub fn set_zip64_comment<S>(&mut self, comment: Option<S>)
850 where
851 S: Into<Box<str>>,
852 {
853 self.set_raw_zip64_comment(comment.map(|v| v.into().into_boxed_bytes()))
854 }
855
856 pub fn set_raw_zip64_comment(&mut self, comment: Option<Box<[u8]>>) {
861 self.zip64_comment = comment;
862 }
863
864 pub fn get_zip64_comment(&mut self) -> Option<Result<&str, Utf8Error>> {
866 self.get_raw_zip64_comment().map(from_utf8)
867 }
868
869 pub fn get_raw_zip64_comment(&self) -> Option<&[u8]> {
874 self.zip64_comment.as_deref()
875 }
876
877 pub unsafe fn set_file_metadata(&mut self, length: u64, crc32: u32) -> ZipResult<()> {
884 if !self.writing_to_file {
885 return Err(ZipError::Io(io::Error::other("No file has been started")));
886 }
887 self.stats.hasher = Hasher::new_with_initial_len(crc32, length);
888 self.stats.bytes_written = length;
889 Ok(())
890 }
891
892 fn ok_or_abort_file<T, E: Into<ZipError>>(&mut self, result: Result<T, E>) -> ZipResult<T> {
893 match result {
894 Err(e) => {
895 let _ = self.abort_file();
896 Err(e.into())
897 }
898 Ok(t) => Ok(t),
899 }
900 }
901
902 fn start_entry<S: ToString, T: FileOptionExtension>(
904 &mut self,
905 name: S,
906 options: FileOptions<T>,
907 raw_values: Option<ZipRawValues>,
908 ) -> ZipResult<()> {
909 self.finish_file()?;
910
911 let header_start = self.inner.get_plain().stream_position()?;
912 let raw_values = raw_values.unwrap_or(ZipRawValues {
913 crc32: 0,
914 compressed_size: 0,
915 uncompressed_size: 0,
916 });
917
918 let mut extra_data = match options.extended_options.extra_data() {
919 Some(data) => data.to_vec(),
920 None => vec![],
921 };
922 let central_extra_data = options.extended_options.central_extra_data();
923 if let Some(zip64_block) =
924 Zip64ExtraFieldBlock::maybe_new(options.large_file, 0, 0, header_start)
925 {
926 let mut new_extra_data = zip64_block.serialize().into_vec();
927 new_extra_data.append(&mut extra_data);
928 extra_data = new_extra_data;
929 }
930 #[allow(unused_mut)]
932 let mut aes_extra_data_start = 0;
933 #[cfg(feature = "aes-crypto")]
934 if let Some(EncryptWith::Aes { mode, .. }) = options.encrypt_with {
935 let aes_dummy_extra_data = [0x02, 0x00, 0x41, 0x45, mode as u8, 0x00, 0x00];
936 aes_extra_data_start = extra_data.len() as u64;
937 ExtendedFileOptions::add_extra_data_unchecked(
938 &mut extra_data,
939 0x9901,
940 &aes_dummy_extra_data,
941 )?;
942 } else if let Some((mode, vendor, underlying)) = options.aes_mode {
943 let mut body = [0; 7];
945 [body[0], body[1]] = (vendor as u16).to_le_bytes(); [body[2], body[3]] = *b"AE"; body[4] = mode as u8; [body[5], body[6]] = underlying.serialize_to_u16().to_le_bytes(); aes_extra_data_start = extra_data.len() as u64;
950 ExtendedFileOptions::add_extra_data_unchecked(&mut extra_data, 0x9901, &body)?;
951 }
952
953 let (compression_method, aes_mode) = match options.encrypt_with {
954 #[cfg(feature = "aes-crypto")]
956 None if options.aes_mode.is_some() => (CompressionMethod::Aes, options.aes_mode),
957 #[cfg(feature = "aes-crypto")]
958 Some(EncryptWith::Aes { mode, .. }) => (
959 CompressionMethod::Aes,
960 Some((mode, AesVendorVersion::Ae2, options.compression_method)),
961 ),
962 _ => (options.compression_method, None),
963 };
964 let header_end =
965 header_start + size_of::<ZipLocalEntryBlock>() as u64 + name.to_string().len() as u64;
966
967 if options.alignment > 1 {
968 let extra_data_end = header_end + extra_data.len() as u64;
969 let align = options.alignment as u64;
970 let unaligned_header_bytes = extra_data_end % align;
971 if unaligned_header_bytes != 0 {
972 let mut pad_length = (align - unaligned_header_bytes) as usize;
973 while pad_length < 6 {
974 pad_length += align as usize;
975 }
976 let mut pad_body = vec![0; pad_length - 4];
978 debug_assert!(pad_body.len() >= 2);
979 [pad_body[0], pad_body[1]] = options.alignment.to_le_bytes();
980 ExtendedFileOptions::add_extra_data_unchecked(&mut extra_data, 0xa11e, &pad_body)?;
981 debug_assert_eq!((extra_data.len() as u64 + header_end) % align, 0);
982 }
983 }
984 let extra_data_len = extra_data.len();
985 if let Some(data) = central_extra_data {
986 if extra_data_len + data.len() > u16::MAX as usize {
987 return Err(invalid!(
988 "Extra data and central extra data must be less than 64KiB when combined"
989 ));
990 }
991 ExtendedFileOptions::validate_extra_data(data, true)?;
992 }
993 let mut file = ZipFileData::initialize_local_block(
994 name,
995 &options,
996 raw_values,
997 header_start,
998 None,
999 aes_extra_data_start,
1000 compression_method,
1001 aes_mode,
1002 &extra_data,
1003 );
1004 file.using_data_descriptor = !self.seek_possible;
1005 file.version_made_by = file.version_made_by.max(file.version_needed() as u8);
1006 file.extra_data_start = Some(header_end);
1007 let index = self.insert_file_data(file)?;
1008 self.writing_to_file = true;
1009 let result: ZipResult<()> = (|| {
1010 ExtendedFileOptions::validate_extra_data(&extra_data, false)?;
1011 let file = &mut self.files[index];
1012 let block = file.local_block()?;
1013 let writer = self.inner.get_plain();
1014 block.write(writer)?;
1015 writer.write_all(&file.file_name_raw)?;
1017 if extra_data_len > 0 {
1018 writer.write_all(&extra_data)?;
1019 file.extra_field = Some(extra_data.into());
1020 }
1021 Ok(())
1022 })();
1023 self.ok_or_abort_file(result)?;
1024 let writer = self.inner.get_plain();
1025 self.stats.start = writer.stream_position()?;
1026 match options.encrypt_with {
1027 #[cfg(feature = "aes-crypto")]
1028 Some(EncryptWith::Aes { mode, password }) => {
1029 let aeswriter = AesWriter::new(
1030 mem::replace(&mut self.inner, Closed).unwrap(),
1031 mode,
1032 password.as_bytes(),
1033 )?;
1034 self.inner = Storer(MaybeEncrypted::Aes(aeswriter));
1035 }
1036 Some(EncryptWith::ZipCrypto(keys, ..)) => {
1037 let mut zipwriter = crate::zipcrypto::ZipCryptoWriter {
1038 writer: mem::replace(&mut self.inner, Closed).unwrap(),
1039 buffer: vec![],
1040 keys,
1041 };
1042 self.stats.start = zipwriter.writer.stream_position()?;
1043 let crypto_header = [0u8; 12];
1045 let result = zipwriter.write_all(&crypto_header);
1046 self.ok_or_abort_file(result)?;
1047 self.inner = Storer(MaybeEncrypted::ZipCrypto(zipwriter));
1048 }
1049 None => {}
1050 }
1051 let file = &mut self.files[index];
1052 debug_assert!(file.data_start.get().is_none());
1053 file.data_start.get_or_init(|| self.stats.start);
1054 self.stats.bytes_written = 0;
1055 self.stats.hasher = Hasher::new();
1056 Ok(())
1057 }
1058
1059 fn insert_file_data(&mut self, file: ZipFileData) -> ZipResult<usize> {
1060 if self.files.contains_key(&file.file_name) {
1061 return Err(invalid!("Duplicate filename: {}", file.file_name));
1062 }
1063 let name = file.file_name.to_owned();
1064 self.files.insert(name.clone(), file);
1065 Ok(self.files.get_index_of(&name).unwrap())
1066 }
1067
1068 fn finish_file(&mut self) -> ZipResult<()> {
1069 if !self.writing_to_file {
1070 return Ok(());
1071 }
1072
1073 let make_plain_writer = self.inner.prepare_next_writer(
1074 Stored,
1075 None,
1076 #[cfg(feature = "deflate-zopfli")]
1077 None,
1078 )?;
1079 self.inner.switch_to(make_plain_writer)?;
1080 self.switch_to_non_encrypting_writer()?;
1081 let writer = self.inner.get_plain();
1082
1083 if !self.writing_raw {
1084 let file = match self.files.last_mut() {
1085 None => return Ok(()),
1086 Some((_, f)) => f,
1087 };
1088 file.uncompressed_size = self.stats.bytes_written;
1089
1090 let file_end = writer.stream_position()?;
1091 debug_assert!(file_end >= self.stats.start);
1092 file.compressed_size = file_end - self.stats.start;
1093 let mut crc = true;
1094 if let Some(aes_mode) = &mut file.aes_mode {
1095 aes_mode.1 = if self.stats.bytes_written < 20 {
1101 crc = false;
1102 AesVendorVersion::Ae2
1103 } else {
1104 AesVendorVersion::Ae1
1105 };
1106 }
1107 file.crc32 = if crc {
1108 self.stats.hasher.clone().finalize()
1109 } else {
1110 0
1111 };
1112 update_aes_extra_data(writer, file)?;
1113 if file.using_data_descriptor {
1114 write_data_descriptor(writer, file)?;
1115 } else {
1116 update_local_file_header(writer, file)?;
1117 writer.seek(SeekFrom::Start(file_end))?;
1118 }
1119 }
1120 if self.flush_on_finish_file {
1121 let result = writer.flush();
1122 self.ok_or_abort_file(result)?;
1123 }
1124
1125 self.writing_to_file = false;
1126 Ok(())
1127 }
1128
1129 fn switch_to_non_encrypting_writer(&mut self) -> Result<(), ZipError> {
1130 match mem::replace(&mut self.inner, Closed) {
1131 #[cfg(feature = "aes-crypto")]
1132 Storer(MaybeEncrypted::Aes(writer)) => {
1133 self.inner = Storer(MaybeEncrypted::Unencrypted(writer.finish()?));
1134 }
1135 Storer(MaybeEncrypted::ZipCrypto(writer)) => {
1136 let crc32 = self.stats.hasher.clone().finalize();
1137 self.inner = Storer(MaybeEncrypted::Unencrypted(writer.finish(crc32)?))
1138 }
1139 Storer(MaybeEncrypted::Unencrypted(w)) => {
1140 self.inner = Storer(MaybeEncrypted::Unencrypted(w))
1141 }
1142 _ => unreachable!(),
1143 }
1144 Ok(())
1145 }
1146
1147 pub fn abort_file(&mut self) -> ZipResult<()> {
1150 let (_, last_file) = self.files.pop().ok_or(ZipError::FileNotFound)?;
1151 let make_plain_writer = self.inner.prepare_next_writer(
1152 Stored,
1153 None,
1154 #[cfg(feature = "deflate-zopfli")]
1155 None,
1156 )?;
1157 self.inner.switch_to(make_plain_writer)?;
1158 self.switch_to_non_encrypting_writer()?;
1159 let rewind_safe: bool = match last_file.data_start.get() {
1162 None => self.files.is_empty(),
1163 Some(last_file_start) => self.files.values().all(|file| {
1164 file.data_start
1165 .get()
1166 .is_some_and(|start| start < last_file_start)
1167 }),
1168 };
1169 if rewind_safe {
1170 self.inner
1171 .get_plain()
1172 .seek(SeekFrom::Start(last_file.header_start))?;
1173 }
1174 self.writing_to_file = false;
1175 Ok(())
1176 }
1177
1178 pub fn start_file<S: ToString, T: FileOptionExtension>(
1183 &mut self,
1184 name: S,
1185 mut options: FileOptions<T>,
1186 ) -> ZipResult<()> {
1187 options.normalize();
1188 let make_new_self = self.inner.prepare_next_writer(
1189 options.compression_method,
1190 options.compression_level,
1191 #[cfg(feature = "deflate-zopfli")]
1192 options.zopfli_buffer_size,
1193 )?;
1194 self.start_entry(name, options, None)?;
1195 let result = self.inner.switch_to(make_new_self);
1196 self.ok_or_abort_file(result)?;
1197 self.writing_raw = false;
1198 Ok(())
1199 }
1200
1201 pub fn merge_archive<R>(&mut self, mut source: ZipArchive<R>) -> ZipResult<()>
1247 where
1248 R: Read + Seek,
1249 {
1250 self.finish_file()?;
1251
1252 self.writing_to_file = true;
1255 self.writing_raw = true;
1256
1257 let writer = self.inner.get_plain();
1258 let new_files = source.merge_contents(writer)?;
1260
1261 self.files.extend(new_files);
1263
1264 Ok(())
1265 }
1266
1267 pub fn start_file_from_path<E: FileOptionExtension, P: AsRef<Path>>(
1273 &mut self,
1274 path: P,
1275 options: FileOptions<E>,
1276 ) -> ZipResult<()> {
1277 self.start_file(path_to_string(path), options)
1278 }
1279
1280 pub fn raw_copy_file_rename<R: Read, S: ToString>(
1307 &mut self,
1308 file: ZipFile<R>,
1309 name: S,
1310 ) -> ZipResult<()> {
1311 let options = file.options();
1312 self.raw_copy_file_rename_internal(file, name, options)
1313 }
1314
1315 fn raw_copy_file_rename_internal<R: Read, S: ToString>(
1316 &mut self,
1317 mut file: ZipFile<R>,
1318 name: S,
1319 options: SimpleFileOptions,
1320 ) -> ZipResult<()> {
1321 let raw_values = ZipRawValues {
1322 crc32: file.crc32(),
1323 compressed_size: file.compressed_size(),
1324 uncompressed_size: file.size(),
1325 };
1326
1327 self.start_entry(name, options, Some(raw_values))?;
1328 self.writing_to_file = true;
1329 self.writing_raw = true;
1330
1331 io::copy(&mut file.take_raw_reader()?, self)?;
1332 self.finish_file()
1333 }
1334
1335 pub fn raw_copy_file_to_path<R: Read, P: AsRef<Path>>(
1341 &mut self,
1342 file: ZipFile<R>,
1343 path: P,
1344 ) -> ZipResult<()> {
1345 self.raw_copy_file_rename(file, path_to_string(path))
1346 }
1347
1348 pub fn raw_copy_file<R: Read>(&mut self, file: ZipFile<R>) -> ZipResult<()> {
1372 let name = file.name().to_owned();
1373 self.raw_copy_file_rename(file, name)
1374 }
1375
1376 pub fn raw_copy_file_touch<R: Read>(
1400 &mut self,
1401 file: ZipFile<R>,
1402 last_modified_time: DateTime,
1403 unix_mode: Option<u32>,
1404 ) -> ZipResult<()> {
1405 let name = file.name().to_owned();
1406
1407 let mut options = file.options();
1408
1409 options = options.last_modified_time(last_modified_time);
1410
1411 if let Some(perms) = unix_mode {
1412 options = options.unix_permissions(perms);
1413 }
1414
1415 options.normalize();
1416
1417 self.raw_copy_file_rename_internal(file, name, options)
1418 }
1419
1420 pub fn add_directory<S, T: FileOptionExtension>(
1424 &mut self,
1425 name: S,
1426 mut options: FileOptions<T>,
1427 ) -> ZipResult<()>
1428 where
1429 S: Into<String>,
1430 {
1431 if options.permissions.is_none() {
1432 options.permissions = Some(0o755);
1433 }
1434 *options.permissions.as_mut().unwrap() |= 0o40000;
1435 options.compression_method = Stored;
1436 options.encrypt_with = None;
1437
1438 let name_as_string = name.into();
1439 let name_with_slash = match name_as_string.chars().last() {
1441 Some('/') | Some('\\') => name_as_string,
1442 _ => name_as_string + "/",
1443 };
1444
1445 self.start_entry(name_with_slash, options, None)?;
1446 self.writing_to_file = false;
1447 self.switch_to_non_encrypting_writer()?;
1448 Ok(())
1449 }
1450
1451 pub fn add_directory_from_path<T: FileOptionExtension, P: AsRef<Path>>(
1457 &mut self,
1458 path: P,
1459 options: FileOptions<T>,
1460 ) -> ZipResult<()> {
1461 self.add_directory(path_to_string(path), options)
1462 }
1463
1464 pub fn finish(mut self) -> ZipResult<W> {
1469 let _central_start = self.finalize()?;
1470 let inner = mem::replace(&mut self.inner, Closed);
1471 Ok(inner.unwrap())
1472 }
1473
1474 pub fn add_symlink<N: ToString, T: ToString, E: FileOptionExtension>(
1487 &mut self,
1488 name: N,
1489 target: T,
1490 mut options: FileOptions<E>,
1491 ) -> ZipResult<()> {
1492 if options.permissions.is_none() {
1493 options.permissions = Some(0o777);
1494 }
1495 *options.permissions.as_mut().unwrap() |= S_IFLNK;
1496 options.compression_method = Stored;
1499
1500 self.start_entry(name, options, None)?;
1501 self.writing_to_file = true;
1502 let result = self.write_all(target.to_string().as_bytes());
1503 self.ok_or_abort_file(result)?;
1504 self.writing_raw = false;
1505 self.finish_file()?;
1506
1507 Ok(())
1508 }
1509
1510 pub fn add_symlink_from_path<P: AsRef<Path>, T: AsRef<Path>, E: FileOptionExtension>(
1516 &mut self,
1517 path: P,
1518 target: T,
1519 options: FileOptions<E>,
1520 ) -> ZipResult<()> {
1521 self.add_symlink(path_to_string(path), path_to_string(target), options)
1522 }
1523
1524 fn finalize(&mut self) -> ZipResult<u64> {
1525 self.finish_file()?;
1526
1527 let mut central_start = self.write_central_and_footer()?;
1528 let writer = self.inner.get_plain();
1529 let footer_end = writer.stream_position()?;
1530 let archive_end = writer.seek(SeekFrom::End(0))?;
1531 if footer_end < archive_end {
1532 writer.seek(SeekFrom::Start(central_start))?;
1536 writer.write_u32_le(0)?;
1537 writer.seek(SeekFrom::Start(
1538 footer_end - size_of::<Zip32CDEBlock>() as u64 - self.comment.len() as u64,
1539 ))?;
1540 writer.write_u32_le(0)?;
1541
1542 let central_and_footer_size = footer_end - central_start;
1544 writer.seek(SeekFrom::End(-(central_and_footer_size as i64)))?;
1545 central_start = self.write_central_and_footer()?;
1546 debug_assert!(self.inner.get_plain().stream_position()? == archive_end);
1547 }
1548
1549 Ok(central_start)
1550 }
1551
1552 fn write_central_and_footer(&mut self) -> Result<u64, ZipError> {
1553 let writer = self.inner.get_plain();
1554
1555 let mut version_needed = MIN_VERSION as u16;
1556 let central_start = writer.stream_position()?;
1557 for file in self.files.values() {
1558 write_central_directory_header(writer, file)?;
1559 version_needed = version_needed.max(file.version_needed());
1560 }
1561 let central_size = writer.stream_position()? - central_start;
1562 let is64 = self.files.len() > spec::ZIP64_ENTRY_THR
1563 || central_size.max(central_start) > spec::ZIP64_BYTES_THR
1564 || self.zip64_comment.is_some();
1565
1566 if is64 {
1567 let comment = self.zip64_comment.clone().unwrap_or_default();
1568
1569 let zip64_footer = spec::Zip64CentralDirectoryEnd {
1570 record_size: comment.len() as u64 + 44,
1571 version_made_by: version_needed,
1572 version_needed_to_extract: version_needed,
1573 disk_number: 0,
1574 disk_with_central_directory: 0,
1575 number_of_files_on_this_disk: self.files.len() as u64,
1576 number_of_files: self.files.len() as u64,
1577 central_directory_size: central_size,
1578 central_directory_offset: central_start,
1579 extensible_data_sector: comment,
1580 };
1581
1582 zip64_footer.write(writer)?;
1583
1584 let zip64_footer = spec::Zip64CentralDirectoryEndLocator {
1585 disk_with_central_directory: 0,
1586 end_of_central_directory_offset: central_start + central_size,
1587 number_of_disks: 1,
1588 };
1589
1590 zip64_footer.write(writer)?;
1591 }
1592
1593 let number_of_files = self.files.len().min(spec::ZIP64_ENTRY_THR) as u16;
1594 let footer = spec::Zip32CentralDirectoryEnd {
1595 disk_number: 0,
1596 disk_with_central_directory: 0,
1597 zip_file_comment: self.comment.clone(),
1598 number_of_files_on_this_disk: number_of_files,
1599 number_of_files,
1600 central_directory_size: central_size.min(spec::ZIP64_BYTES_THR) as u32,
1601 central_directory_offset: central_start.min(spec::ZIP64_BYTES_THR) as u32,
1602 };
1603
1604 footer.write(writer)?;
1605 Ok(central_start)
1606 }
1607
1608 fn index_by_name(&self, name: &str) -> ZipResult<usize> {
1609 self.files.get_index_of(name).ok_or(ZipError::FileNotFound)
1610 }
1611
1612 pub fn shallow_copy_file(&mut self, src_name: &str, dest_name: &str) -> ZipResult<()> {
1618 self.finish_file()?;
1619 if src_name == dest_name {
1620 return Err(invalid!("Trying to copy a file to itself"));
1621 }
1622 let src_index = self.index_by_name(src_name)?;
1623 let mut dest_data = self.files[src_index].to_owned();
1624 dest_data.file_name = dest_name.to_string().into();
1625 dest_data.file_name_raw = dest_name.to_string().into_bytes().into();
1626 dest_data.central_header_start = 0;
1627 self.insert_file_data(dest_data)?;
1628
1629 Ok(())
1630 }
1631
1632 pub fn shallow_copy_file_from_path<T: AsRef<Path>, U: AsRef<Path>>(
1638 &mut self,
1639 src_path: T,
1640 dest_path: U,
1641 ) -> ZipResult<()> {
1642 self.shallow_copy_file(&path_to_string(src_path), &path_to_string(dest_path))
1643 }
1644}
1645
1646impl<W: Write> ZipWriter<StreamWriter<W>> {
1647 pub fn new_stream(inner: W) -> ZipWriter<StreamWriter<W>> {
1651 ZipWriter {
1652 inner: Storer(MaybeEncrypted::Unencrypted(StreamWriter::new(inner))),
1653 files: IndexMap::new(),
1654 stats: Default::default(),
1655 writing_to_file: false,
1656 writing_raw: false,
1657 comment: Box::new([]),
1658 zip64_comment: None,
1659 flush_on_finish_file: false,
1660 seek_possible: false,
1661 }
1662 }
1663}
1664
1665impl<W: Write + Seek> Drop for ZipWriter<W> {
1666 fn drop(&mut self) {
1667 if !self.inner.is_closed() {
1668 if let Err(e) = self.finalize() {
1669 let _ = write!(io::stderr(), "ZipWriter drop failed: {e:?}");
1670 }
1671 }
1672 }
1673}
1674
1675type SwitchWriterFunction<W> = Box<dyn FnOnce(MaybeEncrypted<W>) -> ZipResult<GenericZipWriter<W>>>;
1676
1677impl<W: Write + Seek> GenericZipWriter<W> {
1678 fn prepare_next_writer(
1679 &self,
1680 compression: CompressionMethod,
1681 compression_level: Option<i64>,
1682 #[cfg(feature = "deflate-zopfli")] zopfli_buffer_size: Option<usize>,
1683 ) -> ZipResult<SwitchWriterFunction<W>> {
1684 if let Closed = self {
1685 return Err(
1686 io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed").into(),
1687 );
1688 }
1689
1690 {
1691 #[allow(deprecated)]
1692 #[allow(unreachable_code)]
1693 match compression {
1694 Stored => {
1695 if compression_level.is_some() {
1696 Err(UnsupportedArchive("Unsupported compression level"))
1697 } else {
1698 Ok(Box::new(|bare| Ok(Storer(bare))))
1699 }
1700 }
1701 #[cfg(feature = "_deflate-any")]
1702 CompressionMethod::Deflated => {
1703 #[cfg(feature = "deflate-flate2")]
1704 let default = Compression::default().level() as i64;
1705
1706 #[cfg(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))]
1707 let default = 24;
1708
1709 let level = clamp_opt(
1710 compression_level.unwrap_or(default),
1711 deflate_compression_level_range(),
1712 )
1713 .ok_or(UnsupportedArchive("Unsupported compression level"))?
1714 as u32;
1715
1716 #[cfg(feature = "deflate-zopfli")]
1717 macro_rules! deflate_zopfli_and_return {
1718 ($bare:expr, $best_non_zopfli:expr) => {
1719 let options = Options {
1720 iteration_count: NonZeroU64::try_from(
1721 (level - $best_non_zopfli) as u64,
1722 )
1723 .unwrap(),
1724 ..Default::default()
1725 };
1726 return Ok(Box::new(move |bare| {
1727 Ok(match zopfli_buffer_size {
1728 Some(size) => GenericZipWriter::BufferedZopfliDeflater(
1729 BufWriter::with_capacity(
1730 size,
1731 zopfli::DeflateEncoder::new(
1732 options,
1733 Default::default(),
1734 bare,
1735 ),
1736 ),
1737 ),
1738 None => GenericZipWriter::ZopfliDeflater(
1739 zopfli::DeflateEncoder::new(
1740 options,
1741 Default::default(),
1742 bare,
1743 ),
1744 ),
1745 })
1746 }));
1747 };
1748 }
1749
1750 #[cfg(all(feature = "deflate-zopfli", feature = "deflate-flate2"))]
1751 {
1752 let best_non_zopfli = Compression::best().level();
1753 if level > best_non_zopfli {
1754 deflate_zopfli_and_return!(bare, best_non_zopfli);
1755 }
1756 }
1757
1758 #[cfg(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))]
1759 {
1760 let best_non_zopfli = 9;
1761 deflate_zopfli_and_return!(bare, best_non_zopfli);
1762 }
1763
1764 #[cfg(feature = "deflate-flate2")]
1765 {
1766 Ok(Box::new(move |bare| {
1767 Ok(GenericZipWriter::Deflater(DeflateEncoder::new(
1768 bare,
1769 Compression::new(level),
1770 )))
1771 }))
1772 }
1773 }
1774 #[cfg(feature = "deflate64")]
1775 CompressionMethod::Deflate64 => {
1776 Err(UnsupportedArchive("Compressing Deflate64 is not supported"))
1777 }
1778 #[cfg(feature = "bzip2")]
1779 CompressionMethod::Bzip2 => {
1780 let level = clamp_opt(
1781 compression_level.unwrap_or(bzip2::Compression::default().level() as i64),
1782 bzip2_compression_level_range(),
1783 )
1784 .ok_or(UnsupportedArchive("Unsupported compression level"))?
1785 as u32;
1786 Ok(Box::new(move |bare| {
1787 Ok(GenericZipWriter::Bzip2(BzEncoder::new(
1788 bare,
1789 bzip2::Compression::new(level),
1790 )))
1791 }))
1792 }
1793 CompressionMethod::AES => Err(UnsupportedArchive(
1794 "AES encryption is enabled through FileOptions::with_aes_encryption",
1795 )),
1796 #[cfg(feature = "zstd")]
1797 CompressionMethod::Zstd => {
1798 let level = clamp_opt(
1799 compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL as i64),
1800 zstd::compression_level_range(),
1801 )
1802 .ok_or(UnsupportedArchive("Unsupported compression level"))?;
1803 Ok(Box::new(move |bare| {
1804 Ok(GenericZipWriter::Zstd(
1805 ZstdEncoder::new(bare, level as i32).map_err(ZipError::Io)?,
1806 ))
1807 }))
1808 }
1809 #[cfg(feature = "legacy-zip")]
1810 CompressionMethod::Shrink => Err(ZipError::UnsupportedArchive(
1811 "Shrink compression unsupported",
1812 )),
1813 #[cfg(feature = "legacy-zip")]
1814 CompressionMethod::Reduce(_) => Err(ZipError::UnsupportedArchive(
1815 "Reduce compression unsupported",
1816 )),
1817 #[cfg(feature = "legacy-zip")]
1818 CompressionMethod::Implode => Err(ZipError::UnsupportedArchive(
1819 "Implode compression unsupported",
1820 )),
1821 #[cfg(feature = "lzma")]
1822 CompressionMethod::Lzma => {
1823 Err(UnsupportedArchive("LZMA isn't supported for compression"))
1824 }
1825 #[cfg(feature = "xz")]
1826 CompressionMethod::Xz => {
1827 let level = clamp_opt(compression_level.unwrap_or(6), 0..=9)
1828 .ok_or(UnsupportedArchive("Unsupported compression level"))?
1829 as u32;
1830 Ok(Box::new(move |bare| {
1831 Ok(GenericZipWriter::Xz(Box::new(
1832 lzma_rust2::XzWriter::new(
1833 bare,
1834 lzma_rust2::XzOptions::with_preset(level),
1835 )
1836 .map_err(ZipError::Io)?,
1837 )))
1838 }))
1839 }
1840 #[cfg(feature = "ppmd")]
1841 CompressionMethod::Ppmd => {
1842 const ORDERS: [u32; 10] = [0, 4, 5, 6, 7, 8, 9, 10, 11, 12];
1843
1844 let level = clamp_opt(compression_level.unwrap_or(7), 1..=9)
1845 .ok_or(UnsupportedArchive("Unsupported compression level"))?
1846 as u32;
1847
1848 let order = ORDERS[level as usize];
1849 let memory_size = 1 << (level + 19);
1850 let memory_size_mb = memory_size / 1024 / 1024;
1851
1852 Ok(Box::new(move |mut bare| {
1853 let parameter: u16 = (order as u16 - 1)
1854 + ((memory_size_mb - 1) << 4) as u16
1855 + ((ppmd_rust::RestoreMethod::Restart as u16) << 12);
1856
1857 bare.write_all(¶meter.to_le_bytes())
1858 .map_err(ZipError::Io)?;
1859
1860 let encoder = ppmd_rust::Ppmd8Encoder::new(
1861 bare,
1862 order,
1863 memory_size,
1864 ppmd_rust::RestoreMethod::Restart,
1865 )
1866 .map_err(|error| match error {
1867 ppmd_rust::Error::RangeDecoderInitialization => {
1868 ZipError::InvalidArchive(
1869 "PPMd range coder initialization failed".into(),
1870 )
1871 }
1872 ppmd_rust::Error::InvalidParameter => {
1873 ZipError::InvalidArchive("Invalid PPMd parameter".into())
1874 }
1875 ppmd_rust::Error::IoError(io_error) => ZipError::Io(io_error),
1876 ppmd_rust::Error::MemoryAllocation => ZipError::Io(io::Error::new(
1877 ErrorKind::OutOfMemory,
1878 "PPMd could not allocate memory",
1879 )),
1880 })?;
1881
1882 Ok(GenericZipWriter::Ppmd(Box::new(encoder)))
1883 }))
1884 }
1885 CompressionMethod::Unsupported(..) => {
1886 Err(UnsupportedArchive("Unsupported compression"))
1887 }
1888 }
1889 }
1890 }
1891
1892 fn switch_to(&mut self, make_new_self: SwitchWriterFunction<W>) -> ZipResult<()> {
1893 let bare = match mem::replace(self, Closed) {
1894 Storer(w) => w,
1895 #[cfg(feature = "deflate-flate2")]
1896 GenericZipWriter::Deflater(w) => w.finish()?,
1897 #[cfg(feature = "deflate-zopfli")]
1898 GenericZipWriter::ZopfliDeflater(w) => w.finish()?,
1899 #[cfg(feature = "deflate-zopfli")]
1900 GenericZipWriter::BufferedZopfliDeflater(w) => w
1901 .into_inner()
1902 .map_err(|e| ZipError::Io(e.into_error()))?
1903 .finish()?,
1904 #[cfg(feature = "bzip2")]
1905 GenericZipWriter::Bzip2(w) => w.finish()?,
1906 #[cfg(feature = "zstd")]
1907 GenericZipWriter::Zstd(w) => w.finish()?,
1908 #[cfg(feature = "xz")]
1909 GenericZipWriter::Xz(w) => w.finish()?,
1910 #[cfg(feature = "ppmd")]
1911 GenericZipWriter::Ppmd(w) => {
1912 w.finish(true)?
1914 }
1915 Closed => {
1916 return Err(io::Error::new(
1917 io::ErrorKind::BrokenPipe,
1918 "ZipWriter was already closed",
1919 )
1920 .into());
1921 }
1922 };
1923 *self = make_new_self(bare)?;
1924 Ok(())
1925 }
1926
1927 fn ref_mut(&mut self) -> Option<&mut dyn Write> {
1928 match self {
1929 Storer(ref mut w) => Some(w as &mut dyn Write),
1930 #[cfg(feature = "deflate-flate2")]
1931 GenericZipWriter::Deflater(ref mut w) => Some(w as &mut dyn Write),
1932 #[cfg(feature = "deflate-zopfli")]
1933 GenericZipWriter::ZopfliDeflater(w) => Some(w as &mut dyn Write),
1934 #[cfg(feature = "deflate-zopfli")]
1935 GenericZipWriter::BufferedZopfliDeflater(w) => Some(w as &mut dyn Write),
1936 #[cfg(feature = "bzip2")]
1937 GenericZipWriter::Bzip2(ref mut w) => Some(w as &mut dyn Write),
1938 #[cfg(feature = "zstd")]
1939 GenericZipWriter::Zstd(ref mut w) => Some(w as &mut dyn Write),
1940 #[cfg(feature = "xz")]
1941 GenericZipWriter::Xz(ref mut w) => Some(w as &mut dyn Write),
1942 #[cfg(feature = "ppmd")]
1943 GenericZipWriter::Ppmd(ref mut w) => Some(w as &mut dyn Write),
1944 Closed => None,
1945 }
1946 }
1947
1948 const fn is_closed(&self) -> bool {
1949 matches!(*self, Closed)
1950 }
1951
1952 fn get_plain(&mut self) -> &mut W {
1953 match *self {
1954 Storer(MaybeEncrypted::Unencrypted(ref mut w)) => w,
1955 _ => panic!("Should have switched to stored and unencrypted beforehand"),
1956 }
1957 }
1958
1959 fn unwrap(self) -> W {
1960 match self {
1961 Storer(MaybeEncrypted::Unencrypted(w)) => w,
1962 _ => panic!("Should have switched to stored and unencrypted beforehand"),
1963 }
1964 }
1965}
1966
1967#[cfg(feature = "_deflate-any")]
1968fn deflate_compression_level_range() -> std::ops::RangeInclusive<i64> {
1969 #[cfg(feature = "deflate-flate2")]
1970 let min = Compression::fast().level() as i64;
1971 #[cfg(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))]
1972 let min = 1;
1973
1974 #[cfg(feature = "deflate-zopfli")]
1975 let max = 264;
1976 #[cfg(all(feature = "deflate-flate2", not(feature = "deflate-zopfli")))]
1977 let max = Compression::best().level() as i64;
1978
1979 min..=max
1980}
1981
1982#[cfg(feature = "bzip2")]
1983fn bzip2_compression_level_range() -> std::ops::RangeInclusive<i64> {
1984 let min = bzip2::Compression::fast().level() as i64;
1985 let max = bzip2::Compression::best().level() as i64;
1986 min..=max
1987}
1988
1989#[cfg(any(
1990 feature = "_deflate-any",
1991 feature = "bzip2",
1992 feature = "ppmd",
1993 feature = "xz",
1994 feature = "zstd",
1995))]
1996fn clamp_opt<T: Ord + Copy, U: Ord + Copy + TryFrom<T>>(
1997 value: T,
1998 range: std::ops::RangeInclusive<U>,
1999) -> Option<T> {
2000 if range.contains(&value.try_into().ok()?) {
2001 Some(value)
2002 } else {
2003 None
2004 }
2005}
2006
2007fn update_aes_extra_data<W: Write + Seek>(writer: &mut W, file: &mut ZipFileData) -> ZipResult<()> {
2008 let Some((aes_mode, version, compression_method)) = file.aes_mode else {
2009 return Ok(());
2010 };
2011
2012 let extra_data_start = file.extra_data_start.unwrap();
2013
2014 writer.seek(SeekFrom::Start(
2015 extra_data_start + file.aes_extra_data_start,
2016 ))?;
2017
2018 let mut buf = Vec::new();
2019
2020 buf.write_u16_le(0x9901)?;
2023 buf.write_u16_le(7)?;
2025 buf.write_u16_le(version as u16)?;
2027 buf.write_all(b"AE")?;
2029 buf.write_all(&[aes_mode as u8])?;
2031 buf.write_u16_le(compression_method.serialize_to_u16())?;
2033
2034 writer.write_all(&buf)?;
2035
2036 let aes_extra_data_start = file.aes_extra_data_start as usize;
2037 let extra_field = Arc::get_mut(file.extra_field.as_mut().unwrap()).unwrap();
2038 extra_field[aes_extra_data_start..aes_extra_data_start + buf.len()].copy_from_slice(&buf);
2039
2040 Ok(())
2041}
2042
2043fn write_data_descriptor<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
2044 if let Some(block) = file.data_descriptor_block() {
2045 block.write(writer)?;
2046 } else {
2047 if file.compressed_size > spec::ZIP64_BYTES_THR {
2049 return Err(ZipError::Io(io::Error::other(
2050 "Large file option has not been set",
2051 )));
2052 }
2053
2054 file.zip64_data_descriptor_block().write(writer)?;
2055 }
2056
2057 Ok(())
2058}
2059
2060fn update_local_file_header<T: Write + Seek>(
2061 writer: &mut T,
2062 file: &mut ZipFileData,
2063) -> ZipResult<()> {
2064 const CRC32_OFFSET: u64 = 14;
2065 writer.seek(SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
2066 writer.write_u32_le(file.crc32)?;
2067 if file.large_file {
2068 writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?;
2069 writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?;
2070
2071 update_local_zip64_extra_field(writer, file)?;
2072
2073 file.compressed_size = spec::ZIP64_BYTES_THR;
2074 file.uncompressed_size = spec::ZIP64_BYTES_THR;
2075 } else {
2076 if file.compressed_size > spec::ZIP64_BYTES_THR {
2078 return Err(ZipError::Io(io::Error::other(
2079 "Large file option has not been set",
2080 )));
2081 }
2082 writer.write_u32_le(file.compressed_size as u32)?;
2083 writer.write_u32_le(file.uncompressed_size as u32)?;
2085 }
2086 Ok(())
2087}
2088
2089fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
2090 let block = file.block()?;
2091 block.write(writer)?;
2092 writer.write_all(&file.file_name_raw)?;
2094 if let Some(extra_field) = &file.extra_field {
2096 writer.write_all(extra_field)?;
2097 }
2098 if let Some(central_extra_field) = &file.central_extra_field {
2099 writer.write_all(central_extra_field)?;
2100 }
2101 writer.write_all(file.file_comment.as_bytes())?;
2103
2104 Ok(())
2105}
2106
2107fn update_local_zip64_extra_field<T: Write + Seek>(
2108 writer: &mut T,
2109 file: &mut ZipFileData,
2110) -> ZipResult<()> {
2111 let block = file.zip64_extra_field_block().ok_or(invalid!(
2112 "Attempted to update a nonexistent ZIP64 extra field"
2113 ))?;
2114
2115 let zip64_extra_field_start = file.header_start
2116 + size_of::<ZipLocalEntryBlock>() as u64
2117 + file.file_name_raw.len() as u64;
2118
2119 writer.seek(SeekFrom::Start(zip64_extra_field_start))?;
2120 let block = block.serialize();
2121 writer.write_all(&block)?;
2122
2123 let extra_field = Arc::get_mut(file.extra_field.as_mut().unwrap()).unwrap();
2124 extra_field[..block.len()].copy_from_slice(&block);
2125
2126 Ok(())
2127}
2128
2129pub struct StreamWriter<W: Write> {
2132 inner: W,
2133 bytes_written: u64,
2134}
2135
2136impl<W: Write> StreamWriter<W> {
2137 pub fn new(inner: W) -> StreamWriter<W> {
2139 Self {
2140 inner,
2141 bytes_written: 0,
2142 }
2143 }
2144
2145 pub fn into_inner(self) -> W {
2147 self.inner
2148 }
2149}
2150
2151impl<W: Write> Write for StreamWriter<W> {
2152 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
2153 let bytes_written = self.inner.write(buf)?;
2154 self.bytes_written += bytes_written as u64;
2155 Ok(bytes_written)
2156 }
2157
2158 fn flush(&mut self) -> io::Result<()> {
2159 self.inner.flush()
2160 }
2161}
2162
2163impl<W: Write> Seek for StreamWriter<W> {
2164 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
2165 match pos {
2166 SeekFrom::Current(0) | SeekFrom::End(0) => return Ok(self.bytes_written),
2167 SeekFrom::Start(x) => {
2168 if x == self.bytes_written {
2169 return Ok(self.bytes_written);
2170 }
2171 }
2172 _ => {}
2173 }
2174 Err(io::Error::new(
2175 ErrorKind::Unsupported,
2176 "seek is not supported",
2177 ))
2178 }
2179}
2180
2181#[cfg(not(feature = "unreserved"))]
2182const EXTRA_FIELD_MAPPING: [u16; 43] = [
2183 0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016, 0x0017,
2184 0x0018, 0x0019, 0x0020, 0x0021, 0x0022, 0x0023, 0x0065, 0x0066, 0x4690, 0x07c8, 0x2605, 0x2705,
2185 0x2805, 0x334d, 0x4341, 0x4453, 0x4704, 0x470f, 0x4b46, 0x4c41, 0x4d49, 0x4f4c, 0x5356, 0x554e,
2186 0x5855, 0x6542, 0x756e, 0x7855, 0xa220, 0xfd4a, 0x9902,
2187];
2188
2189#[cfg(test)]
2190#[allow(unknown_lints)] #[allow(clippy::needless_update)] #[allow(clippy::octal_escapes)] mod test {
2194 use super::{ExtendedFileOptions, FileOptions, FullFileOptions, ZipWriter};
2195 use crate::compression::CompressionMethod;
2196 use crate::result::ZipResult;
2197 use crate::types::DateTime;
2198 use crate::write::EncryptWith::ZipCrypto;
2199 use crate::write::SimpleFileOptions;
2200 use crate::zipcrypto::ZipCryptoKeys;
2201 use crate::CompressionMethod::Stored;
2202 use crate::ZipArchive;
2203 #[cfg(feature = "deflate-flate2")]
2204 use std::io::Read;
2205 use std::io::{Cursor, Write};
2206 use std::marker::PhantomData;
2207 use std::path::PathBuf;
2208
2209 #[test]
2210 fn write_empty_zip() {
2211 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2212 writer.set_comment("ZIP");
2213 let result = writer.finish().unwrap();
2214 assert_eq!(result.get_ref().len(), 25);
2215 assert_eq!(
2216 *result.get_ref(),
2217 [80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 90, 73, 80]
2218 );
2219 }
2220
2221 #[test]
2222 fn unix_permissions_bitmask() {
2223 let options = SimpleFileOptions::default().unix_permissions(0o120777);
2225 assert_eq!(options.permissions, Some(0o777));
2226 }
2227
2228 #[test]
2229 fn write_zip_dir() {
2230 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2231 writer
2232 .add_directory(
2233 "test",
2234 SimpleFileOptions::default().last_modified_time(
2235 DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
2236 ),
2237 )
2238 .unwrap();
2239 assert!(writer
2240 .write(b"writing to a directory is not allowed, and will not write any data")
2241 .is_err());
2242 let result = writer.finish().unwrap();
2243 assert_eq!(result.get_ref().len(), 108);
2244 assert_eq!(
2245 *result.get_ref(),
2246 &[
2247 80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2248 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 1, 2, 20, 3, 20, 0, 0, 0, 0, 0,
2249 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2250 0, 0, 237, 65, 0, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0,
2251 1, 0, 51, 0, 0, 0, 35, 0, 0, 0, 0, 0,
2252 ] as &[u8]
2253 );
2254 }
2255
2256 #[test]
2257 fn write_symlink_simple() {
2258 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2259 writer
2260 .add_symlink(
2261 "name",
2262 "target",
2263 SimpleFileOptions::default().last_modified_time(
2264 DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
2265 ),
2266 )
2267 .unwrap();
2268 assert!(writer
2269 .write(b"writing to a symlink is not allowed and will not write any data")
2270 .is_err());
2271 let result = writer.finish().unwrap();
2272 assert_eq!(result.get_ref().len(), 112);
2273 assert_eq!(
2274 *result.get_ref(),
2275 &[
2276 80u8, 75, 3, 4, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0,
2277 6, 0, 0, 0, 4, 0, 0, 0, 110, 97, 109, 101, 116, 97, 114, 103, 101, 116, 80, 75, 1,
2278 2, 10, 3, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0, 6, 0,
2279 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 161, 0, 0, 0, 0, 110, 97, 109, 101,
2280 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 50, 0, 0, 0, 40, 0, 0, 0, 0, 0
2281 ] as &[u8],
2282 );
2283 }
2284
2285 #[test]
2286 fn test_path_normalization() {
2287 let mut path = PathBuf::new();
2288 path.push("foo");
2289 path.push("bar");
2290 path.push("..");
2291 path.push(".");
2292 path.push("example.txt");
2293 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2294 writer
2295 .start_file_from_path(path, SimpleFileOptions::default())
2296 .unwrap();
2297 let archive = writer.finish_into_readable().unwrap();
2298 assert_eq!(Some("foo/example.txt"), archive.name_for_index(0));
2299 }
2300
2301 #[test]
2302 fn write_symlink_wonky_paths() {
2303 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2304 writer
2305 .add_symlink(
2306 "directory\\link",
2307 "/absolute/symlink\\with\\mixed/slashes",
2308 SimpleFileOptions::default().last_modified_time(
2309 DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
2310 ),
2311 )
2312 .unwrap();
2313 assert!(writer
2314 .write(b"writing to a symlink is not allowed and will not write any data")
2315 .is_err());
2316 let result = writer.finish().unwrap();
2317 assert_eq!(result.get_ref().len(), 162);
2318 assert_eq!(
2319 *result.get_ref(),
2320 &[
2321 80u8, 75, 3, 4, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95, 41, 81, 245, 36, 0, 0, 0,
2322 36, 0, 0, 0, 14, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105,
2323 110, 107, 47, 97, 98, 115, 111, 108, 117, 116, 101, 47, 115, 121, 109, 108, 105,
2324 110, 107, 92, 119, 105, 116, 104, 92, 109, 105, 120, 101, 100, 47, 115, 108, 97,
2325 115, 104, 101, 115, 80, 75, 1, 2, 10, 3, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95,
2326 41, 81, 245, 36, 0, 0, 0, 36, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
2327 161, 0, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105, 110,
2328 107, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 60, 0, 0, 0, 80, 0, 0, 0, 0, 0
2329 ] as &[u8],
2330 );
2331 }
2332
2333 #[test]
2334 fn write_mimetype_zip() {
2335 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2336 let options = FileOptions {
2337 compression_method: Stored,
2338 compression_level: None,
2339 last_modified_time: DateTime::default(),
2340 permissions: Some(33188),
2341 large_file: false,
2342 encrypt_with: None,
2343 extended_options: (),
2344 alignment: 1,
2345 #[cfg(feature = "deflate-zopfli")]
2346 zopfli_buffer_size: None,
2347 #[cfg(feature = "aes-crypto")]
2348 aes_mode: None,
2349 };
2350 writer.start_file("mimetype", options).unwrap();
2351 writer
2352 .write_all(b"application/vnd.oasis.opendocument.text")
2353 .unwrap();
2354 let result = writer.finish().unwrap();
2355
2356 assert_eq!(result.get_ref().len(), 153);
2357 let mut v = Vec::new();
2358 v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
2359 assert_eq!(result.get_ref(), &v);
2360 }
2361
2362 #[cfg(feature = "deflate-flate2")]
2363 const RT_TEST_TEXT: &str = "And I can't stop thinking about the moments that I lost to you\
2364 And I can't stop thinking of things I used to do\
2365 And I can't stop making bad decisions\
2366 And I can't stop eating stuff you make me chew\
2367 I put on a smile like you wanna see\
2368 Another day goes by that I long to be like you";
2369 #[cfg(feature = "deflate-flate2")]
2370 const RT_TEST_FILENAME: &str = "subfolder/sub-subfolder/can't_stop.txt";
2371 #[cfg(feature = "deflate-flate2")]
2372 const SECOND_FILENAME: &str = "different_name.xyz";
2373 #[cfg(feature = "deflate-flate2")]
2374 const THIRD_FILENAME: &str = "third_name.xyz";
2375
2376 #[test]
2377 fn write_non_utf8() {
2378 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2379 let options = FileOptions {
2380 compression_method: Stored,
2381 compression_level: None,
2382 last_modified_time: DateTime::default(),
2383 permissions: Some(33188),
2384 large_file: false,
2385 encrypt_with: None,
2386 extended_options: (),
2387 alignment: 1,
2388 #[cfg(feature = "deflate-zopfli")]
2389 zopfli_buffer_size: None,
2390 #[cfg(feature = "aes-crypto")]
2391 aes_mode: None,
2392 };
2393
2394 let filename = unsafe { String::from_utf8_unchecked(vec![214, 208, 206, 196]) };
2397 writer.start_file(filename, options).unwrap();
2398 writer.write_all(b"encoding GB18030").unwrap();
2399
2400 let filename = unsafe { String::from_utf8_unchecked(vec![147, 250, 149, 182]) };
2403 writer.start_file(filename, options).unwrap();
2404 writer.write_all(b"encoding SHIFT_JIS").unwrap();
2405 let result = writer.finish().unwrap();
2406
2407 assert_eq!(result.get_ref().len(), 224);
2408
2409 let mut v = Vec::new();
2410 v.extend_from_slice(include_bytes!("../tests/data/non_utf8.zip"));
2411
2412 assert_eq!(result.get_ref(), &v);
2413 }
2414
2415 #[test]
2416 fn path_to_string() {
2417 let mut path = PathBuf::new();
2418 #[cfg(windows)]
2419 path.push(r"C:\");
2420 #[cfg(unix)]
2421 path.push("/");
2422 path.push("windows");
2423 path.push("..");
2424 path.push(".");
2425 path.push("system32");
2426 let path_str = super::path_to_string(&path);
2427 assert_eq!(&*path_str, "system32");
2428 }
2429
2430 #[test]
2431 #[cfg(feature = "deflate-flate2")]
2432 fn test_shallow_copy() {
2433 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2434 let options = FileOptions {
2435 compression_method: CompressionMethod::default(),
2436 compression_level: None,
2437 last_modified_time: DateTime::default(),
2438 permissions: Some(33188),
2439 large_file: false,
2440 encrypt_with: None,
2441 extended_options: (),
2442 alignment: 0,
2443 #[cfg(feature = "deflate-zopfli")]
2444 zopfli_buffer_size: None,
2445 #[cfg(feature = "aes-crypto")]
2446 aes_mode: None,
2447 };
2448 writer.start_file(RT_TEST_FILENAME, options).unwrap();
2449 writer.write_all(RT_TEST_TEXT.as_ref()).unwrap();
2450 writer
2451 .shallow_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
2452 .unwrap();
2453 writer
2454 .shallow_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
2455 .expect_err("Duplicate filename");
2456 let zip = writer.finish().unwrap();
2457 let mut writer = ZipWriter::new_append(zip).unwrap();
2458 writer
2459 .shallow_copy_file(SECOND_FILENAME, SECOND_FILENAME)
2460 .expect_err("Duplicate filename");
2461 let mut reader = writer.finish_into_readable().unwrap();
2462 let mut file_names: Vec<&str> = reader.file_names().collect();
2463 file_names.sort();
2464 let mut expected_file_names = vec![RT_TEST_FILENAME, SECOND_FILENAME];
2465 expected_file_names.sort();
2466 assert_eq!(file_names, expected_file_names);
2467 let mut first_file_content = String::new();
2468 reader
2469 .by_name(RT_TEST_FILENAME)
2470 .unwrap()
2471 .read_to_string(&mut first_file_content)
2472 .unwrap();
2473 assert_eq!(first_file_content, RT_TEST_TEXT);
2474 let mut second_file_content = String::new();
2475 reader
2476 .by_name(SECOND_FILENAME)
2477 .unwrap()
2478 .read_to_string(&mut second_file_content)
2479 .unwrap();
2480 assert_eq!(second_file_content, RT_TEST_TEXT);
2481 }
2482
2483 #[test]
2484 #[cfg(feature = "deflate-flate2")]
2485 fn test_deep_copy() {
2486 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2487 let options = FileOptions {
2488 compression_method: CompressionMethod::default(),
2489 compression_level: None,
2490 last_modified_time: DateTime::default(),
2491 permissions: Some(33188),
2492 large_file: false,
2493 encrypt_with: None,
2494 extended_options: (),
2495 alignment: 0,
2496 #[cfg(feature = "deflate-zopfli")]
2497 zopfli_buffer_size: None,
2498 #[cfg(feature = "aes-crypto")]
2499 aes_mode: None,
2500 };
2501 writer.start_file(RT_TEST_FILENAME, options).unwrap();
2502 writer.write_all(RT_TEST_TEXT.as_ref()).unwrap();
2503 writer
2504 .deep_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
2505 .unwrap();
2506 let zip = writer.finish().unwrap().into_inner();
2507 zip.iter().copied().for_each(|x| print!("{x:02x}"));
2508 println!();
2509 let mut writer = ZipWriter::new_append(Cursor::new(zip)).unwrap();
2510 writer
2511 .deep_copy_file(RT_TEST_FILENAME, THIRD_FILENAME)
2512 .unwrap();
2513 let zip = writer.finish().unwrap();
2514 let mut reader = ZipArchive::new(zip).unwrap();
2515 let mut file_names: Vec<&str> = reader.file_names().collect();
2516 file_names.sort();
2517 let mut expected_file_names = vec![RT_TEST_FILENAME, SECOND_FILENAME, THIRD_FILENAME];
2518 expected_file_names.sort();
2519 assert_eq!(file_names, expected_file_names);
2520 let mut first_file_content = String::new();
2521 reader
2522 .by_name(RT_TEST_FILENAME)
2523 .unwrap()
2524 .read_to_string(&mut first_file_content)
2525 .unwrap();
2526 assert_eq!(first_file_content, RT_TEST_TEXT);
2527 let mut second_file_content = String::new();
2528 reader
2529 .by_name(SECOND_FILENAME)
2530 .unwrap()
2531 .read_to_string(&mut second_file_content)
2532 .unwrap();
2533 assert_eq!(second_file_content, RT_TEST_TEXT);
2534 }
2535
2536 #[test]
2537 fn duplicate_filenames() {
2538 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2539 writer
2540 .start_file("foo/bar/test", SimpleFileOptions::default())
2541 .unwrap();
2542 writer
2543 .write_all("The quick brown 🦊 jumps over the lazy 🐕".as_bytes())
2544 .unwrap();
2545 writer
2546 .start_file("foo/bar/test", SimpleFileOptions::default())
2547 .expect_err("Expected duplicate filename not to be allowed");
2548 }
2549
2550 #[test]
2551 fn test_filename_looks_like_zip64_locator() {
2552 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2553 writer
2554 .start_file(
2555 "PK\u{6}\u{7}\0\0\0\u{11}\0\0\0\0\0\0\0\0\0\0\0\0",
2556 SimpleFileOptions::default(),
2557 )
2558 .unwrap();
2559 let zip = writer.finish().unwrap();
2560 let _ = ZipArchive::new(zip).unwrap();
2561 }
2562
2563 #[test]
2564 fn test_filename_looks_like_zip64_locator_2() {
2565 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2566 writer
2567 .start_file(
2568 "PK\u{6}\u{6}\0\0\0\0\0\0\0\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
2569 SimpleFileOptions::default(),
2570 )
2571 .unwrap();
2572 let zip = writer.finish().unwrap();
2573 let _ = ZipArchive::new(zip).unwrap();
2574 }
2575
2576 #[test]
2577 fn test_filename_looks_like_zip64_locator_2a() {
2578 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2579 writer
2580 .start_file(
2581 "PK\u{6}\u{6}PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
2582 SimpleFileOptions::default(),
2583 )
2584 .unwrap();
2585 let zip = writer.finish().unwrap();
2586 let _ = ZipArchive::new(zip).unwrap();
2587 }
2588
2589 #[test]
2590 fn test_filename_looks_like_zip64_locator_3() {
2591 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2592 writer
2593 .start_file("\0PK\u{6}\u{6}", SimpleFileOptions::default())
2594 .unwrap();
2595 writer
2596 .start_file(
2597 "\0\u{4}\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{3}",
2598 SimpleFileOptions::default(),
2599 )
2600 .unwrap();
2601 let zip = writer.finish().unwrap();
2602 let _ = ZipArchive::new(zip).unwrap();
2603 }
2604
2605 #[test]
2606 fn test_filename_looks_like_zip64_locator_4() {
2607 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2608 writer
2609 .start_file("PK\u{6}\u{6}", SimpleFileOptions::default())
2610 .unwrap();
2611 writer
2612 .start_file("\0\0\0\0\0\0", SimpleFileOptions::default())
2613 .unwrap();
2614 writer
2615 .start_file("\0", SimpleFileOptions::default())
2616 .unwrap();
2617 writer.start_file("", SimpleFileOptions::default()).unwrap();
2618 writer
2619 .start_file("\0\0", SimpleFileOptions::default())
2620 .unwrap();
2621 writer
2622 .start_file(
2623 "\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
2624 SimpleFileOptions::default(),
2625 )
2626 .unwrap();
2627 let zip = writer.finish().unwrap();
2628 let _ = ZipArchive::new(zip).unwrap();
2629 }
2630
2631 #[test]
2632 fn test_filename_looks_like_zip64_locator_5() -> ZipResult<()> {
2633 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2634 writer
2635 .add_directory("", SimpleFileOptions::default().with_alignment(21))
2636 .unwrap();
2637 let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
2638 writer.shallow_copy_file("/", "").unwrap();
2639 writer.shallow_copy_file("", "\0").unwrap();
2640 writer.shallow_copy_file("\0", "PK\u{6}\u{6}").unwrap();
2641 let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
2642 writer
2643 .start_file("\0\0\0\0\0\0", SimpleFileOptions::default())
2644 .unwrap();
2645 let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
2646 writer
2647 .start_file(
2648 "#PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
2649 SimpleFileOptions::default(),
2650 )
2651 .unwrap();
2652 let zip = writer.finish().unwrap();
2653 let _ = ZipArchive::new(zip).unwrap();
2654 Ok(())
2655 }
2656
2657 #[test]
2658 #[cfg(feature = "deflate-flate2")]
2659 fn remove_shallow_copy_keeps_original() -> ZipResult<()> {
2660 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2661 writer
2662 .start_file("original", SimpleFileOptions::default())
2663 .unwrap();
2664 writer.write_all(RT_TEST_TEXT.as_bytes()).unwrap();
2665 writer
2666 .shallow_copy_file("original", "shallow_copy")
2667 .unwrap();
2668 writer.abort_file().unwrap();
2669 let mut zip = ZipArchive::new(writer.finish().unwrap()).unwrap();
2670 let mut file = zip.by_name("original").unwrap();
2671 let mut contents = Vec::new();
2672 file.read_to_end(&mut contents).unwrap();
2673 assert_eq!(RT_TEST_TEXT.as_bytes(), contents);
2674 Ok(())
2675 }
2676
2677 #[test]
2678 fn remove_encrypted_file() -> ZipResult<()> {
2679 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2680 let first_file_options = SimpleFileOptions::default()
2681 .with_alignment(65535)
2682 .with_deprecated_encryption(b"Password");
2683 writer.start_file("", first_file_options).unwrap();
2684 writer.abort_file().unwrap();
2685 let zip = writer.finish().unwrap();
2686 let mut writer = ZipWriter::new(zip);
2687 writer.start_file("", SimpleFileOptions::default()).unwrap();
2688 Ok(())
2689 }
2690
2691 #[test]
2692 fn remove_encrypted_aligned_symlink() -> ZipResult<()> {
2693 let mut options = SimpleFileOptions::default();
2694 options = options.with_deprecated_encryption(b"Password");
2695 options.alignment = 65535;
2696 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2697 writer.add_symlink("", "s\t\0\0ggggg\0\0", options).unwrap();
2698 writer.abort_file().unwrap();
2699 let zip = writer.finish().unwrap();
2700 let mut writer = ZipWriter::new_append(zip).unwrap();
2701 writer.start_file("", SimpleFileOptions::default()).unwrap();
2702 Ok(())
2703 }
2704
2705 #[cfg(feature = "deflate-zopfli")]
2706 #[test]
2707 fn zopfli_empty_write() -> ZipResult<()> {
2708 let mut options = SimpleFileOptions::default();
2709 options = options
2710 .compression_method(CompressionMethod::default())
2711 .compression_level(Some(264));
2712 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2713 writer.start_file("", options).unwrap();
2714 writer.write_all(&[]).unwrap();
2715 writer.write_all(&[]).unwrap();
2716 Ok(())
2717 }
2718
2719 #[test]
2720 fn crash_with_no_features() -> ZipResult<()> {
2721 const ORIGINAL_FILE_NAME: &str = "PK\u{6}\u{6}\0\0\0\0\0\0\0\0\0\u{2}g\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\u{7}\0\t'";
2722 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2723 let mut options = SimpleFileOptions::default();
2724 options = options.with_alignment(3584).compression_method(Stored);
2725 writer.start_file(ORIGINAL_FILE_NAME, options)?;
2726 let archive = writer.finish()?;
2727 let mut writer = ZipWriter::new_append(archive)?;
2728 writer.shallow_copy_file(ORIGINAL_FILE_NAME, "\u{6}\\")?;
2729 writer.finish()?;
2730 Ok(())
2731 }
2732
2733 #[test]
2734 fn test_alignment() {
2735 let page_size = 4096;
2736 let options = SimpleFileOptions::default()
2737 .compression_method(Stored)
2738 .with_alignment(page_size);
2739 let mut zip = ZipWriter::new(Cursor::new(Vec::new()));
2740 let contents = b"sleeping";
2741 let () = zip.start_file("sleep", options).unwrap();
2742 let _count = zip.write(&contents[..]).unwrap();
2743 let mut zip = zip.finish_into_readable().unwrap();
2744 let file = zip.by_index(0).unwrap();
2745 assert_eq!(file.name(), "sleep");
2746 assert_eq!(file.data_start(), u64::from(page_size));
2747 }
2748
2749 #[test]
2750 fn test_alignment_2() {
2751 let page_size = 4096;
2752 let mut data = Vec::new();
2753 {
2754 let options = SimpleFileOptions::default()
2755 .compression_method(Stored)
2756 .with_alignment(page_size);
2757 let mut zip = ZipWriter::new(Cursor::new(&mut data));
2758 let contents = b"sleeping";
2759 let () = zip.start_file("sleep", options).unwrap();
2760 let _count = zip.write(&contents[..]).unwrap();
2761 }
2762 assert_eq!(data[4096..4104], b"sleeping"[..]);
2763 {
2764 let mut zip = ZipArchive::new(Cursor::new(&mut data)).unwrap();
2765 let file = zip.by_index(0).unwrap();
2766 assert_eq!(file.name(), "sleep");
2767 assert_eq!(file.data_start(), u64::from(page_size));
2768 }
2769 }
2770
2771 #[test]
2772 fn test_crash_short_read() {
2773 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2774 let comment = vec![
2775 1, 80, 75, 5, 6, 237, 237, 237, 237, 237, 237, 237, 237, 44, 255, 191, 255, 255, 255,
2776 255, 255, 255, 255, 255, 16,
2777 ]
2778 .into_boxed_slice();
2779 writer.set_raw_comment(comment);
2780 let options = SimpleFileOptions::default()
2781 .compression_method(Stored)
2782 .with_alignment(11823);
2783 writer.start_file("", options).unwrap();
2784 writer.write_all(&[255, 255, 44, 255, 0]).unwrap();
2785 let written = writer.finish().unwrap();
2786 let _ = ZipWriter::new_append(written).unwrap();
2787 }
2788
2789 #[cfg(all(feature = "_deflate-any", feature = "aes-crypto"))]
2790 #[test]
2791 fn test_fuzz_failure_2024_05_08() -> ZipResult<()> {
2792 let mut first_writer = ZipWriter::new(Cursor::new(Vec::new()));
2793 let mut second_writer = ZipWriter::new(Cursor::new(Vec::new()));
2794 let options = SimpleFileOptions::default()
2795 .compression_method(Stored)
2796 .with_alignment(46036);
2797 second_writer.add_symlink("\0", "", options)?;
2798 let second_archive = second_writer.finish_into_readable()?.into_inner();
2799 let mut second_writer = ZipWriter::new_append(second_archive)?;
2800 let options = SimpleFileOptions::default()
2801 .compression_method(CompressionMethod::Deflated)
2802 .large_file(true)
2803 .with_alignment(46036)
2804 .with_aes_encryption(crate::AesMode::Aes128, "\0\0");
2805 second_writer.add_symlink("", "", options)?;
2806 let second_archive = second_writer.finish_into_readable()?.into_inner();
2807 let mut second_writer = ZipWriter::new_append(second_archive)?;
2808 let options = SimpleFileOptions::default().compression_method(Stored);
2809 second_writer.start_file(" ", options)?;
2810 let second_archive = second_writer.finish_into_readable()?;
2811 first_writer.merge_archive(second_archive)?;
2812 let _ = ZipArchive::new(first_writer.finish()?)?;
2813 Ok(())
2814 }
2815
2816 #[cfg(all(feature = "bzip2", not(miri)))]
2817 #[test]
2818 fn test_fuzz_failure_2024_06_08() -> ZipResult<()> {
2819 use crate::write::ExtendedFileOptions;
2820 use CompressionMethod::Bzip2;
2821
2822 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2823 writer.set_flush_on_finish_file(false);
2824 const SYMLINK_PATH: &str = "PK\u{6}\u{6}K\u{6}\u{6}\u{6}\0\0\0\0\u{18}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{2},\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{2},\0\0\0\0\0\0\0\0\0\0l\0\0\0\0\0\0PK\u{6}\u{7}P\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0";
2825 let sub_writer = {
2826 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2827 writer.set_flush_on_finish_file(false);
2828 let options = FileOptions {
2829 compression_method: Bzip2,
2830 compression_level: None,
2831 last_modified_time: DateTime::from_date_and_time(1980, 5, 20, 21, 0, 57)?,
2832 permissions: None,
2833 large_file: false,
2834 encrypt_with: None,
2835 extended_options: ExtendedFileOptions {
2836 extra_data: vec![].into(),
2837 central_extra_data: vec![].into(),
2838 },
2839 alignment: 2048,
2840 ..Default::default()
2841 };
2842 writer.add_symlink_from_path(SYMLINK_PATH, "||\0\0\0\0", options)?;
2843 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
2844 writer.deep_copy_file_from_path(SYMLINK_PATH, "")?;
2845 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
2846 writer.abort_file()?;
2847 writer
2848 };
2849 writer.merge_archive(sub_writer.finish_into_readable()?)?;
2850 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
2851 writer.deep_copy_file_from_path(SYMLINK_PATH, "foo")?;
2852 let _ = writer.finish_into_readable()?;
2853 Ok(())
2854 }
2855
2856 #[test]
2857 fn test_short_extra_data() {
2858 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2859 writer.set_flush_on_finish_file(false);
2860 let options = FileOptions {
2861 extended_options: ExtendedFileOptions {
2862 extra_data: vec![].into(),
2863 central_extra_data: vec![99, 0, 15, 0, 207].into(),
2864 },
2865 ..Default::default()
2866 };
2867 assert!(writer.start_file_from_path("", options).is_err());
2868 }
2869
2870 #[test]
2871 #[cfg(not(feature = "unreserved"))]
2872 fn test_invalid_extra_data() -> ZipResult<()> {
2873 use crate::write::ExtendedFileOptions;
2874 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2875 writer.set_flush_on_finish_file(false);
2876 let options = FileOptions {
2877 compression_method: Stored,
2878 compression_level: None,
2879 last_modified_time: DateTime::from_date_and_time(1980, 1, 4, 6, 54, 0)?,
2880 permissions: None,
2881 large_file: false,
2882 encrypt_with: None,
2883 extended_options: ExtendedFileOptions {
2884 extra_data: vec![].into(),
2885 central_extra_data: vec![
2886 7, 0, 15, 0, 207, 117, 177, 117, 112, 2, 0, 255, 255, 131, 255, 255, 255, 80,
2887 185,
2888 ]
2889 .into(),
2890 },
2891 alignment: 32787,
2892 ..Default::default()
2893 };
2894 assert!(writer.start_file_from_path("", options).is_err());
2895 Ok(())
2896 }
2897
2898 #[test]
2899 #[cfg(not(feature = "unreserved"))]
2900 fn test_invalid_extra_data_unreserved() {
2901 use crate::write::ExtendedFileOptions;
2902 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2903 let options = FileOptions {
2904 compression_method: Stored,
2905 compression_level: None,
2906 last_modified_time: DateTime::from_date_and_time(2021, 8, 8, 1, 0, 29).unwrap(),
2907 permissions: None,
2908 large_file: true,
2909 encrypt_with: None,
2910 extended_options: ExtendedFileOptions {
2911 extra_data: vec![].into(),
2912 central_extra_data: vec![
2913 1, 41, 4, 0, 1, 255, 245, 117, 117, 112, 5, 0, 80, 255, 149, 255, 247,
2914 ]
2915 .into(),
2916 },
2917 alignment: 4103,
2918 ..Default::default()
2919 };
2920 assert!(writer.start_file_from_path("", options).is_err());
2921 }
2922
2923 #[cfg(feature = "deflate64")]
2924 #[test]
2925 fn test_fuzz_crash_2024_06_13a() -> ZipResult<()> {
2926 use crate::write::ExtendedFileOptions;
2927 use CompressionMethod::Deflate64;
2928
2929 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2930 writer.set_flush_on_finish_file(false);
2931 let options = FileOptions {
2932 compression_method: Deflate64,
2933 compression_level: None,
2934 last_modified_time: DateTime::from_date_and_time(2039, 4, 17, 6, 18, 19)?,
2935 permissions: None,
2936 large_file: true,
2937 encrypt_with: None,
2938 extended_options: ExtendedFileOptions {
2939 extra_data: vec![].into(),
2940 central_extra_data: vec![].into(),
2941 },
2942 alignment: 4,
2943 ..Default::default()
2944 };
2945 writer.add_directory_from_path("", options)?;
2946 let _ = writer.finish_into_readable()?;
2947 Ok(())
2948 }
2949
2950 #[test]
2951 fn test_fuzz_crash_2024_06_13b() -> ZipResult<()> {
2952 use crate::write::ExtendedFileOptions;
2953 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2954 writer.set_flush_on_finish_file(false);
2955 let sub_writer = {
2956 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2957 writer.set_flush_on_finish_file(false);
2958 let options = FileOptions {
2959 compression_method: Stored,
2960 compression_level: None,
2961 last_modified_time: DateTime::from_date_and_time(1980, 4, 14, 6, 11, 54)?,
2962 permissions: None,
2963 large_file: false,
2964 encrypt_with: None,
2965 extended_options: ExtendedFileOptions {
2966 extra_data: vec![].into(),
2967 central_extra_data: vec![].into(),
2968 },
2969 alignment: 185,
2970 ..Default::default()
2971 };
2972 writer.add_symlink_from_path("", "", options)?;
2973 writer
2974 };
2975 writer.merge_archive(sub_writer.finish_into_readable()?)?;
2976 writer.deep_copy_file_from_path("", "_copy")?;
2977 let _ = writer.finish_into_readable()?;
2978 Ok(())
2979 }
2980
2981 #[test]
2982 fn test_fuzz_crash_2024_06_14() -> ZipResult<()> {
2983 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2984 writer.set_flush_on_finish_file(false);
2985 let sub_writer = {
2986 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2987 writer.set_flush_on_finish_file(false);
2988 let options = FullFileOptions {
2989 compression_method: Stored,
2990 large_file: true,
2991 alignment: 93,
2992 ..Default::default()
2993 };
2994 writer.start_file_from_path("\0", options)?;
2995 writer = ZipWriter::new_append(writer.finish()?)?;
2996 writer.deep_copy_file_from_path("\0", "")?;
2997 writer
2998 };
2999 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3000 writer.deep_copy_file_from_path("", "copy")?;
3001 let _ = writer.finish_into_readable()?;
3002 Ok(())
3003 }
3004
3005 #[test]
3006 fn test_fuzz_crash_2024_06_14a() -> ZipResult<()> {
3007 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3008 writer.set_flush_on_finish_file(false);
3009 let options = FileOptions {
3010 compression_method: Stored,
3011 compression_level: None,
3012 last_modified_time: DateTime::from_date_and_time(2083, 5, 30, 21, 45, 35)?,
3013 permissions: None,
3014 large_file: false,
3015 encrypt_with: None,
3016 extended_options: ExtendedFileOptions {
3017 extra_data: vec![].into(),
3018 central_extra_data: vec![].into(),
3019 },
3020 alignment: 2565,
3021 ..Default::default()
3022 };
3023 writer.add_symlink_from_path("", "", options)?;
3024 writer.abort_file()?;
3025 let options = FileOptions {
3026 compression_method: Stored,
3027 compression_level: None,
3028 last_modified_time: DateTime::default(),
3029 permissions: None,
3030 large_file: false,
3031 encrypt_with: None,
3032 extended_options: ExtendedFileOptions {
3033 extra_data: vec![].into(),
3034 central_extra_data: vec![].into(),
3035 },
3036 alignment: 0,
3037 ..Default::default()
3038 };
3039 writer.start_file_from_path("", options)?;
3040 let _ = writer.finish_into_readable()?;
3041 Ok(())
3042 }
3043
3044 #[allow(deprecated)]
3045 #[test]
3046 fn test_fuzz_crash_2024_06_14b() -> ZipResult<()> {
3047 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3048 writer.set_flush_on_finish_file(false);
3049 let options = FileOptions {
3050 compression_method: Stored,
3051 compression_level: None,
3052 last_modified_time: DateTime::from_date_and_time(2078, 3, 6, 12, 48, 58)?,
3053 permissions: None,
3054 large_file: true,
3055 encrypt_with: None,
3056 extended_options: ExtendedFileOptions {
3057 extra_data: vec![].into(),
3058 central_extra_data: vec![].into(),
3059 },
3060 alignment: 65521,
3061 ..Default::default()
3062 };
3063 writer.start_file_from_path("\u{4}\0@\n//\u{c}", options)?;
3064 writer = ZipWriter::new_append(writer.finish()?)?;
3065 writer.abort_file()?;
3066 let options = FileOptions {
3067 compression_method: CompressionMethod::Unsupported(65535),
3068 compression_level: None,
3069 last_modified_time: DateTime::from_date_and_time(2055, 10, 2, 11, 48, 49)?,
3070 permissions: None,
3071 large_file: true,
3072 encrypt_with: None,
3073 extended_options: ExtendedFileOptions {
3074 extra_data: vec![255, 255, 1, 0, 255, 0, 0, 0, 0].into(),
3075 central_extra_data: vec![].into(),
3076 },
3077 alignment: 65535,
3078 ..Default::default()
3079 };
3080 writer.add_directory_from_path("", options)?;
3081 let _ = writer.finish_into_readable()?;
3082 Ok(())
3083 }
3084
3085 #[test]
3086 fn test_fuzz_crash_2024_06_14c() -> ZipResult<()> {
3087 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3088 writer.set_flush_on_finish_file(false);
3089 let sub_writer = {
3090 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3091 writer.set_flush_on_finish_file(false);
3092 let options = FileOptions {
3093 compression_method: Stored,
3094 compression_level: None,
3095 last_modified_time: DateTime::from_date_and_time(2060, 4, 6, 13, 13, 3)?,
3096 permissions: None,
3097 large_file: true,
3098 encrypt_with: None,
3099 extended_options: ExtendedFileOptions {
3100 extra_data: vec![].into(),
3101 central_extra_data: vec![].into(),
3102 },
3103 alignment: 0,
3104 ..Default::default()
3105 };
3106 writer.start_file_from_path("\0", options)?;
3107 writer.write_all(&([]))?;
3108 writer = ZipWriter::new_append(writer.finish()?)?;
3109 writer.deep_copy_file_from_path("\0", "")?;
3110 writer
3111 };
3112 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3113 writer.deep_copy_file_from_path("", "_copy")?;
3114 let _ = writer.finish_into_readable()?;
3115 Ok(())
3116 }
3117
3118 #[cfg(all(feature = "_deflate-any", feature = "aes-crypto"))]
3119 #[test]
3120 fn test_fuzz_crash_2024_06_14d() -> ZipResult<()> {
3121 use crate::write::EncryptWith::Aes;
3122 use crate::AesMode::Aes256;
3123 use CompressionMethod::Deflated;
3124 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3125 writer.set_flush_on_finish_file(false);
3126 let options = FileOptions {
3127 compression_method: Deflated,
3128 compression_level: Some(5),
3129 last_modified_time: DateTime::from_date_and_time(2107, 4, 8, 15, 54, 19)?,
3130 permissions: None,
3131 large_file: true,
3132 encrypt_with: Some(Aes {
3133 mode: Aes256,
3134 password: "",
3135 }),
3136 extended_options: ExtendedFileOptions {
3137 extra_data: vec![2, 0, 1, 0, 0].into(),
3138 central_extra_data: vec![
3139 35, 229, 2, 0, 41, 41, 231, 44, 2, 0, 52, 233, 82, 201, 0, 0, 3, 0, 2, 0, 233,
3140 255, 3, 0, 2, 0, 26, 154, 38, 251, 0, 0,
3141 ]
3142 .into(),
3143 },
3144 alignment: 65535,
3145 ..Default::default()
3146 };
3147 assert!(writer.add_directory_from_path("", options).is_err());
3148 Ok(())
3149 }
3150
3151 #[test]
3152 fn test_fuzz_crash_2024_06_14e() -> ZipResult<()> {
3153 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3154 writer.set_flush_on_finish_file(false);
3155 let options = FileOptions {
3156 compression_method: Stored,
3157 compression_level: None,
3158 last_modified_time: DateTime::from_date_and_time(1988, 1, 1, 1, 6, 26)?,
3159 permissions: None,
3160 large_file: true,
3161 encrypt_with: None,
3162 extended_options: ExtendedFileOptions {
3163 extra_data: vec![76, 0, 1, 0, 0, 2, 0, 0, 0].into(),
3164 central_extra_data: vec![
3165 1, 149, 1, 0, 255, 3, 0, 0, 0, 2, 255, 0, 0, 12, 65, 1, 0, 0, 67, 149, 0, 0,
3166 76, 149, 2, 0, 149, 149, 67, 149, 0, 0,
3167 ]
3168 .into(),
3169 },
3170 alignment: 65535,
3171 ..Default::default()
3172 };
3173 assert!(writer.add_directory_from_path("", options).is_err());
3174 let _ = writer.finish_into_readable()?;
3175 Ok(())
3176 }
3177
3178 #[allow(deprecated)]
3179 #[test]
3180 fn test_fuzz_crash_2024_06_17() -> ZipResult<()> {
3181 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3182 writer.set_flush_on_finish_file(false);
3183 let sub_writer = {
3184 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3185 writer.set_flush_on_finish_file(false);
3186 let sub_writer = {
3187 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3188 writer.set_flush_on_finish_file(false);
3189 let sub_writer = {
3190 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3191 writer.set_flush_on_finish_file(false);
3192 let sub_writer = {
3193 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3194 writer.set_flush_on_finish_file(false);
3195 let sub_writer = {
3196 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3197 writer.set_flush_on_finish_file(false);
3198 let sub_writer = {
3199 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3200 writer.set_flush_on_finish_file(false);
3201 let sub_writer = {
3202 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3203 writer.set_flush_on_finish_file(false);
3204 let sub_writer = {
3205 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3206 writer.set_flush_on_finish_file(false);
3207 let sub_writer = {
3208 let mut writer =
3209 ZipWriter::new(Cursor::new(Vec::new()));
3210 writer.set_flush_on_finish_file(false);
3211 let options = FileOptions {
3212 compression_method: CompressionMethod::Unsupported(
3213 65535,
3214 ),
3215 compression_level: Some(5),
3216 last_modified_time: DateTime::from_date_and_time(
3217 2107, 2, 8, 15, 0, 0,
3218 )?,
3219 permissions: None,
3220 large_file: true,
3221 encrypt_with: Some(ZipCrypto(
3222 ZipCryptoKeys::of(
3223 0x63ff, 0xc62d3103, 0xfffe00ea,
3224 ),
3225 PhantomData,
3226 )),
3227 extended_options: ExtendedFileOptions {
3228 extra_data: vec![].into(),
3229 central_extra_data: vec![].into(),
3230 },
3231 alignment: 255,
3232 ..Default::default()
3233 };
3234 writer.add_symlink_from_path("1\0PK\u{6}\u{6}\u{b}\u{6}\u{6}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\u{b}\0\0PK\u{1}\u{2},\0\0\0\0\0\0\0\0\0\0\0\u{10}\0\0\0K\u{6}\u{6}\0\0\0\0\0\0\0\0PK\u{2}\u{6}", "", options)?;
3235 writer = ZipWriter::new_append(
3236 writer.finish_into_readable()?.into_inner(),
3237 )?;
3238 writer
3239 };
3240 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3241 writer = ZipWriter::new_append(
3242 writer.finish_into_readable()?.into_inner(),
3243 )?;
3244 let options = FileOptions {
3245 compression_method: Stored,
3246 compression_level: None,
3247 last_modified_time: DateTime::from_date_and_time(
3248 1992, 7, 3, 0, 0, 0,
3249 )?,
3250 permissions: None,
3251 large_file: true,
3252 encrypt_with: None,
3253 extended_options: ExtendedFileOptions {
3254 extra_data: vec![].into(),
3255 central_extra_data: vec![].into(),
3256 },
3257 alignment: 43,
3258 ..Default::default()
3259 };
3260 writer.start_file_from_path(
3261 "\0\0\0\u{3}\0\u{1a}\u{1a}\u{1a}\u{1a}\u{1a}\u{1a}",
3262 options,
3263 )?;
3264 let options = FileOptions {
3265 compression_method: Stored,
3266 compression_level: None,
3267 last_modified_time: DateTime::from_date_and_time(
3268 2006, 3, 27, 2, 24, 26,
3269 )?,
3270 permissions: None,
3271 large_file: false,
3272 encrypt_with: None,
3273 extended_options: ExtendedFileOptions {
3274 extra_data: vec![].into(),
3275 central_extra_data: vec![].into(),
3276 },
3277 alignment: 26,
3278 ..Default::default()
3279 };
3280 writer.start_file_from_path("\0K\u{6}\u{6}\0PK\u{6}\u{7}PK\u{6}\u{6}\0\0\0\0\0\0\0\0PK\u{2}\u{6}", options)?;
3281 writer = ZipWriter::new_append(
3282 writer.finish_into_readable()?.into_inner(),
3283 )?;
3284 let options = FileOptions {
3285 compression_method: Stored,
3286 compression_level: Some(17),
3287 last_modified_time: DateTime::from_date_and_time(
3288 2103, 4, 10, 23, 15, 18,
3289 )?,
3290 permissions: Some(3284386755),
3291 large_file: true,
3292 encrypt_with: Some(ZipCrypto(
3293 ZipCryptoKeys::of(
3294 0x8888c5bf, 0x88888888, 0xff888888,
3295 ),
3296 PhantomData,
3297 )),
3298 extended_options: ExtendedFileOptions {
3299 extra_data: vec![3, 0, 1, 0, 255, 144, 136, 0, 0]
3300 .into(),
3301 central_extra_data: vec![].into(),
3302 },
3303 alignment: 65535,
3304 ..Default::default()
3305 };
3306 writer.add_symlink_from_path("", "\nu", options)?;
3307 writer = ZipWriter::new_append(writer.finish()?)?;
3308 writer
3309 };
3310 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3311 writer = ZipWriter::new_append(
3312 writer.finish_into_readable()?.into_inner(),
3313 )?;
3314 writer
3315 };
3316 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3317 writer = ZipWriter::new_append(writer.finish()?)?;
3318 writer
3319 };
3320 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3321 writer =
3322 ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3323 writer.abort_file()?;
3324 let options = FileOptions {
3325 compression_method: CompressionMethod::Unsupported(49603),
3326 compression_level: Some(20),
3327 last_modified_time: DateTime::from_date_and_time(
3328 2047, 4, 14, 3, 15, 14,
3329 )?,
3330 permissions: Some(3284386755),
3331 large_file: true,
3332 encrypt_with: Some(ZipCrypto(
3333 ZipCryptoKeys::of(0xc3, 0x0, 0x0),
3334 PhantomData,
3335 )),
3336 extended_options: ExtendedFileOptions {
3337 extra_data: vec![].into(),
3338 central_extra_data: vec![].into(),
3339 },
3340 alignment: 0,
3341 ..Default::default()
3342 };
3343 writer.add_directory_from_path("", options)?;
3344 writer.deep_copy_file_from_path("/", "")?;
3345 writer.shallow_copy_file_from_path("", "copy")?;
3346 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3347 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3348 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3349 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3350 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3351 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3352 writer
3353 };
3354 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3355 writer
3356 };
3357 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3358 writer
3359 };
3360 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3361 writer
3362 };
3363 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3364 writer
3365 };
3366 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3367 let _ = writer.finish_into_readable()?;
3368 Ok(())
3369 }
3370
3371 #[test]
3372 fn test_fuzz_crash_2024_06_17a() -> ZipResult<()> {
3373 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3374 writer.set_flush_on_finish_file(false);
3375 const PATH_1: &str = "\0I\01\0P\0\0\u{2}\0\0\u{1a}\u{1a}\u{1a}\u{1a}\u{1b}\u{1a}UT\u{5}\0\0\u{1a}\u{1a}\u{1a}\u{1a}UT\u{5}\0\u{1}\0\u{1a}\u{1a}\u{1a}UT\t\0uc\u{5}\0\0\0\0\u{7f}\u{7f}\u{7f}\u{7f}PK\u{6}";
3376 let sub_writer = {
3377 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3378 writer.set_flush_on_finish_file(false);
3379 let sub_writer = {
3380 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3381 writer.set_flush_on_finish_file(false);
3382 let options = FileOptions {
3383 compression_method: Stored,
3384 compression_level: None,
3385 last_modified_time: DateTime::from_date_and_time(1981, 1, 1, 0, 24, 21)?,
3386 permissions: Some(16908288),
3387 large_file: false,
3388 encrypt_with: None,
3389 extended_options: ExtendedFileOptions {
3390 extra_data: vec![].into(),
3391 central_extra_data: vec![].into(),
3392 },
3393 alignment: 20555,
3394 ..Default::default()
3395 };
3396 writer.start_file_from_path(
3397 "\0\u{7}\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{2};",
3398 options,
3399 )?;
3400 writer.write_all(
3401 &([
3402 255, 255, 255, 255, 253, 253, 253, 203, 203, 203, 253, 253, 253, 253, 255,
3403 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 249, 191, 225, 225,
3404 241, 197,
3405 ]),
3406 )?;
3407 writer.write_all(
3408 &([
3409 197, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
3410 255, 75, 0,
3411 ]),
3412 )?;
3413 writer
3414 };
3415 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3416 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3417 let options = FileOptions {
3418 compression_method: Stored,
3419 compression_level: None,
3420 last_modified_time: DateTime::from_date_and_time(1980, 11, 14, 10, 46, 47)?,
3421 permissions: None,
3422 large_file: false,
3423 encrypt_with: None,
3424 extended_options: ExtendedFileOptions {
3425 extra_data: vec![].into(),
3426 central_extra_data: vec![].into(),
3427 },
3428 alignment: 0,
3429 ..Default::default()
3430 };
3431 writer.start_file_from_path(PATH_1, options)?;
3432 writer.deep_copy_file_from_path(PATH_1, "eee\u{6}\0\0\0\0\0\0\0\0\0\0\0$\0\0\0\0\0\0\u{7f}\u{7f}PK\u{6}\u{6}K\u{6}\u{6}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{1e},\0\0\0\0\0\0\0\0\0\0\0\u{8}\0*\0\0\u{1}PK\u{6}\u{7}PK\u{6}\u{6}\0\0\0\0\0\0\0\0}K\u{2}\u{6}")?;
3433 writer
3434 };
3435 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3436 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3437 writer.deep_copy_file_from_path(PATH_1, "")?;
3438 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3439 writer.shallow_copy_file_from_path("", "copy")?;
3440 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3441 let _ = writer.finish_into_readable()?;
3442 Ok(())
3443 }
3444
3445 #[test]
3446 #[allow(clippy::octal_escapes)]
3447 #[cfg(all(feature = "bzip2", not(miri)))]
3448 fn test_fuzz_crash_2024_06_17b() -> ZipResult<()> {
3449 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3450 writer.set_flush_on_finish_file(false);
3451 let sub_writer = {
3452 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3453 writer.set_flush_on_finish_file(false);
3454 let sub_writer = {
3455 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3456 writer.set_flush_on_finish_file(false);
3457 let sub_writer = {
3458 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3459 writer.set_flush_on_finish_file(false);
3460 let sub_writer = {
3461 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3462 writer.set_flush_on_finish_file(false);
3463 let sub_writer = {
3464 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3465 writer.set_flush_on_finish_file(false);
3466 let sub_writer = {
3467 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3468 writer.set_flush_on_finish_file(false);
3469 let sub_writer = {
3470 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3471 writer.set_flush_on_finish_file(false);
3472 let sub_writer = {
3473 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3474 writer.set_flush_on_finish_file(false);
3475 let options = FileOptions {
3476 compression_method: Stored,
3477 compression_level: None,
3478 last_modified_time: DateTime::from_date_and_time(
3479 1981, 1, 1, 0, 0, 21,
3480 )?,
3481 permissions: Some(16908288),
3482 large_file: false,
3483 encrypt_with: None,
3484 extended_options: ExtendedFileOptions {
3485 extra_data: vec![].into(),
3486 central_extra_data: vec![].into(),
3487 },
3488 alignment: 20555,
3489 ..Default::default()
3490 };
3491 writer.start_file_from_path("\0\u{7}\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{2};\u{1a}\u{18}\u{1a}UT\t.........................\0u", options)?;
3492 writer
3493 };
3494 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3495 let options = FileOptions {
3496 compression_method: CompressionMethod::Bzip2,
3497 compression_level: Some(5),
3498 last_modified_time: DateTime::from_date_and_time(
3499 2055, 7, 7, 3, 6, 6,
3500 )?,
3501 permissions: None,
3502 large_file: false,
3503 encrypt_with: None,
3504 extended_options: ExtendedFileOptions {
3505 extra_data: vec![].into(),
3506 central_extra_data: vec![].into(),
3507 },
3508 alignment: 0,
3509 ..Default::default()
3510 };
3511 writer.start_file_from_path("\0\0\0\0..\0\0\0\0\0\u{7f}\u{7f}PK\u{6}\u{6}K\u{6}\u{6}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{1e},\0\0\0\0\0\0\0\0\0\0\0\u{8}\0*\0\0\u{1}PK\u{6}\u{7}PK\u{6}\u{6}\0\0\0\0\0\0\0\0}K\u{2}\u{6}", options)?;
3512 writer = ZipWriter::new_append(
3513 writer.finish_into_readable()?.into_inner(),
3514 )?;
3515 writer
3516 };
3517 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3518 writer = ZipWriter::new_append(
3519 writer.finish_into_readable()?.into_inner(),
3520 )?;
3521 writer
3522 };
3523 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3524 writer =
3525 ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3526 writer
3527 };
3528 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3529 writer =
3530 ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3531 writer
3532 };
3533 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3534 writer
3535 };
3536 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3537 writer
3538 };
3539 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3540 writer
3541 };
3542 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3543 let _ = writer.finish_into_readable()?;
3544 Ok(())
3545 }
3546
3547 #[test]
3548 fn test_fuzz_crash_2024_06_18() -> ZipResult<()> {
3549 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3550 writer.set_raw_comment(Box::<[u8]>::from([
3551 80, 75, 5, 6, 255, 255, 255, 255, 255, 255, 80, 75, 5, 6, 255, 255, 255, 255, 255, 255,
3552 13, 0, 13, 13, 13, 13, 13, 255, 255, 255, 255, 255, 255, 255, 255,
3553 ]));
3554 let sub_writer = {
3555 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3556 writer.set_flush_on_finish_file(false);
3557 writer.set_raw_comment(Box::new([]));
3558 writer
3559 };
3560 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3561 writer = ZipWriter::new_append(writer.finish()?)?;
3562 let _ = writer.finish_into_readable()?;
3563 Ok(())
3564 }
3565
3566 #[test]
3567 fn test_fuzz_crash_2024_06_18a() -> ZipResult<()> {
3568 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3569 writer.set_flush_on_finish_file(false);
3570 writer.set_raw_comment(Box::<[u8]>::from([]));
3571 let sub_writer = {
3572 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3573 writer.set_flush_on_finish_file(false);
3574 let sub_writer = {
3575 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3576 writer.set_flush_on_finish_file(false);
3577 let sub_writer = {
3578 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3579 writer.set_flush_on_finish_file(false);
3580 let options = FullFileOptions {
3581 compression_method: Stored,
3582 compression_level: None,
3583 last_modified_time: DateTime::from_date_and_time(2107, 4, 8, 14, 0, 19)?,
3584 permissions: None,
3585 large_file: false,
3586 encrypt_with: None,
3587 extended_options: ExtendedFileOptions {
3588 extra_data: vec![
3589 182, 180, 1, 0, 180, 182, 74, 0, 0, 200, 0, 0, 0, 2, 0, 0, 0,
3590 ]
3591 .into(),
3592 central_extra_data: vec![].into(),
3593 },
3594 alignment: 1542,
3595 ..Default::default()
3596 };
3597 writer.start_file_from_path("\0\0PK\u{6}\u{6}K\u{6}PK\u{3}\u{4}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\u{1}\u{1}\0PK\u{1}\u{2},\0\0\0\0\0\0\0\0\0\0\0P\u{7}\u{4}/.\0KP\0\0;\0\0\0\u{1e}\0\0\0\0\0\0\0\0\0\0\0\0\0", options)?;
3598 let finished = writer.finish_into_readable()?;
3599 assert_eq!(1, finished.file_names().count());
3600 writer = ZipWriter::new_append(finished.into_inner())?;
3601 let options = FullFileOptions {
3602 compression_method: Stored,
3603 compression_level: Some(5),
3604 last_modified_time: DateTime::from_date_and_time(2107, 4, 1, 0, 0, 0)?,
3605 permissions: None,
3606 large_file: false,
3607 encrypt_with: Some(ZipCrypto(
3608 ZipCryptoKeys::of(0x0, 0x62e4b50, 0x100),
3609 PhantomData,
3610 )),
3611 ..Default::default()
3612 };
3613 writer.add_symlink_from_path(
3614 "\0K\u{6}\0PK\u{6}\u{7}PK\u{6}\u{6}\0\0\0\0\0\0\0\0PK\u{2}\u{6}",
3615 "\u{8}\0\0\0\0/\0",
3616 options,
3617 )?;
3618 let finished = writer.finish_into_readable()?;
3619 assert_eq!(2, finished.file_names().count());
3620 writer = ZipWriter::new_append(finished.into_inner())?;
3621 assert_eq!(2, writer.files.len());
3622 writer
3623 };
3624 let finished = sub_writer.finish_into_readable()?;
3625 assert_eq!(2, finished.file_names().count());
3626 writer.merge_archive(finished)?;
3627 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3628 writer
3629 };
3630 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3631 writer
3632 };
3633 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3634 let _ = writer.finish_into_readable()?;
3635 Ok(())
3636 }
3637
3638 #[cfg(all(feature = "bzip2", feature = "aes-crypto", not(miri)))]
3639 #[test]
3640 fn test_fuzz_crash_2024_06_18b() -> ZipResult<()> {
3641 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3642 writer.set_flush_on_finish_file(true);
3643 writer.set_raw_comment([0].into());
3644 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3645 assert_eq!(writer.get_raw_comment()[0], 0);
3646 let options = FileOptions {
3647 compression_method: CompressionMethod::Bzip2,
3648 compression_level: None,
3649 last_modified_time: DateTime::from_date_and_time(2009, 6, 3, 13, 37, 39)?,
3650 permissions: Some(2644352413),
3651 large_file: true,
3652 encrypt_with: Some(crate::write::EncryptWith::Aes {
3653 mode: crate::AesMode::Aes256,
3654 password: "",
3655 }),
3656 extended_options: ExtendedFileOptions {
3657 extra_data: vec![].into(),
3658 central_extra_data: vec![].into(),
3659 },
3660 alignment: 255,
3661 ..Default::default()
3662 };
3663 writer.add_symlink_from_path("", "", options)?;
3664 writer.deep_copy_file_from_path("", "PK\u{5}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\u{4}\0\0\0")?;
3665 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3666 assert_eq!(writer.get_raw_comment()[0], 0);
3667 writer.deep_copy_file_from_path(
3668 "PK\u{5}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\u{4}\0\0\0",
3669 "\u{2}yy\u{5}qu\0",
3670 )?;
3671 let finished = writer.finish()?;
3672 let archive = ZipArchive::new(finished.clone())?;
3673 assert_eq!(archive.comment(), [0]);
3674 writer = ZipWriter::new_append(finished)?;
3675 assert_eq!(writer.get_raw_comment()[0], 0);
3676 let _ = writer.finish_into_readable()?;
3677 Ok(())
3678 }
3679
3680 #[test]
3681 fn test_fuzz_crash_2024_06_19() -> ZipResult<()> {
3682 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3683 writer.set_flush_on_finish_file(false);
3684 let options = FileOptions {
3685 compression_method: Stored,
3686 compression_level: None,
3687 last_modified_time: DateTime::from_date_and_time(1980, 3, 1, 19, 55, 58)?,
3688 permissions: None,
3689 large_file: false,
3690 encrypt_with: None,
3691 extended_options: ExtendedFileOptions {
3692 extra_data: vec![].into(),
3693 central_extra_data: vec![].into(),
3694 },
3695 alignment: 256,
3696 ..Default::default()
3697 };
3698 writer.start_file_from_path(
3699 "\0\0\0PK\u{5}\u{6}\0\0\0\0\u{1}\0\u{12}\u{6}\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\0",
3700 options,
3701 )?;
3702 writer.set_flush_on_finish_file(false);
3703 writer.shallow_copy_file_from_path(
3704 "\0\0\0PK\u{5}\u{6}\0\0\0\0\u{1}\0\u{12}\u{6}\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\0",
3705 "",
3706 )?;
3707 writer.set_flush_on_finish_file(false);
3708 writer.deep_copy_file_from_path("", "copy")?;
3709 writer.abort_file()?;
3710 writer.set_flush_on_finish_file(false);
3711 writer.set_raw_comment([255, 0].into());
3712 writer.abort_file()?;
3713 assert_eq!(writer.get_raw_comment(), [255, 0]);
3714 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3715 assert_eq!(writer.get_raw_comment(), [255, 0]);
3716 writer.set_flush_on_finish_file(false);
3717 let options = FileOptions {
3718 compression_method: Stored,
3719 compression_level: None,
3720 last_modified_time: DateTime::default(),
3721 permissions: None,
3722 large_file: false,
3723 encrypt_with: None,
3724 extended_options: ExtendedFileOptions {
3725 extra_data: vec![].into(),
3726 central_extra_data: vec![].into(),
3727 },
3728 ..Default::default()
3729 };
3730 writer.start_file_from_path("", options)?;
3731 assert_eq!(writer.get_raw_comment(), [255, 0]);
3732 let archive = writer.finish_into_readable()?;
3733 assert_eq!(archive.comment(), [255, 0]);
3734 Ok(())
3735 }
3736
3737 #[test]
3738 fn fuzz_crash_2024_06_21() -> ZipResult<()> {
3739 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3740 writer.set_flush_on_finish_file(false);
3741 let options = FullFileOptions {
3742 compression_method: Stored,
3743 compression_level: None,
3744 last_modified_time: DateTime::from_date_and_time(1980, 2, 1, 0, 0, 0)?,
3745 permissions: None,
3746 large_file: false,
3747 encrypt_with: None,
3748 ..Default::default()
3749 };
3750 const LONG_PATH: &str = "\0@PK\u{6}\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0@/\0\0\00ΝPK\u{5}\u{6}O\0\u{10}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0@PK\u{6}\u{7}\u{6}\0/@\0\0\0\0\0\0\0\0 \0\0";
3751 writer.start_file_from_path(LONG_PATH, options)?;
3752 writer = ZipWriter::new_append(writer.finish()?)?;
3753 writer.deep_copy_file_from_path(LONG_PATH, "oo\0\0\0")?;
3754 writer.abort_file()?;
3755 writer.set_raw_comment([33].into());
3756 let archive = writer.finish_into_readable()?;
3757 writer = ZipWriter::new_append(archive.into_inner())?;
3758 assert!(writer.get_raw_comment().starts_with(&[33]));
3759 let archive = writer.finish_into_readable()?;
3760 assert!(archive.comment().starts_with(&[33]));
3761 Ok(())
3762 }
3763
3764 #[test]
3765 #[cfg(all(feature = "bzip2", not(miri)))]
3766 fn fuzz_crash_2024_07_17() -> ZipResult<()> {
3767 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3768 writer.set_flush_on_finish_file(false);
3769 let options = FileOptions {
3770 compression_method: CompressionMethod::Bzip2,
3771 compression_level: None,
3772 last_modified_time: DateTime::from_date_and_time(2095, 2, 16, 21, 0, 1)?,
3773 permissions: Some(84238341),
3774 large_file: true,
3775 encrypt_with: None,
3776 extended_options: ExtendedFileOptions {
3777 extra_data: vec![117, 99, 6, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 2, 0, 0, 0].into(),
3778 central_extra_data: vec![].into(),
3779 },
3780 alignment: 65535,
3781 ..Default::default()
3782 };
3783 writer.start_file_from_path("", options)?;
3784 writer.deep_copy_file_from_path("", "copy")?;
3786 let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3787 Ok(())
3788 }
3789
3790 #[test]
3791 fn fuzz_crash_2024_07_19() -> ZipResult<()> {
3792 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3793 writer.set_flush_on_finish_file(false);
3794 let options = FileOptions {
3795 compression_method: Stored,
3796 compression_level: None,
3797 last_modified_time: DateTime::from_date_and_time(1980, 6, 1, 0, 34, 47)?,
3798 permissions: None,
3799 large_file: true,
3800 encrypt_with: None,
3801 extended_options: ExtendedFileOptions {
3802 extra_data: vec![].into(),
3803 central_extra_data: vec![].into(),
3804 },
3805 alignment: 45232,
3806 ..Default::default()
3807 };
3808 writer.add_directory_from_path("", options)?;
3809 writer.deep_copy_file_from_path("/", "")?;
3810 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3811 writer.deep_copy_file_from_path("", "copy")?;
3812 let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3813 Ok(())
3814 }
3815
3816 #[test]
3817 #[cfg(feature = "aes-crypto")]
3818 fn fuzz_crash_2024_07_19a() -> ZipResult<()> {
3819 use crate::write::EncryptWith::Aes;
3820 use crate::AesMode::Aes128;
3821 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3822 writer.set_flush_on_finish_file(false);
3823 let options = FileOptions {
3824 compression_method: Stored,
3825 compression_level: None,
3826 last_modified_time: DateTime::from_date_and_time(2107, 6, 5, 13, 0, 21)?,
3827 permissions: None,
3828 large_file: true,
3829 encrypt_with: Some(Aes {
3830 mode: Aes128,
3831 password: "",
3832 }),
3833 extended_options: ExtendedFileOptions {
3834 extra_data: vec![3, 0, 4, 0, 209, 53, 53, 8, 2, 61, 0, 0].into(),
3835 central_extra_data: vec![].into(),
3836 },
3837 alignment: 65535,
3838 ..Default::default()
3839 };
3840 writer.start_file_from_path("", options)?;
3841 let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3842 Ok(())
3843 }
3844
3845 #[test]
3846 fn fuzz_crash_2024_07_20() -> ZipResult<()> {
3847 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3848 writer.set_flush_on_finish_file(true);
3849 let options = FileOptions {
3850 compression_method: Stored,
3851 compression_level: None,
3852 last_modified_time: DateTime::from_date_and_time(2041, 8, 2, 19, 38, 0)?,
3853 permissions: None,
3854 large_file: false,
3855 encrypt_with: None,
3856 extended_options: ExtendedFileOptions {
3857 extra_data: vec![].into(),
3858 central_extra_data: vec![].into(),
3859 },
3860 alignment: 0,
3861 ..Default::default()
3862 };
3863 writer.add_directory_from_path("\0\0\0\0\0\0\07黻", options)?;
3864 let sub_writer = {
3865 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3866 writer.set_flush_on_finish_file(false);
3867 let options = FileOptions {
3868 compression_method: Stored,
3869 compression_level: None,
3870 last_modified_time: DateTime::default(),
3871 permissions: None,
3872 large_file: false,
3873 encrypt_with: None,
3874 extended_options: ExtendedFileOptions {
3875 extra_data: vec![].into(),
3876 central_extra_data: vec![].into(),
3877 },
3878 alignment: 4,
3879 ..Default::default()
3880 };
3881 writer.add_directory_from_path("\0\0\0黻", options)?;
3882 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3883 writer.abort_file()?;
3884 let options = FileOptions {
3885 compression_method: Stored,
3886 compression_level: None,
3887 last_modified_time: DateTime::from_date_and_time(1980, 1, 1, 0, 7, 0)?,
3888 permissions: Some(2663103419),
3889 large_file: false,
3890 encrypt_with: None,
3891 extended_options: ExtendedFileOptions {
3892 extra_data: vec![].into(),
3893 central_extra_data: vec![].into(),
3894 },
3895 alignment: 32256,
3896 ..Default::default()
3897 };
3898 writer.add_directory_from_path("\0", options)?;
3899 writer = ZipWriter::new_append(writer.finish()?)?;
3900 writer
3901 };
3902 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3903 let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3904 Ok(())
3905 }
3906
3907 #[test]
3908 fn fuzz_crash_2024_07_21() -> ZipResult<()> {
3909 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3910 let sub_writer = {
3911 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3912 writer.add_directory_from_path(
3913 "",
3914 FileOptions {
3915 compression_method: Stored,
3916 compression_level: None,
3917 last_modified_time: DateTime::from_date_and_time(2105, 8, 1, 15, 0, 0)?,
3918 permissions: None,
3919 large_file: false,
3920 encrypt_with: None,
3921 extended_options: ExtendedFileOptions {
3922 extra_data: vec![].into(),
3923 central_extra_data: vec![].into(),
3924 },
3925 alignment: 0,
3926 ..Default::default()
3927 },
3928 )?;
3929 writer.abort_file()?;
3930 let mut writer = ZipWriter::new_append(writer.finish()?)?;
3931 writer.add_directory_from_path(
3932 "",
3933 FileOptions {
3934 compression_method: Stored,
3935 compression_level: None,
3936 last_modified_time: DateTime::default(),
3937 permissions: None,
3938 large_file: false,
3939 encrypt_with: None,
3940 extended_options: ExtendedFileOptions {
3941 extra_data: vec![].into(),
3942 central_extra_data: vec![].into(),
3943 },
3944 alignment: 16,
3945 ..Default::default()
3946 },
3947 )?;
3948 ZipWriter::new_append(writer.finish()?)?
3949 };
3950 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3951 let writer = ZipWriter::new_append(writer.finish()?)?;
3952 let _ = writer.finish_into_readable()?;
3953
3954 Ok(())
3955 }
3956}