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