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