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