1use crate::compression::CompressionMethod;
4use crate::datetime::DateTime;
5use crate::extra_fields::AexEncryption;
6use crate::extra_fields::UsedExtraField;
7use crate::extra_fields::Zip64ExtendedInformation;
8use crate::read::{Config, ZipArchive, ZipFile, parse_single_extra_field};
9use crate::result::{ZipError, ZipResult, invalid};
10use crate::spec::{self, FixedSizeBlock, Magic, Pod, Zip32CDEBlock, ZipLocalEntryBlock};
11use crate::types::{AesVendorVersion, MIN_VERSION, System, ZipFileData, ZipRawValues, ffi};
12use core::default::Default;
13use core::fmt::{Debug, Formatter};
14use core::marker::PhantomData;
15use core::mem::{self, offset_of, size_of};
16use core::str::{Utf8Error, from_utf8};
17use crc32fast::Hasher;
18use indexmap::IndexMap;
19use std::borrow::ToOwned;
20use std::io::{self, Read, Seek, Write};
21use std::io::{BufReader, SeekFrom};
22use std::io::{Cursor, ErrorKind};
23use std::path::Path;
24use std::sync::Arc;
25
26pub use crate::types::{FileOptions, SimpleFileOptions};
28
29#[allow(clippy::large_enum_variant)]
30enum MaybeEncrypted<W> {
31 Unencrypted(W),
32 #[cfg(feature = "aes-crypto")]
33 Aes(crate::aes::AesWriter<W>),
34 ZipCrypto(crate::zipcrypto::ZipCryptoWriter<W>),
35}
36
37impl<W: Write> MaybeEncrypted<W> {
38 fn get_ref(&self) -> &W {
39 match self {
40 MaybeEncrypted::Unencrypted(w) => w,
41 #[cfg(feature = "aes-crypto")]
42 MaybeEncrypted::Aes(w) => w.get_ref(),
43 MaybeEncrypted::ZipCrypto(w) => w.get_ref(),
44 }
45 }
46 unsafe fn get_mut(&mut self) -> &mut W {
47 match self {
48 MaybeEncrypted::Unencrypted(w) => w,
49 #[cfg(feature = "aes-crypto")]
50 MaybeEncrypted::Aes(w) => unsafe { w.get_mut() },
51 MaybeEncrypted::ZipCrypto(w) => w.get_mut(),
52 }
53 }
54}
55
56impl<W> Debug for MaybeEncrypted<W> {
57 fn fmt(&self, f: &mut Formatter<'_>) -> core::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
87#[allow(clippy::large_enum_variant)]
88enum GenericZipWriter<W: Write + Seek> {
89 Closed,
90 Storer(MaybeEncrypted<W>),
91 #[cfg(feature = "deflate-flate2")]
92 Deflater(flate2::write::DeflateEncoder<MaybeEncrypted<W>>),
93 #[cfg(feature = "deflate-zopfli")]
94 ZopfliDeflater(zopfli::DeflateEncoder<MaybeEncrypted<W>>),
95 #[cfg(feature = "deflate-zopfli")]
96 BufferedZopfliDeflater(std::io::BufWriter<zopfli::DeflateEncoder<MaybeEncrypted<W>>>),
97 #[cfg(feature = "_bzip2_any")]
98 Bzip2(bzip2::write::BzEncoder<MaybeEncrypted<W>>),
99 #[cfg(feature = "zstd")]
100 Zstd(zstd::stream::write::Encoder<'static, MaybeEncrypted<W>>),
101 #[cfg(feature = "xz")]
102 Xz(Box<lzma_rust2::XzWriter<MaybeEncrypted<W>>>),
103 #[cfg(feature = "ppmd")]
104 Ppmd(Box<ppmd_rust::Ppmd8Encoder<MaybeEncrypted<W>>>),
105}
106
107impl<W: Write + Seek> Debug for GenericZipWriter<W> {
108 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
109 match self {
110 Self::Closed => f.write_str("Closed"),
111 Self::Storer(w) => f.write_fmt(format_args!("Storer({w:?})")),
112 #[cfg(feature = "deflate-flate2")]
113 Self::Deflater(w) => f.write_fmt(format_args!("Deflater({:?})", w.get_ref())),
114 #[cfg(feature = "deflate-zopfli")]
115 Self::ZopfliDeflater(_) => f.write_str("ZopfliDeflater"),
116 #[cfg(feature = "deflate-zopfli")]
117 Self::BufferedZopfliDeflater(_) => f.write_str("BufferedZopfliDeflater"),
118 #[cfg(feature = "_bzip2_any")]
119 Self::Bzip2(w) => f.write_fmt(format_args!("Bzip2({:?})", w.get_ref())),
120 #[cfg(feature = "zstd")]
121 Self::Zstd(w) => f.write_fmt(format_args!("Zstd({:?})", w.get_ref())),
122 #[cfg(feature = "xz")]
123 Self::Xz(w) => f.write_fmt(format_args!("Xz({:?})", w.inner())),
124 #[cfg(feature = "ppmd")]
125 Self::Ppmd(_) => f.write_fmt(format_args!("Ppmd8Encoder")),
126 }
127 }
128}
129
130pub(crate) mod zip_writer {
132 use crate::{
133 types::ZipFileData,
134 write::{GenericZipWriter, ZipWriterStats},
135 };
136 use core::fmt::{Debug, Formatter};
137 use indexmap::IndexMap;
138 use std::io::{Seek, Write};
139
140 pub struct ZipWriter<W: Write + Seek> {
174 pub(super) inner: GenericZipWriter<W>,
175 pub(super) files: IndexMap<Box<[u8]>, ZipFileData>,
176 pub(super) stats: ZipWriterStats,
177 pub(super) writing_to_file: bool,
178 pub(super) writing_raw: bool,
179 pub(super) comment: Box<[u8]>,
180 pub(super) zip64_extensible_data_sector: Option<Box<[u8]>>,
181 pub(super) flush_on_finish_file: bool,
182 pub(super) seek_possible: bool,
183 pub(crate) auto_large_file: bool,
184 }
185
186 impl<W: Write + Seek> Debug for ZipWriter<W> {
187 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
188 f.write_fmt(format_args!(
189 "ZipWriter {{files: {:?}, stats: {:?}, writing_to_file: {}, writing_raw: {}, comment: {:?}, flush_on_finish_file: {}}}",
190 self.files, self.stats, self.writing_to_file, self.writing_raw,
191 self.comment, self.flush_on_finish_file))
192 }
193 }
194
195 impl<W: Write + Seek> ZipWriter<W> {
196 pub fn get_ref(&self) -> Option<&W> {
198 match &self.inner {
199 GenericZipWriter::Closed => None,
200 GenericZipWriter::Storer(w) => Some(w.get_ref()),
201 #[cfg(feature = "deflate-flate2")]
202 GenericZipWriter::Deflater(w) => Some(w.get_ref().get_ref()),
203 #[cfg(feature = "deflate-zopfli")]
204 GenericZipWriter::ZopfliDeflater(w) => Some(w.get_ref().get_ref()),
205 #[cfg(feature = "deflate-zopfli")]
206 GenericZipWriter::BufferedZopfliDeflater(w) => {
207 Some(w.get_ref().get_ref().get_ref())
208 }
209 #[cfg(feature = "_bzip2_any")]
210 GenericZipWriter::Bzip2(w) => Some(w.get_ref().get_ref()),
211 #[cfg(feature = "zstd")]
212 GenericZipWriter::Zstd(w) => Some(w.get_ref().get_ref()),
213 #[cfg(feature = "xz")]
214 GenericZipWriter::Xz(w) => Some(w.inner().get_ref()),
215 #[cfg(feature = "ppmd")]
216 GenericZipWriter::Ppmd(w) => Some(w.get_ref().get_ref()),
217 }
218 }
219
220 pub unsafe fn get_mut(&mut self) -> Option<&mut W> {
227 unsafe {
228 match &mut self.inner {
229 GenericZipWriter::Closed => None,
230 GenericZipWriter::Storer(w) => Some(w.get_mut()),
231 #[cfg(feature = "deflate-flate2")]
232 GenericZipWriter::Deflater(w) => Some(w.get_mut().get_mut()),
233 #[cfg(feature = "deflate-zopfli")]
234 GenericZipWriter::ZopfliDeflater(w) => Some(w.get_mut().get_mut()),
235 #[cfg(feature = "deflate-zopfli")]
236 GenericZipWriter::BufferedZopfliDeflater(w) => {
237 Some(w.get_mut().get_mut().get_mut())
238 }
239 #[cfg(feature = "_bzip2_any")]
240 GenericZipWriter::Bzip2(w) => Some(w.get_mut().get_mut()),
241 #[cfg(feature = "zstd")]
242 GenericZipWriter::Zstd(w) => Some(w.get_mut().get_mut()),
243 #[cfg(feature = "xz")]
244 GenericZipWriter::Xz(w) => Some(w.inner_mut().get_mut()),
245 #[cfg(feature = "ppmd")]
246 GenericZipWriter::Ppmd(w) => Some(w.get_mut().get_mut()),
247 }
248 }
249 }
250 }
251}
252#[doc(inline)]
253pub use self::sealed::FileOptionExtension;
254use crate::CompressionMethod::Stored;
255use crate::result::ZipError::UnsupportedArchive;
256use crate::unstable::LittleEndianWriteExt;
257use crate::unstable::path_to_string;
258use crate::zipcrypto::{CHUNK_SIZE, EncryptWith, ZipCryptoKeys};
259pub use zip_writer::ZipWriter;
260
261#[derive(Default, Debug)]
262struct ZipWriterStats {
263 hasher: Hasher,
264 start: u64,
265 bytes_written: u64,
266}
267
268mod sealed {
269 use std::sync::Arc;
270
271 use super::ExtendedFileOptions;
272
273 pub trait Sealed {}
274 #[doc(hidden)]
276 pub trait FileOptionExtension: Default + Sealed {
277 fn extra_data(&self) -> Option<&Arc<Vec<u8>>>;
279 fn central_extra_data(&self) -> Option<&Arc<Vec<u8>>>;
281 fn file_comment(&self) -> Option<&str>;
283 fn take_file_comment(&mut self) -> Option<Box<str>>;
285 }
286 impl Sealed for () {}
287 impl FileOptionExtension for () {
288 fn extra_data(&self) -> Option<&Arc<Vec<u8>>> {
289 None
290 }
291 fn central_extra_data(&self) -> Option<&Arc<Vec<u8>>> {
292 None
293 }
294 fn file_comment(&self) -> Option<&str> {
295 None
296 }
297 fn take_file_comment(&mut self) -> Option<Box<str>> {
298 None
299 }
300 }
301 impl Sealed for ExtendedFileOptions {}
302
303 impl FileOptionExtension for ExtendedFileOptions {
304 fn extra_data(&self) -> Option<&Arc<Vec<u8>>> {
305 Some(&self.extra_data)
306 }
307 fn central_extra_data(&self) -> Option<&Arc<Vec<u8>>> {
308 Some(&self.central_extra_data)
309 }
310 fn file_comment(&self) -> Option<&str> {
311 self.file_comment.as_ref().map(Box::as_ref)
312 }
313 fn take_file_comment(&mut self) -> Option<Box<str>> {
314 self.file_comment.take()
315 }
316 }
317}
318
319pub type FullFileOptions<'k> = FileOptions<'k, ExtendedFileOptions>;
321#[cfg_attr(feature = "_arbitrary", derive(arbitrary::Arbitrary))]
323#[derive(Clone, Default, Eq, PartialEq)]
324pub struct ExtendedFileOptions {
325 extra_data: Arc<Vec<u8>>,
326 central_extra_data: Arc<Vec<u8>>,
327 file_comment: Option<Box<str>>,
328}
329
330impl ExtendedFileOptions {
331 pub fn add_extra_data<D: AsRef<[u8]>>(
355 &mut self,
356 header_id: u16,
357 data: D,
358 central_only: bool,
359 ) -> ZipResult<()> {
360 let data = data.as_ref();
361 let len = data.len() + 4;
362 let local_extra_data_len = self.extra_data.len();
363 let central_extra_data_len = self.central_extra_data.len();
364 let field = if central_only {
365 &mut self.central_extra_data
366 } else {
367 &mut self.extra_data
368 };
369 if local_extra_data_len + central_extra_data_len + len > u16::MAX as usize {
370 Err(invalid!("Extra data field would be longer than allowed"))
371 } else {
372 let vec = Arc::make_mut(field);
373 Self::add_extra_data_unchecked(vec, header_id, data)?;
374 Self::validate_extra_data(vec, true)?;
375 Ok(())
376 }
377 }
378
379 pub(crate) fn add_extra_data_unchecked(
391 vec: &mut Vec<u8>,
392 header_id: u16,
393 data: &[u8],
394 ) -> Result<(), ZipError> {
395 vec.reserve_exact(data.len() + size_of::<u16>() + size_of::<u16>());
396 vec.write_u16_le(header_id)?;
397 vec.write_u16_le(data.len() as u16)?;
398 vec.write_all(data)?;
399 Ok(())
400 }
401
402 fn validate_extra_data(data: &[u8], disallow_zip64: bool) -> ZipResult<()> {
403 let len = data.len() as u64;
404 if len == 0 {
405 return Ok(());
406 }
407 if len > u64::from(u16::MAX) {
408 return Err(ZipError::Io(io::Error::other(
409 "Extra-data field can't exceed u16::MAX bytes",
410 )));
411 }
412 let mut data = Cursor::new(data);
413 let mut pos = data.position();
414 while pos < len {
415 if len - data.position() < 4 {
416 return Err(ZipError::Io(io::Error::other(
417 "Extra-data field doesn't have room for ID and length",
418 )));
419 }
420 #[cfg(not(feature = "unreserved"))]
421 {
422 use crate::{
423 extra_fields::{EXTRA_FIELD_MAPPING, UsedExtraField},
424 unstable::LittleEndianReadExt,
425 };
426 let header_id = data.read_u16_le()?;
427 if let Err(()) = UsedExtraField::try_from(header_id)
429 && EXTRA_FIELD_MAPPING.contains(&header_id)
430 {
431 return Err(ZipError::Io(io::Error::other(format!(
432 "Extra data header ID {header_id:#06} (0x{header_id:x}) \
433 requires crate feature \"unreserved\"",
434 ))));
435 }
436 data.seek(SeekFrom::Current(-2))?;
437 }
438 parse_single_extra_field(&mut ZipFileData::default(), &mut data, pos, disallow_zip64)?;
439 pos = data.position();
440 }
441 Ok(())
442 }
443}
444
445impl Debug for ExtendedFileOptions {
446 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
447 f.write_fmt(format_args!("ExtendedFileOptions {{extra_data: vec!{:?}.into(), central_extra_data: vec!{:?}.into()}}",
448 self.extra_data, self.central_extra_data))
449 }
450}
451
452#[cfg(feature = "_arbitrary")]
453impl<'a> arbitrary::Arbitrary<'a> for FileOptions<'a, ExtendedFileOptions> {
454 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
455 let mut options = FullFileOptions {
456 compression_method: CompressionMethod::arbitrary(u)?,
457 compression_level: if bool::arbitrary(u)? {
458 Some(u.int_in_range(0..=24)?)
459 } else {
460 None
461 },
462 last_modified_time: DateTime::arbitrary(u)?,
463 permissions: Option::<u32>::arbitrary(u)?,
464 large_file: bool::arbitrary(u)?,
465 encrypt_with: Option::<EncryptWith<'_>>::arbitrary(u)?,
466 alignment: u16::arbitrary(u)?,
467 #[cfg(feature = "deflate-zopfli")]
468 zopfli_buffer_size: None,
469 ..Default::default()
470 };
471 #[cfg(feature = "deflate-zopfli")]
472 if options.compression_method == CompressionMethod::Deflated && bool::arbitrary(u)? {
473 options.zopfli_buffer_size =
474 Some(if bool::arbitrary(u)? { 2 } else { 3 } << u.int_in_range(8..=20)?);
475 }
476 u.arbitrary_loop(Some(0), Some(10), |u| {
477 options
478 .add_extra_data(
479 u.int_in_range(2..=u16::MAX)?,
480 Box::<[u8]>::arbitrary(u)?,
481 bool::arbitrary(u)?,
482 )
483 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
484 Ok(core::ops::ControlFlow::Continue(()))
485 })?;
486 ZipWriter::new(Cursor::new(Vec::new()))
487 .start_file("", options.clone())
488 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
489 Ok(options)
490 }
491}
492
493const DEFAULT_FILE_PERMISSIONS: u32 = 0o644; const DEFAULT_DIR_PERMISSIONS: u32 = 0o755; impl<T: FileOptionExtension> FileOptions<'_, T> {
497 pub(crate) fn normalize(&mut self) {
498 if !self.last_modified_time.is_valid() {
499 self.last_modified_time = FileOptions::<T>::default().last_modified_time;
500 }
501
502 *self.permissions.get_or_insert(DEFAULT_FILE_PERMISSIONS) |= ffi::S_IFREG;
503 }
504
505 pub const fn has_encryption(&self) -> bool {
507 #[cfg(feature = "aes-crypto")]
508 {
509 self.encrypt_with.is_some() || self.aes_mode.is_some()
510 }
511 #[cfg(not(feature = "aes-crypto"))]
512 {
513 self.encrypt_with.is_some()
514 }
515 }
516
517 #[must_use]
523 pub const fn compression_method(mut self, method: CompressionMethod) -> Self {
524 self.compression_method = method;
525 self
526 }
527
528 #[must_use]
532 pub const fn system(mut self, system: System) -> Self {
533 self.system = Some(system);
534 self
535 }
536
537 #[must_use]
548 pub const fn compression_level(mut self, level: Option<i64>) -> Self {
549 self.compression_level = level;
550 self
551 }
552
553 #[must_use]
558 pub const fn last_modified_time(mut self, mod_time: DateTime) -> Self {
559 self.last_modified_time = mod_time;
560 self
561 }
562
563 #[must_use]
573 pub const fn unix_permissions(mut self, mode: u32) -> Self {
574 self.permissions = Some(mode & 0o777);
575 self
576 }
577
578 #[must_use]
584 pub const fn large_file(mut self, large: bool) -> Self {
585 self.large_file = large;
586 self
587 }
588
589 pub(crate) fn with_deprecated_encryption(self, password: &[u8]) -> FileOptions<'static, T> {
590 FileOptions {
591 encrypt_with: Some(EncryptWith::ZipCrypto(
592 ZipCryptoKeys::derive(password),
593 PhantomData,
594 )),
595 ..self
596 }
597 }
598
599 #[cfg(feature = "aes-crypto")]
604 pub fn with_aes_encryption_and_salt(
605 self,
606 password: &[u8],
607 salt: crate::aes::AesSalt,
608 ) -> FileOptions<'_, T> {
609 FileOptions {
610 encrypt_with: Some(EncryptWith::Aes {
611 mode: salt.mode(),
612 password,
613 salt: Some(salt),
614 }),
615 ..self
616 }
617 }
618
619 #[cfg(feature = "aes-crypto")]
621 pub fn with_aes_encryption(self, mode: crate::AesMode, password: &str) -> FileOptions<'_, T> {
622 self.with_aes_encryption_bytes(mode, password.as_bytes())
623 }
624
625 #[cfg(feature = "aes-crypto")]
627 pub fn with_aes_encryption_bytes(
628 self,
629 mode: crate::AesMode,
630 password: &[u8],
631 ) -> FileOptions<'_, T> {
632 FileOptions {
633 encrypt_with: Some(EncryptWith::Aes {
634 mode,
635 password,
636 salt: None,
637 }),
638 ..self
639 }
640 }
641
642 #[must_use]
647 #[cfg(feature = "deflate-zopfli")]
648 pub const fn with_zopfli_buffer(mut self, size: Option<usize>) -> Self {
649 self.zopfli_buffer_size = size;
650 self
651 }
652
653 pub const fn get_compression_level(&self) -> Option<i64> {
655 self.compression_level
656 }
657 #[must_use]
659 pub const fn with_alignment(mut self, alignment: u16) -> Self {
660 self.alignment = alignment;
661 self
662 }
663}
664impl FileOptions<'_, ExtendedFileOptions> {
665 #[must_use]
667 pub fn with_file_comment<S: Into<Box<str>>>(mut self, comment: S) -> Self {
668 self.extended_options.file_comment = Some(comment.into());
669 self
670 }
671
672 pub fn add_extra_data<D: AsRef<[u8]>>(
674 &mut self,
675 header_id: u16,
676 data: D,
677 central_only: bool,
678 ) -> ZipResult<()> {
679 self.extended_options
680 .add_extra_data(header_id, data, central_only)
681 }
682
683 #[must_use]
685 pub fn clear_extra_data(mut self) -> Self {
686 if !self.extended_options.extra_data.is_empty() {
687 self.extended_options.extra_data = Arc::new(vec![]);
688 }
689 if !self.extended_options.central_extra_data.is_empty() {
690 self.extended_options.central_extra_data = Arc::new(vec![]);
691 }
692 self
693 }
694}
695impl FileOptions<'static, ()> {
696 pub const DEFAULT: Self = Self {
702 compression_method: CompressionMethod::DEFAULT,
703 compression_level: None,
704 last_modified_time: DateTime::DEFAULT,
705 large_file: false,
706 permissions: None,
707 encrypt_with: None,
708 extended_options: (),
709 alignment: 1,
710 #[cfg(feature = "deflate-zopfli")]
711 zopfli_buffer_size: Some(1 << 15),
712 #[cfg(feature = "aes-crypto")]
713 aes_mode: None,
714 system: None,
715 };
716}
717
718impl<'k> FileOptions<'k, ()> {
719 #[must_use]
721 pub fn into_full_options(self) -> FullFileOptions<'k> {
722 FileOptions {
723 compression_method: self.compression_method,
724 compression_level: self.compression_level,
725 last_modified_time: self.last_modified_time,
726 permissions: self.permissions,
727 large_file: self.large_file,
728 encrypt_with: self.encrypt_with,
729 extended_options: ExtendedFileOptions::default(),
730 alignment: self.alignment,
731 #[cfg(feature = "deflate-zopfli")]
732 zopfli_buffer_size: self.zopfli_buffer_size,
733 #[cfg(feature = "aes-crypto")]
734 aes_mode: self.aes_mode,
735 system: self.system,
736 }
737 }
738}
739
740impl<T: FileOptionExtension> Default for FileOptions<'_, T> {
741 fn default() -> Self {
743 Self {
744 compression_method: CompressionMethod::default(),
745 compression_level: None,
746 last_modified_time: DateTime::default_for_write(),
747 permissions: None,
748 large_file: false,
749 encrypt_with: None,
750 extended_options: T::default(),
751 alignment: 1,
752 #[cfg(feature = "deflate-zopfli")]
753 zopfli_buffer_size: Some(1 << 15),
754 #[cfg(feature = "aes-crypto")]
755 aes_mode: None,
756 system: None,
757 }
758 }
759}
760
761impl<W: Write + Seek> Write for ZipWriter<W> {
762 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
763 if !self.writing_to_file {
764 return Err(io::Error::other("No file has been started"));
765 }
766 if buf.is_empty() {
767 return Ok(0);
768 }
769 match self.inner.ref_mut() {
770 Some(ref mut w) => {
771 let write_result = w.write(buf);
772 if let Ok(count) = write_result {
773 self.stats.update(&buf[..count]);
774 if self.stats.bytes_written > spec::ZIP64_BYTES_THR {
776 let is_large_file = self
777 .files
778 .last()
779 .ok_or_else(|| std::io::Error::other("Cannot get last file"))?
780 .1
781 .large_file;
782 if !is_large_file {
783 return Err(if let Err(e) = self.abort_file() {
784 let abort_io_err: io::Error = e.into();
785 io::Error::new(
786 abort_io_err.kind(),
787 format!(
788 "Large file option has not been set and abort_file() failed: {abort_io_err}"
789 ),
790 )
791 } else {
792 io::Error::other("Large file option has not been set")
793 });
794 }
795 }
796 }
797 write_result
798 }
799 None => Err(io::Error::new(
800 io::ErrorKind::BrokenPipe,
801 "write(): ZipWriter was already closed",
802 )),
803 }
804 }
805
806 fn flush(&mut self) -> io::Result<()> {
807 match self.inner.ref_mut() {
808 Some(ref mut w) => w.flush(),
809 None => Err(io::Error::new(
810 io::ErrorKind::BrokenPipe,
811 "flush(): ZipWriter was already closed",
812 )),
813 }
814 }
815}
816
817impl ZipWriterStats {
818 fn update(&mut self, buf: &[u8]) {
819 self.hasher.update(buf);
820 self.bytes_written += buf.len() as u64;
821 }
822}
823
824impl<A: Read + Write + Seek> ZipWriter<A> {
825 pub fn new_append(readwriter: A) -> ZipResult<ZipWriter<A>> {
829 Self::new_append_with_config(Config::default(), readwriter)
830 }
831
832 pub fn new_append_with_config(config: Config, mut readwriter: A) -> ZipResult<ZipWriter<A>> {
836 readwriter.seek(SeekFrom::Start(0))?;
837 let shared = ZipArchive::get_metadata(config, &mut readwriter)?;
838 Ok(ZipWriter {
839 inner: GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(readwriter)),
840 files: shared.files,
841 stats: ZipWriterStats::default(),
842 writing_to_file: false,
843 comment: shared.comment,
844 zip64_extensible_data_sector: shared.zip64_extensible_data_sector,
845 writing_raw: true, flush_on_finish_file: false,
847 seek_possible: true,
848 auto_large_file: false,
849 })
850 }
851
852 pub fn set_flush_on_finish_file(&mut self, flush_on_finish_file: bool) {
866 self.flush_on_finish_file = flush_on_finish_file;
867 }
868}
869
870impl<A: Read + Write + Seek> ZipWriter<A> {
871 pub fn deep_copy_file(&mut self, src_name: &str, dest_name: &str) -> ZipResult<()> {
874 self.finish_file()?;
875 if src_name == dest_name || self.files.contains_key(dest_name.as_bytes()) {
876 return Err(invalid!("That file already exists"));
877 }
878 let write_position = self.inner.try_inner_mut()?.stream_position()?;
879 let src_index = self.index_by_name(src_name.as_bytes())?;
880 let src_data = &mut self.files[src_index];
881 let src_data_start = src_data.data_start(self.inner.try_inner_mut()?)?;
882 debug_assert!(src_data_start <= write_position);
883 let mut compressed_size = src_data.compressed_size;
884 if compressed_size > (write_position - src_data_start) {
885 compressed_size = write_position - src_data_start;
886 src_data.compressed_size = compressed_size;
887 }
888 let mut reader = BufReader::new(self.inner.try_inner_mut()?);
889 reader.seek(SeekFrom::Start(src_data_start))?;
890 let mut copy = vec![0; compressed_size as usize];
891 reader.take(compressed_size).read_exact(&mut copy)?;
892 self.inner
893 .try_inner_mut()?
894 .seek(SeekFrom::Start(write_position))?;
895 let mut new_data = src_data.clone();
896 new_data.file_name_raw = dest_name.as_bytes().into();
897 new_data.file_name = dest_name.into();
898 new_data.header_start = write_position;
899 let extra_data_start = write_position
900 + (size_of::<Magic>() + size_of::<ZipLocalEntryBlock>()) as u64
901 + new_data.file_name_raw.len() as u64;
902 new_data.extra_data_start = Some(extra_data_start);
903 if let Some(extra) = &src_data.extra_field {
904 let stripped = strip_alignment_extra_field(extra, false);
905 if stripped.is_empty() {
906 new_data.extra_field = None;
907 } else {
908 new_data.extra_field = Some(Arc::from(stripped.into_boxed_slice()));
909 }
910 }
911
912 let mut data_start = extra_data_start;
913 if let Some(extra) = &new_data.extra_field {
914 data_start += extra.len() as u64;
915 }
916 new_data.data_start.take();
917 new_data.data_start.get_or_init(|| data_start);
918 new_data.central_header_start = 0;
919 let block = new_data.local_block()?;
920 let index = self.insert_file_data(new_data)?;
921 let new_data = &self.files[index];
922 let result: io::Result<()> = {
923 let plain_writer = self.inner.try_inner_mut()?;
924 block.write(plain_writer)?;
925 plain_writer.write_all(&new_data.file_name_raw)?;
926 if let Some(data) = &new_data.extra_field {
927 plain_writer.write_all(data)?;
928 }
929 debug_assert_eq!(data_start, plain_writer.stream_position()?);
930 self.writing_to_file = true;
931 plain_writer.write_all(©)?;
932 if self.flush_on_finish_file {
933 plain_writer.flush()?;
934 }
935 Ok(())
936 };
937 self.ok_or_abort_file(result)?;
938 self.writing_to_file = false;
939 Ok(())
940 }
941
942 pub fn deep_copy_file_from_path<T: AsRef<Path>, U: AsRef<Path>>(
948 &mut self,
949 src_path: T,
950 dest_path: U,
951 ) -> ZipResult<()> {
952 let src = path_to_string(src_path)?;
953 let dest = path_to_string(dest_path)?;
954 self.deep_copy_file(&src, &dest)
955 }
956
957 pub fn finish_into_readable(mut self) -> ZipResult<ZipArchive<A>> {
985 let central_start = self.finalize()?;
986 let inner = self.close_writer()?;
987 let comment = mem::take(&mut self.comment);
988 let zip64_extensible_data_sector = mem::take(&mut self.zip64_extensible_data_sector);
989 let files = mem::take(&mut self.files);
990 Ok(ZipArchive::from_finalized_writer(
991 files,
992 comment,
993 zip64_extensible_data_sector,
994 inner,
995 central_start,
996 ))
997 }
998}
999
1000impl<W: Write + Seek> ZipWriter<W> {
1001 pub fn new(inner: W) -> ZipWriter<W> {
1007 ZipWriter {
1008 inner: GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(inner)),
1009 files: IndexMap::new(),
1010 stats: ZipWriterStats::default(),
1011 writing_to_file: false,
1012 writing_raw: false,
1013 comment: Box::new([]),
1014 zip64_extensible_data_sector: None,
1015 flush_on_finish_file: false,
1016 seek_possible: true,
1017 auto_large_file: false,
1018 }
1019 }
1020
1021 #[must_use]
1023 pub fn set_auto_large_file(mut self) -> Self {
1024 self.auto_large_file = true;
1025 self
1026 }
1027
1028 pub const fn is_writing_file(&self) -> bool {
1030 self.writing_to_file && !self.inner.is_closed()
1031 }
1032
1033 pub fn set_comment<S>(&mut self, comment: S) -> ZipResult<()>
1035 where
1036 S: Into<Box<str>>,
1037 {
1038 self.set_raw_comment(comment.into().into_boxed_bytes())
1039 }
1040
1041 pub fn set_raw_comment(&mut self, comment: Box<[u8]>) -> ZipResult<()> {
1047 let max_comment_len = u16::MAX as usize; if comment.len() <= max_comment_len {
1049 self.comment = comment;
1050 return Ok(());
1051 }
1052 let (allowed, rest) = comment.split_at(max_comment_len);
1053 self.comment = allowed.into();
1054 Err(ZipError::Io(std::io::Error::new(
1055 std::io::ErrorKind::InvalidData,
1056 format!("Unexpected data comment - {} bytes too long", rest.len()),
1057 )))
1058 }
1059
1060 pub fn get_comment(&mut self) -> Result<&str, Utf8Error> {
1062 from_utf8(self.get_raw_comment())
1063 }
1064
1065 pub const fn get_raw_comment(&self) -> &[u8] {
1070 &self.comment
1071 }
1072
1073 #[deprecated(
1075 note = "Zip64 comment is not part of the zip specification - see https://github.com/zip-rs/zip2/pull/747"
1076 )]
1077 pub fn set_zip64_comment<S>(&mut self, comment: Option<S>) -> ZipResult<()>
1078 where
1079 S: Into<Box<str>>,
1080 {
1081 if let Some(com) = comment {
1082 self.set_comment(com)?;
1083 }
1084 Ok(())
1085 }
1086
1087 #[deprecated(
1092 note = "Zip64 comment is not part of the zip specification - see https://github.com/zip-rs/zip2/pull/747"
1093 )]
1094 pub fn set_raw_zip64_comment(&mut self, comment: Option<Box<[u8]>>) -> ZipResult<()> {
1095 if let Some(com) = comment {
1096 self.set_raw_comment(com)?;
1097 }
1098 Ok(())
1099 }
1100
1101 pub fn set_raw_zip64_extensible_data_sector(&mut self, extensible_data: Box<[u8]>) {
1104 self.zip64_extensible_data_sector = Some(extensible_data);
1105 }
1106
1107 #[deprecated(
1109 note = "Zip64 comment is not part of the zip specification - see https://github.com/zip-rs/zip2/pull/747"
1110 )]
1111 pub fn get_zip64_comment(&mut self) -> Option<Result<&str, Utf8Error>> {
1112 None
1114 }
1115
1116 #[deprecated(
1121 note = "Zip64 comment is not part of the zip specification - see https://github.com/zip-rs/zip2/pull/747"
1122 )]
1123 pub fn get_raw_zip64_comment(&self) -> Option<&[u8]> {
1124 None
1126 }
1127
1128 pub unsafe fn set_file_metadata(&mut self, length: u64, crc32: u32) -> ZipResult<()> {
1150 if !self.writing_to_file {
1151 return Err(ZipError::Io(io::Error::other("No file has been started")));
1152 }
1153 self.stats.hasher = Hasher::new_with_initial_len(crc32, length);
1154 self.stats.bytes_written = length;
1155 Ok(())
1156 }
1157
1158 fn ok_or_abort_file<T, E: Into<ZipError>>(&mut self, result: Result<T, E>) -> ZipResult<T> {
1159 match result {
1160 Err(e) => {
1161 let _ = self.abort_file();
1162 Err(e.into())
1163 }
1164 Ok(t) => Ok(t),
1165 }
1166 }
1167
1168 fn start_entry<S: ToString, T: FileOptionExtension>(
1170 &mut self,
1171 name: &S,
1172 mut options: FileOptions<'_, T>,
1173 raw_values: Option<ZipRawValues>,
1174 ) -> ZipResult<()> {
1175 self.finish_file()?;
1176
1177 let header_start = self.inner.try_inner_mut()?.stream_position()?;
1178 let raw_values = raw_values.unwrap_or(ZipRawValues {
1179 crc32: 0,
1180 compressed_size: 0,
1181 uncompressed_size: 0,
1182 });
1183
1184 let mut extra_data = match options.extended_options.extra_data() {
1185 Some(data) => data.to_vec(),
1186 None => vec![],
1187 };
1188 let central_extra_data = options.extended_options.central_extra_data();
1189 if let Some(zip64_block) = Zip64ExtendedInformation::new_local(options.large_file) {
1190 let mut new_extra_data = zip64_block.serialize().into_vec();
1191 new_extra_data.append(&mut extra_data);
1192 extra_data = new_extra_data;
1193 }
1194
1195 let (compression_method, aes_mode) = match options.encrypt_with {
1198 #[cfg(feature = "aes-crypto")]
1200 None if options.aes_mode.is_some() => (CompressionMethod::Aes, options.aes_mode),
1201 #[cfg(feature = "aes-crypto")]
1202 Some(EncryptWith::Aes { mode, salt, .. }) => {
1203 let aes_mode = crate::aes::AesModeOptions::new(
1204 mode,
1205 AesVendorVersion::Ae2,
1206 options.compression_method,
1207 salt,
1208 );
1209 (CompressionMethod::Aes, Some(aes_mode))
1210 }
1211 _ => (options.compression_method, None),
1212 };
1213
1214 #[allow(unused_mut)]
1216 let mut aes_extra_data_start = 0;
1217 #[cfg(feature = "aes-crypto")]
1218 if let Some(crate::aes::AesModeOptions {
1219 mode,
1220 vendor_version,
1221 actual_compression_method,
1222 ..
1223 }) = aes_mode
1224 {
1225 let aex_extra_field =
1227 AexEncryption::new(vendor_version, mode, actual_compression_method);
1228 let buf = &aex_extra_field.as_bytes()[offset_of!(AexEncryption, version)..];
1229 aes_extra_data_start = extra_data.len() as u64;
1230 ExtendedFileOptions::add_extra_data_unchecked(
1231 &mut extra_data,
1232 UsedExtraField::AeXEncryption.as_u16(),
1233 buf,
1234 )?;
1235 }
1236 let header_end = header_start
1237 + (size_of::<Magic>() + size_of::<ZipLocalEntryBlock>()) as u64
1238 + name.to_string().len() as u64;
1239
1240 if options.alignment > 1 {
1241 let extra_data_end = header_end + extra_data.len() as u64;
1242 let align = u64::from(options.alignment);
1243 let unaligned_header_bytes = extra_data_end % align;
1244 if unaligned_header_bytes != 0 {
1245 let mut pad_length = (align - unaligned_header_bytes) as usize;
1246 while pad_length < 6 {
1247 pad_length += align as usize;
1248 }
1249 let new_len = extra_data.len() + pad_length;
1250 if new_len > u16::MAX as usize {
1251 } else {
1254 let mut pad_body = vec![0; pad_length - 4];
1256 debug_assert!(pad_body.len() >= 2);
1257 [pad_body[0], pad_body[1]] = options.alignment.to_le_bytes();
1258 ExtendedFileOptions::add_extra_data_unchecked(
1259 &mut extra_data,
1260 UsedExtraField::DataStreamAlignment.as_u16(),
1261 &pad_body,
1262 )?;
1263 debug_assert_eq!((extra_data.len() as u64 + header_end) % align, 0);
1264 }
1265 }
1266 }
1267 let extra_data_len = extra_data.len();
1268 if let Some(data) = central_extra_data {
1269 if extra_data_len + data.len() > u16::MAX as usize {
1270 return Err(invalid!(
1271 "Extra data and central extra data must be less than 64KiB when combined"
1272 ));
1273 }
1274 ExtendedFileOptions::validate_extra_data(data, true)?;
1275 }
1276 #[cfg(feature = "aes-crypto")]
1277 let aes_mode = aes_mode.map(super::aes::AesModeOptions::to_tuple);
1278 let mut file = ZipFileData::initialize_local_block(
1279 name,
1280 &options,
1281 &raw_values,
1282 header_start,
1283 None,
1284 aes_extra_data_start,
1285 compression_method,
1286 aes_mode,
1287 &extra_data,
1288 );
1289 if let Some(comment) = options.extended_options.take_file_comment() {
1290 if comment.len() > u16::MAX as usize {
1291 return Err(invalid!("File comment must be less than 64KiB"));
1292 }
1293 if !comment.is_ascii() {
1294 file.is_utf8 = true;
1295 }
1296 file.file_comment = comment;
1297 }
1298 file.using_data_descriptor =
1299 !self.seek_possible || matches!(options.encrypt_with, Some(EncryptWith::ZipCrypto(..)));
1300 file.version_made_by = file.version_made_by.max(file.version_needed() as u8);
1301 file.extra_data_start = Some(header_end);
1302 let index = self.insert_file_data(file)?;
1303 self.writing_to_file = true;
1304 let result: ZipResult<()> = {
1305 ExtendedFileOptions::validate_extra_data(&extra_data, false)?;
1306 let file = &mut self.files[index];
1307 let block = file.local_block()?;
1308 let writer = self.inner.try_inner_mut()?;
1309 block.write(writer)?;
1310 writer.write_all(&file.file_name_raw)?;
1312 if extra_data_len > 0 {
1313 writer.write_all(&extra_data)?;
1314 file.extra_field = Some(Arc::from(extra_data.into_boxed_slice()));
1315 }
1316 Ok(())
1317 };
1318 self.ok_or_abort_file(result)?;
1319 self.stats.start = self.inner.try_inner_mut()?.stream_position()?;
1320 match options.encrypt_with {
1321 #[cfg(feature = "aes-crypto")]
1322 Some(EncryptWith::Aes {
1323 mode,
1324 password,
1325 salt,
1326 }) => {
1327 let writer = self.close_writer()?;
1328 let aeswriter =
1329 crate::aes::AesWriter::new_with_options(writer, mode, password, salt)?;
1330 self.inner = GenericZipWriter::Storer(MaybeEncrypted::Aes(aeswriter));
1331 }
1332 Some(EncryptWith::ZipCrypto(keys, ..)) => {
1333 let writer = self.close_writer()?;
1334 let file = &mut self.files[index];
1335 let mut zipwriter = crate::zipcrypto::ZipCryptoWriter {
1338 writer,
1339 keys,
1340 buffer: [0u8; CHUNK_SIZE],
1341 };
1342 self.stats.start = zipwriter.writer.stream_position()?;
1343 let mut crypto_header = [0u8; 12];
1345 crypto_header[10..=11].copy_from_slice(
1360 &file
1361 .last_modified_time
1362 .unwrap_or_else(DateTime::default_for_write)
1363 .timepart()
1364 .to_le_bytes(),
1365 );
1366 let result = zipwriter.write_all(&crypto_header);
1367 self.ok_or_abort_file(result)?;
1368 self.inner = GenericZipWriter::Storer(MaybeEncrypted::ZipCrypto(zipwriter));
1369 }
1370 None => {}
1371 }
1372 let file = &mut self.files[index];
1373 debug_assert!(file.data_start.get().is_none());
1374 file.data_start.get_or_init(|| self.stats.start);
1375 self.stats.bytes_written = 0;
1376 self.stats.hasher = Hasher::new();
1377 Ok(())
1378 }
1379
1380 fn insert_file_data(&mut self, file: ZipFileData) -> ZipResult<usize> {
1381 if self.files.contains_key(&file.file_name_raw) {
1382 return Err(invalid!("Duplicate filename: {}", file.file_name));
1383 }
1384 let (index, _) = self.files.insert_full(file.file_name_raw.clone(), file);
1385 Ok(index)
1386 }
1387
1388 fn finish_file(&mut self) -> ZipResult<()> {
1389 if !self.writing_to_file {
1390 return Ok(());
1391 }
1392
1393 let make_plain_writer = self.inner.prepare_next_writer(
1394 Stored,
1395 None,
1396 #[cfg(feature = "deflate-zopfli")]
1397 None,
1398 )?;
1399 self.inner.switch_to(make_plain_writer)?;
1400 self.switch_to_non_encrypting_writer()?;
1401 let writer = self.inner.try_inner_mut()?;
1402
1403 if !self.writing_raw {
1404 let file = match self.files.last_mut() {
1405 None => return Ok(()),
1406 Some((_, f)) => f,
1407 };
1408 file.uncompressed_size = self.stats.bytes_written;
1409
1410 let file_end = writer.stream_position()?;
1411 debug_assert!(file_end >= self.stats.start);
1412 file.compressed_size = file_end - self.stats.start;
1413
1414 if !file.using_data_descriptor {
1415 update_aes_extra_data(writer, file, self.stats.bytes_written)?;
1419 }
1420
1421 let crc = !matches!(file.aes_mode, Some((_, AesVendorVersion::Ae2, _)));
1422
1423 file.crc32 = if crc {
1424 self.stats.hasher.clone().finalize()
1425 } else {
1426 0
1427 };
1428
1429 if file.using_data_descriptor {
1430 file.write_data_descriptor(writer, self.auto_large_file)?;
1431 } else {
1432 file.update_local_file_header(writer)?;
1433 writer.seek(SeekFrom::Start(file_end))?;
1434 }
1435 }
1436 if self.flush_on_finish_file {
1437 let result = writer.flush();
1438 self.ok_or_abort_file(result)?;
1439 }
1440
1441 self.writing_to_file = false;
1442 Ok(())
1443 }
1444
1445 fn switch_to_non_encrypting_writer(&mut self) -> Result<(), ZipError> {
1446 self.inner = GenericZipWriter::Storer(
1447 match mem::replace(&mut self.inner, GenericZipWriter::Closed) {
1448 #[cfg(feature = "aes-crypto")]
1449 GenericZipWriter::Storer(MaybeEncrypted::Aes(writer)) => {
1450 MaybeEncrypted::Unencrypted(writer.finish()?)
1451 }
1452 GenericZipWriter::Storer(MaybeEncrypted::ZipCrypto(writer)) => {
1453 MaybeEncrypted::Unencrypted(writer.finish()?)
1454 }
1455 GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(w)) => {
1456 MaybeEncrypted::Unencrypted(w)
1457 }
1458 _ => unreachable!(
1459 "switch_to_non_encrypting_writer called with incompatible GenericZipWriter variant"
1460 ),
1461 },
1462 );
1463 Ok(())
1464 }
1465
1466 pub fn abort_file(&mut self) -> ZipResult<()> {
1469 let (_, last_file) = self.files.pop().ok_or(ZipError::FileNotFound)?;
1470 let make_plain_writer = self.inner.prepare_next_writer(
1471 Stored,
1472 None,
1473 #[cfg(feature = "deflate-zopfli")]
1474 None,
1475 )?;
1476 self.inner.switch_to(make_plain_writer)?;
1477 self.switch_to_non_encrypting_writer()?;
1478 let rewind_safe: bool = match last_file.data_start.get() {
1481 None => self.files.is_empty(),
1482 Some(last_file_start) => self.files.values().all(|file| {
1483 file.data_start
1484 .get()
1485 .is_some_and(|start| start < last_file_start)
1486 }),
1487 };
1488 if rewind_safe {
1489 self.inner
1490 .try_inner_mut()?
1491 .seek(SeekFrom::Start(last_file.header_start))?;
1492 }
1493 self.writing_to_file = false;
1494 Ok(())
1495 }
1496
1497 pub fn start_file<S: ToString, T: FileOptionExtension>(
1502 &mut self,
1503 name: S,
1504 mut options: FileOptions<'_, T>,
1505 ) -> ZipResult<()> {
1506 options.normalize();
1507 let make_new_self = self.inner.prepare_next_writer(
1508 options.compression_method,
1509 options.compression_level,
1510 #[cfg(feature = "deflate-zopfli")]
1511 options.zopfli_buffer_size,
1512 )?;
1513 self.start_entry(&name, options, None)?;
1514 let result = self.inner.switch_to(make_new_self);
1515 self.ok_or_abort_file(result)?;
1516 self.writing_raw = false;
1517 Ok(())
1518 }
1519
1520 pub fn merge_archive<R>(&mut self, mut source: ZipArchive<R>) -> ZipResult<()>
1564 where
1565 R: Read + Seek,
1566 {
1567 self.finish_file()?;
1568
1569 self.writing_to_file = true;
1572 self.writing_raw = true;
1573
1574 let writer = self.inner.try_inner_mut()?;
1575 let new_files = source.merge_contents(writer)?;
1577
1578 self.files.extend(new_files);
1580
1581 Ok(())
1582 }
1583
1584 pub fn start_file_from_path<E: FileOptionExtension, P: AsRef<Path>>(
1590 &mut self,
1591 path: P,
1592 options: FileOptions<'_, E>,
1593 ) -> ZipResult<()> {
1594 self.start_file(path_to_string(path)?, options)
1595 }
1596
1597 pub fn raw_copy_file_rename<R: Read, S: ToString>(
1624 &mut self,
1625 file: ZipFile<'_, R>,
1626 name: S,
1627 ) -> ZipResult<()> {
1628 let mut options = file.options().into_full_options();
1629 if !file.comment().is_empty() {
1630 options = options.with_file_comment(file.comment());
1631 }
1632 self.raw_copy_file_rename_internal(file, &name, options)
1633 }
1634
1635 fn raw_copy_file_rename_internal<R: Read, S: ToString, T: FileOptionExtension>(
1636 &mut self,
1637 mut file: ZipFile<'_, R>,
1638 name: &S,
1639 options: FileOptions<'_, T>,
1640 ) -> ZipResult<()> {
1641 let raw_values = ZipRawValues {
1642 crc32: file.crc32(),
1643 compressed_size: file.compressed_size(),
1644 uncompressed_size: file.size(),
1645 };
1646
1647 self.start_entry(name, options, Some(raw_values))?;
1648 self.writing_to_file = true;
1649 self.writing_raw = true;
1650
1651 io::copy(&mut file.take_raw_reader()?, self)?;
1652 self.finish_file()
1653 }
1654
1655 pub fn raw_copy_file_to_path<R: Read, P: AsRef<Path>>(
1661 &mut self,
1662 file: ZipFile<'_, R>,
1663 path: P,
1664 ) -> ZipResult<()> {
1665 self.raw_copy_file_rename(file, path_to_string(path)?)
1666 }
1667
1668 pub fn raw_copy_file<R: Read>(&mut self, file: ZipFile<'_, R>) -> ZipResult<()> {
1692 let name = file.name().to_owned();
1693 self.raw_copy_file_rename(file, name)
1694 }
1695
1696 pub fn raw_copy_file_touch<R: Read>(
1720 &mut self,
1721 file: ZipFile<'_, R>,
1722 last_modified_time: DateTime,
1723 unix_mode: Option<u32>,
1724 ) -> ZipResult<()> {
1725 let name = file.name().to_owned();
1726
1727 let mut options = file.options().into_full_options();
1728
1729 options = options.last_modified_time(last_modified_time);
1730
1731 if let Some(perms) = unix_mode {
1732 options = options.unix_permissions(perms);
1733 }
1734
1735 if !file.comment().is_empty() {
1736 options = options.with_file_comment(file.comment());
1737 }
1738
1739 options.normalize();
1740
1741 self.raw_copy_file_rename_internal(file, &name, options)
1742 }
1743
1744 pub fn add_directory<S, T: FileOptionExtension>(
1748 &mut self,
1749 name: S,
1750 mut options: FileOptions<'_, T>,
1751 ) -> ZipResult<()>
1752 where
1753 S: Into<String>,
1754 {
1755 if options.permissions.is_none() {
1756 options.permissions = Some(DEFAULT_DIR_PERMISSIONS);
1757 }
1758 *options
1759 .permissions
1760 .as_mut()
1761 .ok_or_else(|| std::io::Error::other("Cannot get permissions as mutable"))? |= 0o40000;
1762 options.compression_method = Stored;
1763 options.encrypt_with = None;
1764
1765 let name_as_string = name.into();
1766 let name_with_slash = match name_as_string.chars().last() {
1768 Some('/' | '\\') => name_as_string,
1769 _ => name_as_string + "/",
1770 };
1771
1772 self.start_entry(&name_with_slash, options, None)?;
1773 self.writing_to_file = false;
1774 self.switch_to_non_encrypting_writer()?;
1775 Ok(())
1776 }
1777
1778 pub fn add_directory_from_path<T: FileOptionExtension, P: AsRef<Path>>(
1784 &mut self,
1785 path: P,
1786 options: FileOptions<'_, T>,
1787 ) -> ZipResult<()> {
1788 self.add_directory(path_to_string(path)?, options)
1789 }
1790
1791 pub fn finish(mut self) -> ZipResult<W> {
1796 let _central_start = self.finalize()?;
1797 self.close_writer()
1798 }
1799
1800 fn close_writer(&mut self) -> ZipResult<W> {
1801 let inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
1802 match inner {
1803 GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(w)) => Ok(w),
1804 _ => Err(invalid!(
1805 "Should have switched to stored and unencrypted beforehand",
1806 )),
1807 }
1808 }
1809
1810 pub fn add_symlink<N: ToString, T: ToString, E: FileOptionExtension>(
1823 &mut self,
1824 name: N,
1825 target: T,
1826 mut options: FileOptions<'_, E>,
1827 ) -> ZipResult<()> {
1828 if options.permissions.is_none() {
1829 options.permissions = Some(0o777);
1830 }
1831 *options
1832 .permissions
1833 .as_mut()
1834 .ok_or_else(|| std::io::Error::other("Cannot get permissions as mutable"))? |=
1835 ffi::S_IFLNK;
1836 options.compression_method = Stored;
1839
1840 self.start_entry(&name, options, None)?;
1841 self.writing_to_file = true;
1842 let result = self.write_all(target.to_string().as_bytes());
1843 self.ok_or_abort_file(result)?;
1844 self.writing_raw = false;
1845 self.finish_file()?;
1846
1847 Ok(())
1848 }
1849
1850 pub fn add_symlink_from_path<P: AsRef<Path>, T: AsRef<Path>, E: FileOptionExtension>(
1856 &mut self,
1857 path: P,
1858 target: T,
1859 options: FileOptions<'_, E>,
1860 ) -> ZipResult<()> {
1861 let path = path_to_string(path)?;
1862 let target = path_to_string(target)?;
1863 self.add_symlink(path, target, options)
1864 }
1865
1866 fn finalize(&mut self) -> ZipResult<u64> {
1867 self.finish_file()?;
1868
1869 let mut central_start = self.write_central_and_footer()?;
1870 let writer = self.inner.try_inner_mut()?;
1871 let footer_end = writer.stream_position()?;
1872 let archive_end = writer.seek(SeekFrom::End(0))?;
1873 if footer_end < archive_end {
1874 writer.seek(SeekFrom::Start(central_start))?;
1878 writer.write_u32_le(0)?;
1879 writer.seek(SeekFrom::Start(
1880 footer_end
1881 - (size_of::<Magic>() + size_of::<Zip32CDEBlock>()) as u64
1882 - self.comment.len() as u64,
1883 ))?;
1884 writer.write_u32_le(0)?;
1885
1886 let central_and_footer_size = footer_end - central_start;
1888 writer.seek(SeekFrom::End(-(central_and_footer_size as i64)))?;
1889 central_start = self.write_central_and_footer()?;
1890 debug_assert!(self.inner.try_inner_mut()?.stream_position()? == archive_end);
1891 }
1892
1893 Ok(central_start)
1894 }
1895
1896 fn write_central_and_footer(&mut self) -> Result<u64, ZipError> {
1897 let writer = self.inner.try_inner_mut()?;
1898
1899 let mut version_needed = u16::from(MIN_VERSION);
1900 let central_start = writer.stream_position()?;
1901 for file in self.files.values() {
1902 file.write_central_directory_header(writer)?;
1903 version_needed = version_needed.max(file.version_needed());
1904 }
1905 let central_size = writer.stream_position()? - central_start;
1906 let is64 = self.files.len() > spec::ZIP64_ENTRY_THR
1907 || central_size.max(central_start) > spec::ZIP64_BYTES_THR
1908 || self.zip64_extensible_data_sector.is_some();
1909
1910 if is64 {
1911 let zip64_extensible_data_sector = self.zip64_extensible_data_sector.clone();
1912 let extensible_len = zip64_extensible_data_sector
1913 .as_ref()
1914 .map(|e| e.len() as u64)
1915 .unwrap_or(0);
1916
1917 let zip64_footer = spec::Zip64CentralDirectoryEnd {
1918 record_size: extensible_len + 44,
1919 version_made_by: version_needed,
1920 version_needed_to_extract: version_needed,
1921 disk_number: 0,
1922 disk_with_central_directory: 0,
1923 number_of_files_on_this_disk: self.files.len() as u64,
1924 number_of_files: self.files.len() as u64,
1925 central_directory_size: central_size,
1926 central_directory_offset: central_start,
1927 zip64_extensible_data_sector,
1928 };
1929
1930 zip64_footer.write(writer)?;
1931
1932 let zip64_footer = spec::Zip64CentralDirectoryEndLocator {
1933 disk_with_central_directory: 0,
1934 end_of_central_directory_offset: central_start + central_size,
1935 number_of_disks: 1,
1936 };
1937
1938 zip64_footer.write(writer)?;
1939 }
1940
1941 let central_directory_size = if is64 {
1942 spec::ZIP64_BYTES_THR as u32
1943 } else {
1944 central_size.min(spec::ZIP64_BYTES_THR) as u32
1945 };
1946
1947 let number_of_files = self.files.len().min(spec::ZIP64_ENTRY_THR) as u16;
1948 let footer = spec::Zip32CentralDirectoryEnd {
1949 disk_number: 0,
1950 disk_with_central_directory: 0,
1951 zip_file_comment: self.comment.clone(),
1952 number_of_files_on_this_disk: number_of_files,
1953 number_of_files,
1954 central_directory_size,
1955 central_directory_offset: central_start.min(spec::ZIP64_BYTES_THR) as u32,
1956 };
1957
1958 footer.write(writer)?;
1959 Ok(central_start)
1960 }
1961
1962 fn index_by_name(&self, name: &[u8]) -> ZipResult<usize> {
1963 self.files.get_index_of(name).ok_or(ZipError::FileNotFound)
1964 }
1965
1966 pub fn shallow_copy_file(&mut self, src_name: &str, dest_name: &str) -> ZipResult<()> {
1972 self.finish_file()?;
1973 if src_name == dest_name {
1974 return Err(invalid!("Trying to copy a file to itself"));
1975 }
1976 let src_index = self.index_by_name(src_name.as_bytes())?;
1977 let mut dest_data = self.files[src_index].clone();
1978 dest_data.file_name = dest_name.into();
1979 dest_data.file_name_raw = dest_name.as_bytes().into();
1980 dest_data.central_header_start = 0;
1981 self.insert_file_data(dest_data)?;
1982
1983 Ok(())
1984 }
1985
1986 pub fn shallow_copy_file_from_path<T: AsRef<Path>, U: AsRef<Path>>(
1992 &mut self,
1993 src_path: T,
1994 dest_path: U,
1995 ) -> ZipResult<()> {
1996 let src = path_to_string(src_path)?;
1997 let dest = path_to_string(dest_path)?;
1998 self.shallow_copy_file(&src, &dest)
1999 }
2000}
2001
2002impl<W: Write> ZipWriter<StreamWriter<W>> {
2003 pub fn new_stream(inner: W) -> ZipWriter<StreamWriter<W>> {
2007 ZipWriter {
2008 inner: GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(StreamWriter::new(inner))),
2009 files: IndexMap::new(),
2010 stats: ZipWriterStats::default(),
2011 writing_to_file: false,
2012 writing_raw: false,
2013 comment: Box::new([]),
2014 zip64_extensible_data_sector: None,
2015 flush_on_finish_file: false,
2016 seek_possible: false,
2017 auto_large_file: false,
2018 }
2019 }
2020}
2021
2022impl<W: Write + Seek> Drop for ZipWriter<W> {
2023 fn drop(&mut self) {
2024 if !self.inner.is_closed()
2025 && let Err(e) = self.finalize()
2026 {
2027 let _ = write!(
2028 io::stderr(),
2029 "ZipWriter::drop: failed to finalize archive: {e:?}"
2030 );
2031 }
2032 }
2033}
2034
2035type SwitchWriterFunction<W> = Box<dyn FnOnce(MaybeEncrypted<W>) -> ZipResult<GenericZipWriter<W>>>;
2036
2037impl<W: Write + Seek> GenericZipWriter<W> {
2038 fn prepare_next_writer(
2039 &self,
2040 compression: CompressionMethod,
2041 compression_level: Option<i64>,
2042 #[cfg(feature = "deflate-zopfli")] zopfli_buffer_size: Option<usize>,
2043 ) -> ZipResult<SwitchWriterFunction<W>> {
2044 if let GenericZipWriter::Closed = self {
2045 return Err(
2046 io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed").into(),
2047 );
2048 }
2049
2050 match compression {
2051 Stored => {
2052 if compression_level.is_some() {
2053 Err(UnsupportedArchive("Unsupported compression level"))
2054 } else {
2055 Ok(Box::new(|bare| Ok(GenericZipWriter::Storer(bare))))
2056 }
2057 }
2058 #[allow(unreachable_code)]
2059 #[cfg(feature = "_deflate-any")]
2060 CompressionMethod::Deflated => {
2061 let default = {
2062 #[cfg(feature = "deflate-flate2")]
2063 {
2064 i64::from(flate2::Compression::default().level())
2065 }
2066 #[cfg(all(not(feature = "deflate-flate2"), feature = "deflate-zopfli"))]
2067 {
2068 24
2069 }
2070 #[cfg(not(any(feature = "deflate-zopfli", feature = "deflate-flate2")))]
2071 {
2072 compile_error!("could not calculate default: unknown deflate variant")
2073 }
2074 };
2075
2076 let level = validate_value_in_range(
2077 compression_level.unwrap_or(default),
2078 deflate_compression_level_range(),
2079 )
2080 .ok_or(UnsupportedArchive("Unsupported compression level"))?;
2081
2082 #[cfg(feature = "deflate-zopfli")]
2083 {
2084 macro_rules! deflate_zopfli_and_return {
2085 ($bare:expr, $best_non_zopfli:expr) => {{
2086 let options = zopfli::Options {
2087 iteration_count: core::num::NonZeroU64::try_from(u64::from(
2088 level - $best_non_zopfli,
2089 ))
2090 .map_err(|e| {
2091 std::io::Error::other(format!(
2092 "Failed to get the iteration count for zopfli : {e}"
2093 ))
2094 })?,
2095 ..Default::default()
2096 };
2097 Ok(Box::new(move |bare| {
2098 Ok(match zopfli_buffer_size {
2099 Some(size) => GenericZipWriter::BufferedZopfliDeflater(
2100 std::io::BufWriter::with_capacity(
2101 size,
2102 zopfli::DeflateEncoder::new(
2103 options,
2104 Default::default(),
2105 bare,
2106 ),
2107 ),
2108 ),
2109 None => GenericZipWriter::ZopfliDeflater(
2110 zopfli::DeflateEncoder::new(
2111 options,
2112 Default::default(),
2113 bare,
2114 ),
2115 ),
2116 })
2117 }))
2118 }};
2119 }
2120
2121 #[cfg(feature = "deflate-flate2")]
2122 {
2123 let best_non_zopfli = flate2::Compression::best().level();
2124 if level > best_non_zopfli {
2125 return deflate_zopfli_and_return!(bare, best_non_zopfli);
2126 }
2127 }
2128 #[cfg(not(feature = "deflate-flate2"))]
2129 {
2130 let best_non_zopfli = 9;
2131 return deflate_zopfli_and_return!(bare, best_non_zopfli);
2132 }
2133 }
2134 #[cfg(feature = "deflate-flate2")]
2135 {
2136 Ok(Box::new(move |bare| {
2137 Ok(GenericZipWriter::Deflater(
2138 flate2::write::DeflateEncoder::new(
2139 bare,
2140 flate2::Compression::new(level),
2141 ),
2142 ))
2143 }))
2144 }
2145 #[cfg(not(feature = "deflate-flate2"))]
2146 {
2147 unreachable!("deflate writer: have no fallback for this case")
2148 }
2149 }
2150 #[cfg(feature = "deflate64")]
2151 CompressionMethod::Deflate64 => {
2152 Err(UnsupportedArchive("Compressing Deflate64 is not supported"))
2153 }
2154 #[cfg(feature = "_bzip2_any")]
2155 CompressionMethod::Bzip2 => {
2156 let level: u32 = validate_value_in_range(
2157 compression_level.unwrap_or(i64::from(bzip2::Compression::default().level())),
2158 bzip2_compression_level_range(),
2159 )
2160 .ok_or(UnsupportedArchive("Unsupported compression level"))?;
2161 Ok(Box::new(move |bare| {
2162 Ok(GenericZipWriter::Bzip2(bzip2::write::BzEncoder::new(
2163 bare,
2164 bzip2::Compression::new(level),
2165 )))
2166 }))
2167 }
2168 CompressionMethod::AES => Err(UnsupportedArchive(
2169 "AES encryption is enabled through FileOptions::with_aes_encryption",
2170 )),
2171 #[cfg(feature = "zstd")]
2172 CompressionMethod::Zstd => {
2173 let level = validate_value_in_range(
2174 compression_level.unwrap_or(i64::from(zstd::DEFAULT_COMPRESSION_LEVEL)),
2175 zstd::compression_level_range(),
2176 )
2177 .ok_or(UnsupportedArchive("Unsupported compression level"))?;
2178 Ok(Box::new(move |bare| {
2179 Ok(GenericZipWriter::Zstd(
2180 zstd::stream::write::Encoder::new(bare, level as i32)
2181 .map_err(ZipError::Io)?,
2182 ))
2183 }))
2184 }
2185 #[cfg(feature = "legacy-zip")]
2186 CompressionMethod::Shrink => Err(ZipError::UnsupportedArchive(
2187 "Shrink compression unsupported",
2188 )),
2189 #[cfg(feature = "legacy-zip")]
2190 CompressionMethod::Reduce(_) => Err(ZipError::UnsupportedArchive(
2191 "Reduce compression unsupported",
2192 )),
2193 #[cfg(feature = "legacy-zip")]
2194 CompressionMethod::Implode => Err(ZipError::UnsupportedArchive(
2195 "Implode compression unsupported",
2196 )),
2197 #[cfg(feature = "lzma")]
2198 CompressionMethod::Lzma => {
2199 Err(UnsupportedArchive("LZMA isn't supported for compression"))
2200 }
2201 #[cfg(feature = "xz")]
2202 CompressionMethod::Xz => {
2203 let level = validate_value_in_range(compression_level.unwrap_or(6), 0_u32..=9_u32)
2204 .ok_or(UnsupportedArchive("Unsupported compression level"))?;
2205 Ok(Box::new(move |bare| {
2206 Ok(GenericZipWriter::Xz(Box::new(
2207 lzma_rust2::XzWriter::new(bare, lzma_rust2::XzOptions::with_preset(level))
2208 .map_err(ZipError::Io)?,
2209 )))
2210 }))
2211 }
2212 #[cfg(feature = "ppmd")]
2213 CompressionMethod::Ppmd => {
2214 const ORDERS: [u32; 10] = [0, 4, 5, 6, 7, 8, 9, 10, 11, 12];
2215
2216 let level = validate_value_in_range(compression_level.unwrap_or(7), 1_u32..=9_u32)
2217 .ok_or(UnsupportedArchive("Unsupported compression level"))?;
2218
2219 let order = ORDERS[level as usize];
2220 let memory_size = 1 << (level + 19);
2221 let memory_size_mb = memory_size / 1024 / 1024;
2222
2223 Ok(Box::new(move |mut bare| {
2224 let parameter: u16 = (order as u16 - 1)
2225 + ((memory_size_mb - 1) << 4) as u16
2226 + ((ppmd_rust::RestoreMethod::Restart as u16) << 12);
2227
2228 bare.write_all(¶meter.to_le_bytes())
2229 .map_err(ZipError::Io)?;
2230
2231 let encoder = ppmd_rust::Ppmd8Encoder::new(
2232 bare,
2233 order,
2234 memory_size,
2235 ppmd_rust::RestoreMethod::Restart,
2236 )
2237 .map_err(|error| match error {
2238 ppmd_rust::Error::RangeDecoderInitialization => ZipError::InvalidArchive(
2239 "PPMd range coder initialization failed".into(),
2240 ),
2241 ppmd_rust::Error::InvalidParameter => {
2242 ZipError::InvalidArchive("Invalid PPMd parameter".into())
2243 }
2244 ppmd_rust::Error::IoError(io_error) => ZipError::Io(io_error),
2245 ppmd_rust::Error::MemoryAllocation => ZipError::Io(io::Error::new(
2246 ErrorKind::OutOfMemory,
2247 "PPMd could not allocate memory",
2248 )),
2249 })?;
2250
2251 Ok(GenericZipWriter::Ppmd(Box::new(encoder)))
2252 }))
2253 }
2254 #[allow(deprecated)]
2255 CompressionMethod::Unsupported(..) => {
2256 Err(UnsupportedArchive("Unsupported compression"))
2257 }
2258 }
2259 }
2260
2261 fn switch_to(&mut self, make_new_self: SwitchWriterFunction<W>) -> ZipResult<()> {
2262 let bare = match mem::replace(self, GenericZipWriter::Closed) {
2263 GenericZipWriter::Storer(w) => w,
2264 #[cfg(feature = "deflate-flate2")]
2265 GenericZipWriter::Deflater(w) => w.finish()?,
2266 #[cfg(feature = "deflate-zopfli")]
2267 GenericZipWriter::ZopfliDeflater(w) => w.finish()?,
2268 #[cfg(feature = "deflate-zopfli")]
2269 GenericZipWriter::BufferedZopfliDeflater(w) => w
2270 .into_inner()
2271 .map_err(|e| ZipError::Io(e.into_error()))?
2272 .finish()?,
2273 #[cfg(feature = "_bzip2_any")]
2274 GenericZipWriter::Bzip2(w) => w.finish()?,
2275 #[cfg(feature = "zstd")]
2276 GenericZipWriter::Zstd(w) => w.finish()?,
2277 #[cfg(feature = "xz")]
2278 GenericZipWriter::Xz(w) => w.finish()?,
2279 #[cfg(feature = "ppmd")]
2280 GenericZipWriter::Ppmd(w) => {
2281 w.finish(true)?
2283 }
2284 GenericZipWriter::Closed => {
2285 return Err(io::Error::new(
2286 io::ErrorKind::BrokenPipe,
2287 "ZipWriter was already closed",
2288 )
2289 .into());
2290 }
2291 };
2292 *self = make_new_self(bare)?;
2293 Ok(())
2294 }
2295
2296 fn ref_mut(&mut self) -> Option<&mut dyn Write> {
2297 match self {
2298 GenericZipWriter::Storer(w) => Some(w as &mut dyn Write),
2299 #[cfg(feature = "deflate-flate2")]
2300 GenericZipWriter::Deflater(w) => Some(w as &mut dyn Write),
2301 #[cfg(feature = "deflate-zopfli")]
2302 GenericZipWriter::ZopfliDeflater(w) => Some(w as &mut dyn Write),
2303 #[cfg(feature = "deflate-zopfli")]
2304 GenericZipWriter::BufferedZopfliDeflater(w) => Some(w as &mut dyn Write),
2305 #[cfg(feature = "_bzip2_any")]
2306 GenericZipWriter::Bzip2(w) => Some(w as &mut dyn Write),
2307 #[cfg(feature = "zstd")]
2308 GenericZipWriter::Zstd(w) => Some(w as &mut dyn Write),
2309 #[cfg(feature = "xz")]
2310 GenericZipWriter::Xz(w) => Some(w as &mut dyn Write),
2311 #[cfg(feature = "ppmd")]
2312 GenericZipWriter::Ppmd(w) => Some(w as &mut dyn Write),
2313 GenericZipWriter::Closed => None,
2314 }
2315 }
2316
2317 const fn is_closed(&self) -> bool {
2318 matches!(*self, GenericZipWriter::Closed)
2319 }
2320
2321 fn try_inner_mut(&mut self) -> Result<&mut W, std::io::Error> {
2322 match *self {
2323 GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(ref mut w)) => Ok(w),
2324 _ => Err(std::io::Error::other(
2325 "Should have switched to stored and unencrypted beforehand",
2326 )),
2327 }
2328 }
2329}
2330
2331#[cfg(feature = "_deflate-any")]
2332fn deflate_compression_level_range() -> std::ops::RangeInclusive<u32> {
2333 #[cfg(not(any(feature = "deflate-zopfli", feature = "deflate-flate2")))]
2334 {
2335 compile_error!("min: unknown deflate variant - enable deflate-zopfli or deflate-flate2")
2336 }
2337
2338 let min = {
2339 #[cfg(feature = "deflate-flate2")]
2340 {
2341 flate2::Compression::fast().level()
2342 }
2343 #[cfg(all(not(feature = "deflate-flate2"), feature = "deflate-zopfli"))]
2344 {
2345 1
2346 }
2347 };
2348
2349 let max = {
2350 #[cfg(feature = "deflate-zopfli")]
2351 {
2352 264
2353 }
2354 #[cfg(all(not(feature = "deflate-zopfli"), feature = "deflate-flate2"))]
2355 {
2356 flate2::Compression::best().level()
2357 }
2358 };
2359
2360 min..=max
2361}
2362
2363#[cfg(feature = "_bzip2_any")]
2364fn bzip2_compression_level_range() -> std::ops::RangeInclusive<u32> {
2365 let min = bzip2::Compression::fast().level();
2366 let max = bzip2::Compression::best().level();
2367 min..=max
2368}
2369
2370#[cfg(any(
2371 feature = "_deflate-any",
2372 feature = "_bzip2_any",
2373 feature = "ppmd",
2374 feature = "xz",
2375 feature = "zstd",
2376))]
2377fn validate_value_in_range<T: Ord + Copy, U: Ord + Copy + TryFrom<T>>(
2378 value: T,
2379 range: std::ops::RangeInclusive<U>,
2380) -> Option<U> {
2381 let value: U = value.try_into().ok()?;
2382 if range.contains(&value) {
2383 Some(value)
2384 } else {
2385 None
2386 }
2387}
2388
2389fn update_aes_extra_data<W: Write + Seek>(
2390 writer: &mut W,
2391 file: &mut ZipFileData,
2392 bytes_written: u64,
2393) -> ZipResult<()> {
2394 let Some((aes_mode, version, compression_method)) = &mut file.aes_mode else {
2395 return Ok(());
2396 };
2397
2398 *version = if bytes_written < 20 {
2407 AesVendorVersion::Ae2
2408 } else {
2409 AesVendorVersion::Ae1
2410 };
2411
2412 let extra_data_start = file
2413 .extra_data_start
2414 .ok_or_else(|| std::io::Error::other("Cannot get the extra data start"))?;
2415
2416 writer.seek(SeekFrom::Start(
2417 extra_data_start + file.aes_extra_data_start,
2418 ))?;
2419
2420 let aes_extra_field = AexEncryption::new(*version, *aes_mode, *compression_method);
2421 let buf = aes_extra_field.as_bytes();
2422 writer.write_all(buf)?;
2423
2424 let aes_extra_data_start = file.aes_extra_data_start as usize;
2425 let Some(ref mut extra_field) = file.extra_field else {
2426 return Err(invalid!(
2427 "update_aes_extra_data called on a file that has no extra-data field"
2428 ));
2429 };
2430 let mut vec = extra_field.to_vec();
2431 vec[aes_extra_data_start..aes_extra_data_start + size_of::<AexEncryption>()]
2432 .copy_from_slice(buf);
2433 *extra_field = Arc::from(vec.into_boxed_slice());
2434
2435 Ok(())
2436}
2437
2438impl ZipFileData {
2439 pub(crate) fn update_local_file_header<T: Write + Seek>(
2440 &mut self,
2441 writer: &mut T,
2442 ) -> ZipResult<()> {
2443 writer.seek(SeekFrom::Start(
2444 self.header_start + (size_of::<Magic>() + offset_of!(ZipLocalEntryBlock, crc32)) as u64,
2445 ))?;
2446 writer.write_u32_le(self.crc32)?;
2447 if self.large_file {
2448 writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?;
2449 writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?;
2450
2451 self.update_local_zip64_extra_field(writer)?;
2452
2453 } else {
2456 if self.compressed_size > spec::ZIP64_BYTES_THR {
2458 return Err(ZipError::Io(std::io::Error::other(
2459 "large_file(true) option has not been set",
2460 )));
2461 }
2462 writer.write_u32_le(self.compressed_size as u32)?;
2463 writer.write_u32_le(self.uncompressed_size as u32)?;
2465 }
2466 Ok(())
2467 }
2468
2469 fn update_local_zip64_extra_field<T: Write + Seek>(&mut self, writer: &mut T) -> ZipResult<()> {
2470 let zip64_block = Zip64ExtendedInformation::local_header(
2471 self.large_file,
2472 self.uncompressed_size,
2473 self.compressed_size,
2474 )
2475 .ok_or(invalid!(
2476 "Attempted to update a nonexistent ZIP64 extra field"
2477 ))?;
2478
2479 let zip64_extra_field_start = self.header_start
2480 + (size_of::<Magic>() + size_of::<ZipLocalEntryBlock>()) as u64
2481 + self.file_name_raw.len() as u64;
2482
2483 writer.seek(SeekFrom::Start(zip64_extra_field_start))?;
2484 let zip64_block = zip64_block.serialize();
2485 writer.write_all(&zip64_block)?;
2486 Ok(())
2487 }
2488
2489 pub(crate) fn write_central_directory_header<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
2490 let mut block = self.block()?;
2491 let stripped_extra = if let Some(extra) = &self.extra_field {
2492 strip_alignment_extra_field(extra, true)
2493 } else {
2494 Vec::new()
2495 };
2496 let central_len = self.central_extra_field_len();
2497 let zip64_extra_field_block = Zip64ExtendedInformation::central_header(
2498 self.large_file,
2499 self.uncompressed_size,
2500 self.compressed_size,
2501 self.header_start,
2502 );
2503 let zip64_block_len = if let Some(zip64) = zip64_extra_field_block {
2504 zip64.full_size()
2505 } else {
2506 0
2507 };
2508 let total_extra_len = zip64_block_len + stripped_extra.len() + central_len;
2509 block.extra_field_length = u16::try_from(total_extra_len)
2510 .map_err(|_| invalid!("Extra field length in central directory exceeds 64KiB"))?;
2511
2512 block.write(writer)?;
2513 writer.write_all(&self.file_name_raw)?;
2515 if let Some(zip64_extra_field) = zip64_extra_field_block {
2517 writer.write_all(&zip64_extra_field.serialize())?;
2518 }
2519 if !stripped_extra.is_empty() {
2520 writer.write_all(&stripped_extra)?;
2521 }
2522 if let Some(central_extra_field) = &self.central_extra_field {
2523 writer.write_all(central_extra_field)?;
2524 }
2525 writer.write_all(self.file_comment.as_bytes())?;
2527
2528 Ok(())
2529 }
2530}
2531
2532pub(crate) fn strip_alignment_extra_field(extra_field: &[u8], remove_zip64: bool) -> Vec<u8> {
2533 let mut new_extra = Vec::with_capacity(extra_field.len());
2534 let mut cursor = 0;
2535 while cursor + 4 <= extra_field.len() {
2536 let tag = u16::from_le_bytes([extra_field[cursor], extra_field[cursor + 1]]);
2537 let len = u16::from_le_bytes([extra_field[cursor + 2], extra_field[cursor + 3]]) as usize;
2538 if cursor + 4 + len > extra_field.len() {
2539 new_extra.extend_from_slice(&extra_field[cursor..]);
2540 break;
2541 }
2542
2543 if tag != UsedExtraField::DataStreamAlignment.as_u16()
2544 && !(tag == UsedExtraField::Zip64ExtendedInfo.as_u16() && remove_zip64)
2545 {
2546 new_extra.extend_from_slice(&extra_field[cursor..cursor + 4 + len]);
2547 }
2548 cursor += 4 + len;
2549 }
2550 if cursor < extra_field.len() {
2551 new_extra.extend_from_slice(&extra_field[cursor..]);
2552 }
2553 new_extra
2554}
2555
2556pub struct StreamWriter<W: Write> {
2559 inner: W,
2560 bytes_written: u64,
2561}
2562
2563impl<W: Write> StreamWriter<W> {
2564 pub fn new(inner: W) -> StreamWriter<W> {
2566 Self {
2567 inner,
2568 bytes_written: 0,
2569 }
2570 }
2571
2572 pub fn get_ref(&self) -> &W {
2574 &self.inner
2575 }
2576
2577 pub fn get_mut(&mut self) -> &mut W {
2579 &mut self.inner
2580 }
2581
2582 pub fn into_inner(self) -> W {
2584 self.inner
2585 }
2586}
2587
2588impl<W: Write> Write for StreamWriter<W> {
2589 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
2590 let bytes_written = self.inner.write(buf)?;
2591 self.bytes_written += bytes_written as u64;
2592 Ok(bytes_written)
2593 }
2594
2595 fn flush(&mut self) -> io::Result<()> {
2596 self.inner.flush()
2597 }
2598}
2599
2600impl<W: Write> Seek for StreamWriter<W> {
2601 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
2602 match pos {
2603 SeekFrom::Current(0) | SeekFrom::End(0) => return Ok(self.bytes_written),
2604 SeekFrom::Start(x) if x == self.bytes_written => {
2605 return Ok(self.bytes_written);
2606 }
2607 _ => {}
2608 }
2609 Err(io::Error::new(
2610 ErrorKind::Unsupported,
2611 "seek is not supported",
2612 ))
2613 }
2614}
2615
2616#[cfg(test)]
2617#[allow(unknown_lints)] #[allow(clippy::needless_update)] #[allow(clippy::octal_escapes)] mod tests {
2621 use super::{ExtendedFileOptions, FileOptions, FullFileOptions, ZipWriter};
2622 use crate::CompressionMethod::Stored;
2623 use crate::ZipArchive;
2624 use crate::compression::CompressionMethod;
2625 use crate::datetime::DateTime;
2626 use crate::result::ZipResult;
2627 use crate::types::System;
2628 use crate::write::EncryptWith::ZipCrypto;
2629 use crate::write::SimpleFileOptions;
2630 use crate::zipcrypto::ZipCryptoKeys;
2631 #[cfg(feature = "deflate-flate2")]
2632 use std::io::Read;
2633 use std::io::{Cursor, Write};
2634 use std::marker::PhantomData;
2635 use std::path::PathBuf;
2636
2637 const SYSTEM_BYTE: u8 = if cfg!(windows) {
2638 System::Dos
2639 } else {
2640 System::Unix
2641 } as u8;
2642
2643 #[test]
2644 fn write_empty_zip() {
2645 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2646 writer.set_comment("ZIP").unwrap();
2647 let result = writer.finish().unwrap();
2648 assert_eq!(result.get_ref().len(), 25);
2649 assert_eq!(
2650 *result.get_ref(),
2651 [
2652 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
2653 ]
2654 );
2655 }
2656
2657 #[test]
2658 fn write_file_comment_roundtrip() -> ZipResult<()> {
2659 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2660 let options = FileOptions {
2662 extended_options: ExtendedFileOptions {
2663 extra_data: vec![].into(),
2664 central_extra_data: vec![].into(),
2665 file_comment: Some("file comment".into()),
2666 },
2667
2668 #[cfg(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))]
2670 compression_method: crate::CompressionMethod::Stored,
2671 ..Default::default()
2672 };
2673 writer.start_file("foo.txt", options)?;
2674 writer.write_all(b"data")?;
2675 let options2 = FileOptions {
2676 extended_options: ExtendedFileOptions {
2677 extra_data: vec![].into(),
2678 central_extra_data: vec![].into(),
2679 file_comment: Some("file2 comment".into()),
2680 },
2681 #[cfg(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))]
2683 compression_method: crate::CompressionMethod::Stored,
2684 ..Default::default()
2685 };
2686 writer.start_file("foo2.txt", options2)?;
2687 writer.write_all(b"data2")?;
2688
2689 let bytes = writer.finish()?.into_inner();
2690 let mut archive = ZipArchive::new(Cursor::new(bytes))?;
2691 {
2692 let file = archive.by_name("foo.txt")?;
2693 assert_eq!(file.comment(), "file comment");
2694 }
2695 let file2 = archive.by_name("foo2.txt")?;
2696 assert_eq!(file2.comment(), "file2 comment");
2697 Ok(())
2698 }
2699
2700 #[test]
2701 fn unix_permissions_bitmask() {
2702 let options = SimpleFileOptions::default().unix_permissions(0o120777);
2704 assert_eq!(options.permissions, Some(0o777));
2705 }
2706
2707 #[test]
2708 fn write_zip_dir() {
2709 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2710 writer
2711 .add_directory(
2712 "test",
2713 SimpleFileOptions::default().last_modified_time(
2714 DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
2715 ),
2716 )
2717 .unwrap();
2718 assert!(
2719 writer
2720 .write(b"writing to a directory is not allowed, and will not write any data")
2721 .is_err()
2722 );
2723 let result = writer.finish().unwrap();
2724 assert_eq!(result.get_ref().len(), 108);
2725 const DOS_DIR_ATTRIBUTES_BYTE: u8 = if cfg!(windows) { 0x10 } else { 0 };
2726 #[rustfmt::skip]
2727 assert_eq!(
2728 *result.get_ref(),
2729 &[
2730 80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2731 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 1, 2, 20, SYSTEM_BYTE, 20, 0, 0, 0, 0, 0,
2732 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,
2733 DOS_DIR_ATTRIBUTES_BYTE, 0, 237, 65, 0, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0,
2734 1, 0, 51, 0, 0, 0, 35, 0, 0, 0, 0, 0,
2735 ] as &[u8]
2736 );
2737 }
2738
2739 #[test]
2740 fn write_symlink_simple() {
2741 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2742 writer
2743 .add_symlink(
2744 "name",
2745 "target",
2746 SimpleFileOptions::default().last_modified_time(
2747 DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
2748 ),
2749 )
2750 .unwrap();
2751 assert!(
2752 writer
2753 .write(b"writing to a symlink is not allowed and will not write any data")
2754 .is_err()
2755 );
2756 let result = writer.finish().unwrap();
2757 assert_eq!(result.get_ref().len(), 112);
2758 #[rustfmt::skip]
2759 assert_eq!(
2760 *result.get_ref(),
2761 &[
2762 80u8, 75, 3, 4, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0,
2763 6, 0, 0, 0, 4, 0, 0, 0, 110, 97, 109, 101, 116, 97, 114, 103, 101, 116, 80, 75, 1,
2764 2, 10, System::Unix as u8, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0, 6, 0,
2765 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 161, 0, 0, 0, 0, 110, 97, 109, 101,
2766 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 50, 0, 0, 0, 40, 0, 0, 0, 0, 0
2767 ] as &[u8],
2768 );
2769 }
2770
2771 #[test]
2772 fn test_path_normalization() {
2773 let mut path = PathBuf::new();
2774 path.push("foo");
2775 path.push("bar");
2776 path.push("..");
2777 path.push(".");
2778 path.push("example.txt");
2779 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2780 writer
2781 .start_file_from_path(path, SimpleFileOptions::default())
2782 .unwrap();
2783 let archive = writer.finish_into_readable().unwrap();
2784 assert_eq!(Some("foo/example.txt"), archive.name_for_index(0));
2785 }
2786
2787 #[test]
2788 fn write_symlink_wonky_paths() {
2789 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2790 writer
2791 .add_symlink(
2792 "directory\\link",
2793 "/absolute/symlink\\with\\mixed/slashes",
2794 SimpleFileOptions::default().last_modified_time(
2795 DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
2796 ),
2797 )
2798 .unwrap();
2799 assert!(
2800 writer
2801 .write(b"writing to a symlink is not allowed and will not write any data")
2802 .is_err()
2803 );
2804 let result = writer.finish().unwrap();
2805 assert_eq!(result.get_ref().len(), 162);
2806 #[rustfmt::skip]
2807 assert_eq!(
2808 *result.get_ref(),
2809 &[
2810 80u8, 75, 3, 4, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95, 41, 81, 245, 36, 0, 0, 0,
2811 36, 0, 0, 0, 14, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105,
2812 110, 107, 47, 97, 98, 115, 111, 108, 117, 116, 101, 47, 115, 121, 109, 108, 105,
2813 110, 107, 92, 119, 105, 116, 104, 92, 109, 105, 120, 101, 100, 47, 115, 108, 97,
2814 115, 104, 101, 115, 80, 75, 1, 2, 10, System::Unix as u8, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95,
2815 41, 81, 245, 36, 0, 0, 0, 36, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
2816 161, 0, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105, 110,
2817 107, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 60, 0, 0, 0, 80, 0, 0, 0, 0, 0
2818 ] as &[u8],
2819 );
2820 }
2821
2822 #[test]
2823 fn write_mimetype_zip() {
2824 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2825 let options = FileOptions {
2826 compression_method: Stored,
2827 compression_level: None,
2828 last_modified_time: DateTime::default(),
2829 permissions: Some(33188),
2830 large_file: false,
2831 encrypt_with: None,
2832 extended_options: (),
2833 alignment: 1,
2834 #[cfg(feature = "deflate-zopfli")]
2835 zopfli_buffer_size: None,
2836 #[cfg(feature = "aes-crypto")]
2837 aes_mode: None,
2838 system: None,
2839 };
2840 writer.start_file("mimetype", options).unwrap();
2841 writer
2842 .write_all(b"application/vnd.oasis.opendocument.text")
2843 .unwrap();
2844 let result = writer.finish().unwrap();
2845
2846 assert_eq!(result.get_ref().len(), 153);
2847 #[rustfmt::skip]
2848 assert_eq!(result.get_ref(), &[80, 75, 3, 4, 10, 0, 0, 0, 0, 0, 0, 0, 33, 0, 94, 198, 50,
2849 12, 39, 0, 0, 0, 39, 0, 0, 0, 8, 0, 0, 0, 109, 105, 109, 101, 116, 121, 112, 101, 97,
2850 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 118, 110, 100, 46, 111, 97, 115,
2851 105, 115, 46, 111, 112, 101, 110, 100, 111, 99, 117, 109, 101, 110, 116, 46, 116, 101,
2852 120, 116, 80, 75, 1, 2, 10, SYSTEM_BYTE, 10, 0, 0, 0, 0, 0, 0, 0, 33, 0, 94, 198, 50,
2853 12, 39, 0, 0, 0, 39, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 129, 0, 0, 0, 0,
2854 109, 105, 109, 101, 116, 121, 112, 101, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 54, 0, 0,
2855 0, 77, 0, 0, 0, 0, 0]);
2856 }
2857
2858 #[cfg(feature = "deflate-flate2")]
2859 const RT_TEST_TEXT: &str = "And I can't stop thinking about the moments that I lost to you\
2860 And I can't stop thinking of things I used to do\
2861 And I can't stop making bad decisions\
2862 And I can't stop eating stuff you make me chew\
2863 I put on a smile like you wanna see\
2864 Another day goes by that I long to be like you";
2865 #[cfg(feature = "deflate-flate2")]
2866 const RT_TEST_FILENAME: &str = "subfolder/sub-subfolder/can't_stop.txt";
2867 #[cfg(feature = "deflate-flate2")]
2868 const SECOND_FILENAME: &str = "different_name.xyz";
2869 #[cfg(feature = "deflate-flate2")]
2870 const THIRD_FILENAME: &str = "third_name.xyz";
2871
2872 #[test]
2873 fn write_non_utf8() {
2874 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2875 let options = FileOptions {
2876 compression_method: Stored,
2877 compression_level: None,
2878 last_modified_time: DateTime::default(),
2879 permissions: Some(33188),
2880 large_file: false,
2881 encrypt_with: None,
2882 extended_options: (),
2883 alignment: 1,
2884 #[cfg(feature = "deflate-zopfli")]
2885 zopfli_buffer_size: None,
2886 #[cfg(feature = "aes-crypto")]
2887 aes_mode: None,
2888 system: None,
2889 };
2890
2891 let filename = unsafe { String::from_utf8_unchecked(vec![214, 208, 206, 196]) };
2894 writer.start_file(filename, options).unwrap();
2895 writer.write_all(b"encoding GB18030").unwrap();
2896
2897 let filename = unsafe { String::from_utf8_unchecked(vec![147, 250, 149, 182]) };
2900 writer.start_file(filename, options).unwrap();
2901 writer.write_all(b"encoding SHIFT_JIS").unwrap();
2902 let result = writer.finish().unwrap();
2903
2904 assert_eq!(result.get_ref().len(), 224);
2905 #[rustfmt::skip]
2906 assert_eq!(result.get_ref(), &[80, 75, 3, 4, 10, 0, 0, 0, 0, 0, 0, 0, 33, 0, 29, 183, 125,
2907 75, 16, 0, 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 214, 208, 206, 196, 101, 110, 99, 111, 100,
2908 105, 110, 103, 32, 71, 66, 49, 56, 48, 51, 48, 80, 75, 3, 4, 10, 0, 0, 0, 0, 0, 0, 0,
2909 33, 0, 75, 110, 14, 242, 18, 0, 0, 0, 18, 0, 0, 0, 4, 0, 0, 0, 147, 250, 149, 182, 101,
2910 110, 99, 111, 100, 105, 110, 103, 32, 83, 72, 73, 70, 84, 95, 74, 73, 83, 80, 75, 1, 2,
2911 10, SYSTEM_BYTE, 10, 0, 0, 0, 0, 0, 0, 0, 33, 0, 29, 183, 125, 75, 16, 0, 0, 0, 16, 0,
2912 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 129, 0, 0, 0, 0, 214, 208, 206, 196, 80,
2913 75, 1, 2, 10, SYSTEM_BYTE, 10, 0, 0, 0, 0, 0, 0, 0, 33, 0, 75, 110, 14, 242, 18, 0, 0, 0, 18, 0,
2914 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 129, 50, 0, 0, 0, 147, 250, 149, 182, 80,
2915 75, 5, 6, 0, 0, 0, 0, 2, 0, 2, 0, 100, 0, 0, 0, 102, 0, 0, 0, 0, 0]);
2916 }
2917
2918 #[test]
2919 fn path_to_string() {
2920 let mut path = PathBuf::new();
2921 #[cfg(windows)]
2922 path.push(r"C:\");
2923 #[cfg(unix)]
2924 path.push("/");
2925 path.push("windows");
2926 path.push("..");
2927 path.push(".");
2928 path.push("system32");
2929 let path_str = super::path_to_string(&path).unwrap();
2930 assert_eq!(&*path_str, "system32");
2931 }
2932
2933 #[test]
2934 #[cfg(feature = "deflate-flate2")]
2935 fn test_shallow_copy() {
2936 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2937 let options = FileOptions {
2938 compression_method: CompressionMethod::default(),
2939 compression_level: None,
2940 last_modified_time: DateTime::default(),
2941 permissions: Some(33188),
2942 large_file: false,
2943 encrypt_with: None,
2944 extended_options: (),
2945 alignment: 0,
2946 #[cfg(feature = "deflate-zopfli")]
2947 zopfli_buffer_size: None,
2948 #[cfg(feature = "aes-crypto")]
2949 aes_mode: None,
2950 system: None,
2951 };
2952 writer.start_file(RT_TEST_FILENAME, options).unwrap();
2953 writer.write_all(RT_TEST_TEXT.as_ref()).unwrap();
2954 writer
2955 .shallow_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
2956 .unwrap();
2957 writer
2958 .shallow_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
2959 .expect_err("Duplicate filename");
2960 let zip = writer.finish().unwrap();
2961 let mut writer = ZipWriter::new_append(zip).unwrap();
2962 writer
2963 .shallow_copy_file(SECOND_FILENAME, SECOND_FILENAME)
2964 .expect_err("Duplicate filename");
2965 let mut reader = writer.finish_into_readable().unwrap();
2966 let mut file_names: Vec<&str> = reader.file_names().collect();
2967 file_names.sort();
2968 let mut expected_file_names = vec![RT_TEST_FILENAME, SECOND_FILENAME];
2969 expected_file_names.sort();
2970 assert_eq!(file_names, expected_file_names);
2971 let mut first_file_content = String::new();
2972 reader
2973 .by_name(RT_TEST_FILENAME)
2974 .unwrap()
2975 .read_to_string(&mut first_file_content)
2976 .unwrap();
2977 assert_eq!(first_file_content, RT_TEST_TEXT);
2978 let mut second_file_content = String::new();
2979 reader
2980 .by_name(SECOND_FILENAME)
2981 .unwrap()
2982 .read_to_string(&mut second_file_content)
2983 .unwrap();
2984 assert_eq!(second_file_content, RT_TEST_TEXT);
2985 }
2986
2987 #[test]
2988 #[cfg(feature = "deflate-flate2")]
2989 fn test_deep_copy() {
2990 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
2991 let options = FileOptions {
2992 compression_method: CompressionMethod::default(),
2993 compression_level: None,
2994 last_modified_time: DateTime::default(),
2995 permissions: Some(33188),
2996 large_file: false,
2997 encrypt_with: None,
2998 extended_options: (),
2999 alignment: 0,
3000 #[cfg(feature = "deflate-zopfli")]
3001 zopfli_buffer_size: None,
3002 #[cfg(feature = "aes-crypto")]
3003 aes_mode: None,
3004 system: None,
3005 };
3006 writer.start_file(RT_TEST_FILENAME, options).unwrap();
3007 writer.write_all(RT_TEST_TEXT.as_ref()).unwrap();
3008 writer
3009 .deep_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
3010 .unwrap();
3011 let zip = writer.finish().unwrap().into_inner();
3012 zip.iter().copied().for_each(|x| print!("{x:02x}"));
3013 println!();
3014 let mut writer = ZipWriter::new_append(Cursor::new(zip)).unwrap();
3015 writer
3016 .deep_copy_file(RT_TEST_FILENAME, THIRD_FILENAME)
3017 .unwrap();
3018 let zip = writer.finish().unwrap();
3019 let mut reader = ZipArchive::new(zip).unwrap();
3020 let mut file_names: Vec<&str> = reader.file_names().collect();
3021 file_names.sort();
3022 let mut expected_file_names = vec![RT_TEST_FILENAME, SECOND_FILENAME, THIRD_FILENAME];
3023 expected_file_names.sort();
3024 assert_eq!(file_names, expected_file_names);
3025 let mut first_file_content = String::new();
3026 reader
3027 .by_name(RT_TEST_FILENAME)
3028 .unwrap()
3029 .read_to_string(&mut first_file_content)
3030 .unwrap();
3031 assert_eq!(first_file_content, RT_TEST_TEXT);
3032 let mut second_file_content = String::new();
3033 reader
3034 .by_name(SECOND_FILENAME)
3035 .unwrap()
3036 .read_to_string(&mut second_file_content)
3037 .unwrap();
3038 assert_eq!(second_file_content, RT_TEST_TEXT);
3039 }
3040
3041 #[test]
3042 fn duplicate_filenames() {
3043 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3044 writer
3045 .start_file("foo/bar/test", SimpleFileOptions::default())
3046 .unwrap();
3047 writer
3048 .write_all("The quick brown 🦊 jumps over the lazy 🐕".as_bytes())
3049 .unwrap();
3050 writer
3051 .start_file("foo/bar/test", SimpleFileOptions::default())
3052 .expect_err("Expected duplicate filename not to be allowed");
3053 }
3054
3055 #[test]
3056 fn test_filename_looks_like_zip64_locator() {
3057 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3058 writer
3059 .start_file(
3060 "PK\u{6}\u{7}\0\0\0\u{11}\0\0\0\0\0\0\0\0\0\0\0\0",
3061 SimpleFileOptions::default(),
3062 )
3063 .unwrap();
3064 let zip = writer.finish().unwrap();
3065 let _ = ZipArchive::new(zip).unwrap();
3066 }
3067
3068 #[test]
3069 fn test_filename_looks_like_zip64_locator_2() {
3070 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3071 writer
3072 .start_file(
3073 "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",
3074 SimpleFileOptions::default(),
3075 )
3076 .unwrap();
3077 let zip = writer.finish().unwrap();
3078 let _ = ZipArchive::new(zip).unwrap();
3079 }
3080
3081 #[test]
3082 fn test_filename_looks_like_zip64_locator_2a() {
3083 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3084 writer
3085 .start_file(
3086 "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",
3087 SimpleFileOptions::default(),
3088 )
3089 .unwrap();
3090 let zip = writer.finish().unwrap();
3091 let _ = ZipArchive::new(zip).unwrap();
3092 }
3093
3094 #[test]
3095 fn test_filename_looks_like_zip64_locator_3() {
3096 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3097 writer
3098 .start_file("\0PK\u{6}\u{6}", SimpleFileOptions::default())
3099 .unwrap();
3100 writer
3101 .start_file(
3102 "\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}",
3103 SimpleFileOptions::default(),
3104 )
3105 .unwrap();
3106 let zip = writer.finish().unwrap();
3107 let _ = ZipArchive::new(zip).unwrap();
3108 }
3109
3110 #[test]
3111 fn test_filename_looks_like_zip64_locator_4() {
3112 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3113 writer
3114 .start_file("PK\u{6}\u{6}", SimpleFileOptions::default())
3115 .unwrap();
3116 writer
3117 .start_file("\0\0\0\0\0\0", SimpleFileOptions::default())
3118 .unwrap();
3119 writer
3120 .start_file("\0", SimpleFileOptions::default())
3121 .unwrap();
3122 writer.start_file("", SimpleFileOptions::default()).unwrap();
3123 writer
3124 .start_file("\0\0", SimpleFileOptions::default())
3125 .unwrap();
3126 writer
3127 .start_file(
3128 "\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
3129 SimpleFileOptions::default(),
3130 )
3131 .unwrap();
3132 let zip = writer.finish().unwrap();
3133 let _ = ZipArchive::new(zip).unwrap();
3134 }
3135
3136 #[test]
3137 fn test_filename_looks_like_zip64_locator_5() -> ZipResult<()> {
3138 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3139 writer
3140 .add_directory("", SimpleFileOptions::default().with_alignment(21))
3141 .unwrap();
3142 let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
3143 writer.shallow_copy_file("/", "").unwrap();
3144 writer.shallow_copy_file("", "\0").unwrap();
3145 writer.shallow_copy_file("\0", "PK\u{6}\u{6}").unwrap();
3146 let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
3147 writer
3148 .start_file("\0\0\0\0\0\0", SimpleFileOptions::default())
3149 .unwrap();
3150 let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
3151 writer
3152 .start_file(
3153 "#PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
3154 SimpleFileOptions::default(),
3155 )
3156 .unwrap();
3157 let zip = writer.finish().unwrap();
3158 let _ = ZipArchive::new(zip).unwrap();
3159 Ok(())
3160 }
3161
3162 #[test]
3163 #[cfg(feature = "deflate-flate2")]
3164 fn remove_shallow_copy_keeps_original() -> ZipResult<()> {
3165 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3166 writer
3167 .start_file("original", SimpleFileOptions::default())
3168 .unwrap();
3169 writer.write_all(RT_TEST_TEXT.as_bytes()).unwrap();
3170 writer
3171 .shallow_copy_file("original", "shallow_copy")
3172 .unwrap();
3173 writer.abort_file().unwrap();
3174 let mut zip = ZipArchive::new(writer.finish().unwrap()).unwrap();
3175 let mut file = zip.by_name("original").unwrap();
3176 let mut contents = Vec::new();
3177 file.read_to_end(&mut contents).unwrap();
3178 assert_eq!(RT_TEST_TEXT.as_bytes(), contents);
3179 Ok(())
3180 }
3181
3182 #[test]
3183 fn remove_encrypted_file() -> ZipResult<()> {
3184 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3185 let first_file_options = SimpleFileOptions::default()
3186 .with_alignment(0xFFFF)
3187 .with_deprecated_encryption(b"Password");
3188 writer.start_file("", first_file_options).unwrap();
3189 writer.abort_file().unwrap();
3190 let zip = writer.finish().unwrap();
3191 let mut writer = ZipWriter::new(zip);
3192 writer.start_file("", SimpleFileOptions::default()).unwrap();
3193 Ok(())
3194 }
3195
3196 #[test]
3197 fn remove_encrypted_aligned_symlink() -> ZipResult<()> {
3198 let mut options = SimpleFileOptions::default();
3199 options = options.with_deprecated_encryption(b"Password");
3200 options.alignment = 0xFFFF;
3201 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3202 writer.add_symlink("", "s\t\0\0ggggg\0\0", options).unwrap();
3203 writer.abort_file().unwrap();
3204 let zip = writer.finish().unwrap();
3205 let mut writer = ZipWriter::new_append(zip).unwrap();
3206 writer.start_file("", SimpleFileOptions::default()).unwrap();
3207 Ok(())
3208 }
3209
3210 #[cfg(feature = "deflate-zopfli")]
3211 #[test]
3212 fn zopfli_empty_write() -> ZipResult<()> {
3213 let mut options = SimpleFileOptions::default();
3214 options = options
3215 .compression_method(CompressionMethod::default())
3216 .compression_level(Some(264));
3217 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3218 writer.start_file("", options).unwrap();
3219 writer.write_all(&[]).unwrap();
3220 writer.write_all(&[]).unwrap();
3221 Ok(())
3222 }
3223
3224 #[test]
3225 fn crash_with_no_features() -> ZipResult<()> {
3226 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'";
3227 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3228 let mut options = SimpleFileOptions::default();
3229 options = options.with_alignment(3584).compression_method(Stored);
3230 writer.start_file(ORIGINAL_FILE_NAME, options)?;
3231 let archive = writer.finish()?;
3232 let mut writer = ZipWriter::new_append(archive)?;
3233 writer.shallow_copy_file(ORIGINAL_FILE_NAME, "\u{6}\\")?;
3234 writer.finish()?;
3235 Ok(())
3236 }
3237
3238 #[test]
3239 fn test_alignment() {
3240 let page_size = 4096;
3241 let options = SimpleFileOptions::default()
3242 .compression_method(Stored)
3243 .with_alignment(page_size);
3244 let mut zip = ZipWriter::new(Cursor::new(Vec::new()));
3245 let contents = b"sleeping";
3246 let () = zip.start_file("sleep", options).unwrap();
3247 let _count = zip.write(&contents[..]).unwrap();
3248 let mut zip = zip.finish_into_readable().unwrap();
3249 let file = zip.by_index(0).unwrap();
3250 assert_eq!(file.name(), "sleep");
3251 let data_start = file.data_start().unwrap();
3252 assert_eq!(data_start, u64::from(page_size));
3253 }
3254
3255 #[test]
3256 fn test_alignment_2() {
3257 let page_size = 4096;
3258 let mut data = Vec::new();
3259 {
3260 let options = SimpleFileOptions::default()
3261 .compression_method(Stored)
3262 .with_alignment(page_size);
3263 let mut zip = ZipWriter::new(Cursor::new(&mut data));
3264 let contents = b"sleeping";
3265 let () = zip.start_file("sleep", options).unwrap();
3266 let _count = zip.write(&contents[..]).unwrap();
3267 }
3268 assert_eq!(data[4096..4104], b"sleeping"[..]);
3269 {
3270 let mut zip = ZipArchive::new(Cursor::new(&mut data)).unwrap();
3271 let file = zip.by_index(0).unwrap();
3272 assert_eq!(file.name(), "sleep");
3273 let data_start = file.data_start().unwrap();
3274 assert_eq!(data_start, u64::from(page_size));
3275 }
3276 }
3277
3278 #[test]
3279 fn test_crash_short_read() {
3280 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3281 let comment = vec![
3282 1, 80, 75, 5, 6, 237, 237, 237, 237, 237, 237, 237, 237, 44, 255, 191, 255, 255, 255,
3283 255, 255, 255, 255, 255, 16,
3284 ]
3285 .into_boxed_slice();
3286 writer.set_raw_comment(comment).unwrap();
3287 let options = SimpleFileOptions::default()
3288 .compression_method(Stored)
3289 .with_alignment(11823);
3290 writer.start_file("", options).unwrap();
3291 writer.write_all(&[255, 255, 44, 255, 0]).unwrap();
3292 let written = writer.finish().unwrap();
3293 let _ = ZipWriter::new_append(written).unwrap();
3294 }
3295
3296 #[cfg(all(feature = "_deflate-any", feature = "aes-crypto"))]
3297 #[test]
3298 fn test_fuzz_failure_2024_05_08() -> ZipResult<()> {
3299 let mut first_writer = ZipWriter::new(Cursor::new(Vec::new()));
3300 let mut second_writer = ZipWriter::new(Cursor::new(Vec::new()));
3301 let options = SimpleFileOptions::default()
3302 .compression_method(Stored)
3303 .with_alignment(46036);
3304 second_writer.add_symlink("\0", "", options)?;
3305 let second_archive = second_writer.finish_into_readable()?.into_inner();
3306 let mut second_writer = ZipWriter::new_append(second_archive)?;
3307 let options = SimpleFileOptions::default()
3308 .compression_method(CompressionMethod::Deflated)
3309 .large_file(true)
3310 .with_alignment(46036)
3311 .with_aes_encryption(crate::AesMode::Aes128, "\0\0");
3312 second_writer.add_symlink("", "", options)?;
3313 let second_archive = second_writer.finish_into_readable()?.into_inner();
3314 let mut second_writer = ZipWriter::new_append(second_archive)?;
3315 let options = SimpleFileOptions::default().compression_method(Stored);
3316 second_writer.start_file(" ", options)?;
3317 let second_archive = second_writer.finish_into_readable()?;
3318 first_writer.merge_archive(second_archive)?;
3319 let _ = ZipArchive::new(first_writer.finish()?)?;
3320 Ok(())
3321 }
3322
3323 #[cfg(all(feature = "_bzip2_any", not(miri)))]
3324 #[test]
3325 fn test_fuzz_failure_2024_06_08() -> ZipResult<()> {
3326 use crate::write::ExtendedFileOptions;
3327 use CompressionMethod::Bzip2;
3328
3329 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3330 writer.set_flush_on_finish_file(false);
3331 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";
3332 let sub_writer = {
3333 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3334 writer.set_flush_on_finish_file(false);
3335 let options = FileOptions {
3336 compression_method: Bzip2,
3337 compression_level: None,
3338 last_modified_time: DateTime::from_date_and_time(1980, 5, 20, 21, 0, 57)?,
3339 permissions: None,
3340 large_file: false,
3341 encrypt_with: None,
3342 extended_options: ExtendedFileOptions {
3343 extra_data: vec![].into(),
3344 central_extra_data: vec![].into(),
3345 file_comment: None,
3346 },
3347 alignment: 2048,
3348 ..Default::default()
3349 };
3350 writer.add_symlink_from_path(SYMLINK_PATH, "||\0\0\0\0", options)?;
3351 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3352 writer.deep_copy_file_from_path(SYMLINK_PATH, "")?;
3353 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3354 writer.abort_file()?;
3355 writer
3356 };
3357 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3358 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3359 writer.deep_copy_file_from_path(SYMLINK_PATH, "foo")?;
3360 let _ = writer.finish_into_readable()?;
3361 Ok(())
3362 }
3363
3364 #[test]
3365 fn test_short_extra_data() {
3366 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3367 writer.set_flush_on_finish_file(false);
3368 let options = FileOptions {
3369 extended_options: ExtendedFileOptions {
3370 extra_data: vec![].into(),
3371 central_extra_data: vec![99, 0, 15, 0, 207].into(),
3372 file_comment: None,
3373 },
3374 ..Default::default()
3375 };
3376 assert!(writer.start_file_from_path("", options).is_err());
3377 }
3378
3379 #[test]
3380 #[cfg(not(feature = "unreserved"))]
3381 fn test_invalid_extra_data() -> ZipResult<()> {
3382 use crate::write::ExtendedFileOptions;
3383 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3384 writer.set_flush_on_finish_file(false);
3385 let options = FileOptions {
3386 compression_method: Stored,
3387 compression_level: None,
3388 last_modified_time: DateTime::from_date_and_time(1980, 1, 4, 6, 54, 0)?,
3389 permissions: None,
3390 large_file: false,
3391 encrypt_with: None,
3392 extended_options: ExtendedFileOptions {
3393 extra_data: vec![].into(),
3394 central_extra_data: vec![
3395 7, 0, 15, 0, 207, 117, 177, 117, 112, 2, 0, 255, 255, 131, 255, 255, 255, 80,
3396 185,
3397 ]
3398 .into(),
3399 file_comment: None,
3400 },
3401 alignment: 32787,
3402 ..Default::default()
3403 };
3404 assert!(writer.start_file_from_path("", options).is_err());
3405 Ok(())
3406 }
3407
3408 #[test]
3409 #[cfg(not(feature = "unreserved"))]
3410 fn test_invalid_extra_data_unreserved() {
3411 use crate::write::ExtendedFileOptions;
3412 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3413 let options = FileOptions {
3414 compression_method: Stored,
3415 compression_level: None,
3416 last_modified_time: DateTime::from_date_and_time(2021, 8, 8, 1, 0, 29).unwrap(),
3417 permissions: None,
3418 large_file: true,
3419 encrypt_with: None,
3420 extended_options: ExtendedFileOptions {
3421 extra_data: vec![].into(),
3422 central_extra_data: vec![
3423 1, 41, 4, 0, 1, 255, 245, 117, 117, 112, 5, 0, 80, 255, 149, 255, 247,
3424 ]
3425 .into(),
3426 file_comment: None,
3427 },
3428 alignment: 4103,
3429 ..Default::default()
3430 };
3431 assert!(writer.start_file_from_path("", options).is_err());
3432 }
3433
3434 #[cfg(feature = "deflate64")]
3435 #[test]
3436 fn test_fuzz_crash_2024_06_13a() -> ZipResult<()> {
3437 use crate::write::ExtendedFileOptions;
3438 use CompressionMethod::Deflate64;
3439
3440 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3441 writer.set_flush_on_finish_file(false);
3442 let options = FileOptions {
3443 compression_method: Deflate64,
3444 compression_level: None,
3445 last_modified_time: DateTime::from_date_and_time(2039, 4, 17, 6, 18, 19)?,
3446 permissions: None,
3447 large_file: true,
3448 encrypt_with: None,
3449 extended_options: ExtendedFileOptions {
3450 extra_data: vec![].into(),
3451 central_extra_data: vec![].into(),
3452 file_comment: None,
3453 },
3454 alignment: 4,
3455 ..Default::default()
3456 };
3457 writer.add_directory_from_path("", options)?;
3458 let _ = writer.finish_into_readable()?;
3459 Ok(())
3460 }
3461
3462 #[test]
3463 fn test_fuzz_crash_2024_06_13b() -> ZipResult<()> {
3464 use crate::write::ExtendedFileOptions;
3465 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3466 writer.set_flush_on_finish_file(false);
3467 let sub_writer = {
3468 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3469 writer.set_flush_on_finish_file(false);
3470 let options = FileOptions {
3471 compression_method: Stored,
3472 compression_level: None,
3473 last_modified_time: DateTime::from_date_and_time(1980, 4, 14, 6, 11, 54)?,
3474 permissions: None,
3475 large_file: false,
3476 encrypt_with: None,
3477 extended_options: ExtendedFileOptions {
3478 extra_data: vec![].into(),
3479 central_extra_data: vec![].into(),
3480 file_comment: None,
3481 },
3482 alignment: 185,
3483 ..Default::default()
3484 };
3485 writer.add_symlink_from_path("", "", options)?;
3486 writer
3487 };
3488 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3489 writer.deep_copy_file_from_path("", "_copy")?;
3490 let _ = writer.finish_into_readable()?;
3491 Ok(())
3492 }
3493
3494 #[test]
3495 fn test_fuzz_crash_2024_06_14() -> ZipResult<()> {
3496 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3497 writer.set_flush_on_finish_file(false);
3498 let sub_writer = {
3499 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3500 writer.set_flush_on_finish_file(false);
3501 let options = FullFileOptions {
3502 compression_method: Stored,
3503 large_file: true,
3504 alignment: 93,
3505 ..Default::default()
3506 };
3507 writer.start_file_from_path("\0", options)?;
3508 writer = ZipWriter::new_append(writer.finish()?)?;
3509 writer.deep_copy_file_from_path("\0", "")?;
3510 writer
3511 };
3512 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3513 writer.deep_copy_file_from_path("", "copy")?;
3514 let _ = writer.finish_into_readable()?;
3515 Ok(())
3516 }
3517
3518 #[test]
3519 fn test_fuzz_crash_2024_06_14a() -> ZipResult<()> {
3520 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3521 writer.set_flush_on_finish_file(false);
3522 let options = FileOptions {
3523 compression_method: Stored,
3524 compression_level: None,
3525 last_modified_time: DateTime::from_date_and_time(2083, 5, 30, 21, 45, 35)?,
3526 permissions: None,
3527 large_file: false,
3528 encrypt_with: None,
3529 extended_options: ExtendedFileOptions {
3530 extra_data: vec![].into(),
3531 central_extra_data: vec![].into(),
3532 file_comment: None,
3533 },
3534 alignment: 2565,
3535 ..Default::default()
3536 };
3537 writer.add_symlink_from_path("", "", options)?;
3538 writer.abort_file()?;
3539 let options = FileOptions {
3540 compression_method: Stored,
3541 compression_level: None,
3542 last_modified_time: DateTime::default(),
3543 permissions: None,
3544 large_file: false,
3545 encrypt_with: None,
3546 extended_options: ExtendedFileOptions {
3547 extra_data: vec![].into(),
3548 central_extra_data: vec![].into(),
3549 file_comment: None,
3550 },
3551 alignment: 0,
3552 ..Default::default()
3553 };
3554 writer.start_file_from_path("", options)?;
3555 let _ = writer.finish_into_readable()?;
3556 Ok(())
3557 }
3558
3559 #[allow(deprecated)]
3560 #[test]
3561 fn test_fuzz_crash_2024_06_14b() -> ZipResult<()> {
3562 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3563 writer.set_flush_on_finish_file(false);
3564 let options = FileOptions {
3565 compression_method: Stored,
3566 compression_level: None,
3567 last_modified_time: DateTime::from_date_and_time(2078, 3, 6, 12, 48, 58)?,
3568 permissions: None,
3569 large_file: true,
3570 encrypt_with: None,
3571 extended_options: ExtendedFileOptions {
3572 extra_data: vec![].into(),
3573 central_extra_data: vec![].into(),
3574 file_comment: None,
3575 },
3576 alignment: 0xFFF1,
3577 ..Default::default()
3578 };
3579 writer.start_file_from_path("\u{4}\0@\n//\u{c}", options)?;
3580 writer = ZipWriter::new_append(writer.finish()?)?;
3581 writer.abort_file()?;
3582 let options = FileOptions {
3583 compression_method: CompressionMethod::Unsupported(0xFFFF),
3584 compression_level: None,
3585 last_modified_time: DateTime::from_date_and_time(2055, 10, 2, 11, 48, 49)?,
3586 permissions: None,
3587 large_file: true,
3588 encrypt_with: None,
3589 extended_options: ExtendedFileOptions {
3590 extra_data: vec![255, 255, 1, 0, 255, 0, 0, 0, 0].into(),
3591 central_extra_data: vec![].into(),
3592 file_comment: None,
3593 },
3594 alignment: 0xFFFF,
3595 ..Default::default()
3596 };
3597 writer.add_directory_from_path("", options)?;
3598 let _ = writer.finish_into_readable()?;
3599 Ok(())
3600 }
3601
3602 #[test]
3603 fn test_fuzz_crash_2024_06_14c() -> ZipResult<()> {
3604 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3605 writer.set_flush_on_finish_file(false);
3606 let sub_writer = {
3607 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3608 writer.set_flush_on_finish_file(false);
3609 let options = FileOptions {
3610 compression_method: Stored,
3611 compression_level: None,
3612 last_modified_time: DateTime::from_date_and_time(2060, 4, 6, 13, 13, 3)?,
3613 permissions: None,
3614 large_file: true,
3615 encrypt_with: None,
3616 extended_options: ExtendedFileOptions {
3617 extra_data: vec![].into(),
3618 central_extra_data: vec![].into(),
3619 file_comment: None,
3620 },
3621 alignment: 0,
3622 ..Default::default()
3623 };
3624 writer.start_file_from_path("\0", options)?;
3625 writer.write_all(&([]))?;
3626 writer = ZipWriter::new_append(writer.finish()?)?;
3627 writer.deep_copy_file_from_path("\0", "")?;
3628 writer
3629 };
3630 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3631 writer.deep_copy_file_from_path("", "_copy")?;
3632 let _ = writer.finish_into_readable()?;
3633 Ok(())
3634 }
3635
3636 #[cfg(all(feature = "_deflate-any", feature = "aes-crypto"))]
3637 #[test]
3638 fn test_fuzz_crash_2024_06_14d() -> ZipResult<()> {
3639 use crate::AesMode::Aes256;
3640 use crate::write::EncryptWith::Aes;
3641 use CompressionMethod::Deflated;
3642 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3643 writer.set_flush_on_finish_file(false);
3644 let options = FileOptions {
3645 compression_method: Deflated,
3646 compression_level: Some(5),
3647 last_modified_time: DateTime::from_date_and_time(2107, 4, 8, 15, 54, 19)?,
3648 permissions: None,
3649 large_file: true,
3650 encrypt_with: Some(Aes {
3651 mode: Aes256,
3652 password: &[],
3653 salt: None,
3654 }),
3655 extended_options: ExtendedFileOptions {
3656 extra_data: vec![2, 0, 1, 0, 0].into(),
3657 central_extra_data: vec![
3658 35, 229, 2, 0, 41, 41, 231, 44, 2, 0, 52, 233, 82, 201, 0, 0, 3, 0, 2, 0, 233,
3659 255, 3, 0, 2, 0, 26, 154, 38, 251, 0, 0,
3660 ]
3661 .into(),
3662 file_comment: None,
3663 },
3664 alignment: 0xFFFF,
3665 ..Default::default()
3666 };
3667 assert!(writer.add_directory_from_path("", options).is_err());
3668 Ok(())
3669 }
3670
3671 #[test]
3672 fn test_fuzz_crash_2024_06_14e() -> ZipResult<()> {
3673 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3674 writer.set_flush_on_finish_file(false);
3675 let options = FileOptions {
3676 compression_method: Stored,
3677 compression_level: None,
3678 last_modified_time: DateTime::from_date_and_time(1988, 1, 1, 1, 6, 26)?,
3679 permissions: None,
3680 large_file: true,
3681 encrypt_with: None,
3682 extended_options: ExtendedFileOptions {
3683 extra_data: vec![76, 0, 1, 0, 0, 2, 0, 0, 0].into(),
3684 central_extra_data: vec![
3685 1, 149, 1, 0, 255, 3, 0, 0, 0, 2, 255, 0, 0, 12, 65, 1, 0, 0, 67, 149, 0, 0,
3686 76, 149, 2, 0, 149, 149, 67, 149, 0, 0,
3687 ]
3688 .into(),
3689 file_comment: None,
3690 },
3691 alignment: 0xFFFF,
3692 ..Default::default()
3693 };
3694 assert!(writer.add_directory_from_path("", options).is_err());
3695 let _ = writer.finish_into_readable()?;
3696 Ok(())
3697 }
3698
3699 #[allow(deprecated)]
3700 #[test]
3701 fn test_fuzz_crash_2024_06_17() -> ZipResult<()> {
3702 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3703 writer.set_flush_on_finish_file(false);
3704 let sub_writer = {
3705 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3706 writer.set_flush_on_finish_file(false);
3707 let sub_writer = {
3708 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3709 writer.set_flush_on_finish_file(false);
3710 let sub_writer = {
3711 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3712 writer.set_flush_on_finish_file(false);
3713 let sub_writer = {
3714 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3715 writer.set_flush_on_finish_file(false);
3716 let sub_writer = {
3717 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3718 writer.set_flush_on_finish_file(false);
3719 let sub_writer = {
3720 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3721 writer.set_flush_on_finish_file(false);
3722 let sub_writer = {
3723 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3724 writer.set_flush_on_finish_file(false);
3725 let sub_writer = {
3726 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3727 writer.set_flush_on_finish_file(false);
3728 let sub_writer = {
3729 let mut writer =
3730 ZipWriter::new(Cursor::new(Vec::new()));
3731 writer.set_flush_on_finish_file(false);
3732 let options = FileOptions {
3733 compression_method: CompressionMethod::Unsupported(
3734 0xFFFF,
3735 ),
3736 compression_level: Some(5),
3737 last_modified_time: DateTime::from_date_and_time(
3738 2107, 2, 8, 15, 0, 0,
3739 )?,
3740 permissions: None,
3741 large_file: true,
3742 encrypt_with: Some(ZipCrypto(
3743 ZipCryptoKeys::of(
3744 0x63ff, 0xc62d3103, 0xfffe00ea,
3745 ),
3746 PhantomData,
3747 )),
3748 extended_options: ExtendedFileOptions {
3749 extra_data: vec![].into(),
3750 central_extra_data: vec![].into(),
3751 file_comment: None,
3752 },
3753 alignment: 255,
3754 ..Default::default()
3755 };
3756 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)?;
3757 writer = ZipWriter::new_append(
3758 writer.finish_into_readable()?.into_inner(),
3759 )?;
3760 writer
3761 };
3762 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3763 writer = ZipWriter::new_append(
3764 writer.finish_into_readable()?.into_inner(),
3765 )?;
3766 let options = FileOptions {
3767 compression_method: Stored,
3768 compression_level: None,
3769 last_modified_time: DateTime::from_date_and_time(
3770 1992, 7, 3, 0, 0, 0,
3771 )?,
3772 permissions: None,
3773 large_file: true,
3774 encrypt_with: None,
3775 extended_options: ExtendedFileOptions {
3776 extra_data: vec![].into(),
3777 central_extra_data: vec![].into(),
3778 file_comment: None,
3779 },
3780 alignment: 43,
3781 ..Default::default()
3782 };
3783 writer.start_file_from_path(
3784 "\0\0\0\u{3}\0\u{1a}\u{1a}\u{1a}\u{1a}\u{1a}\u{1a}",
3785 options,
3786 )?;
3787 let options = FileOptions {
3788 compression_method: Stored,
3789 compression_level: None,
3790 last_modified_time: DateTime::from_date_and_time(
3791 2006, 3, 27, 2, 24, 26,
3792 )?,
3793 permissions: None,
3794 large_file: false,
3795 encrypt_with: None,
3796 extended_options: ExtendedFileOptions {
3797 extra_data: vec![].into(),
3798 central_extra_data: vec![].into(),
3799 file_comment: None,
3800 },
3801 alignment: 26,
3802 ..Default::default()
3803 };
3804 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)?;
3805 writer = ZipWriter::new_append(
3806 writer.finish_into_readable()?.into_inner(),
3807 )?;
3808 let options = FileOptions {
3809 compression_method: Stored,
3810 compression_level: Some(17),
3811 last_modified_time: DateTime::from_date_and_time(
3812 2103, 4, 10, 23, 15, 18,
3813 )?,
3814 permissions: Some(3284386755),
3815 large_file: true,
3816 encrypt_with: Some(ZipCrypto(
3817 ZipCryptoKeys::of(
3818 0x8888c5bf, 0x88888888, 0xff888888,
3819 ),
3820 PhantomData,
3821 )),
3822 extended_options: ExtendedFileOptions {
3823 extra_data: vec![3, 0, 1, 0, 255, 144, 136, 0, 0]
3824 .into(),
3825 central_extra_data: vec![].into(),
3826 file_comment: None,
3827 },
3828 alignment: 0xFFFF,
3829 ..Default::default()
3830 };
3831 writer.add_symlink_from_path("", "\nu", options)?;
3832 writer = ZipWriter::new_append(writer.finish()?)?;
3833 writer
3834 };
3835 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3836 writer = ZipWriter::new_append(
3837 writer.finish_into_readable()?.into_inner(),
3838 )?;
3839 writer
3840 };
3841 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3842 writer = ZipWriter::new_append(writer.finish()?)?;
3843 writer
3844 };
3845 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3846 writer =
3847 ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3848 writer.abort_file()?;
3849 let options = FileOptions {
3850 compression_method: CompressionMethod::Unsupported(49603),
3851 compression_level: Some(20),
3852 last_modified_time: DateTime::from_date_and_time(
3853 2047, 4, 14, 3, 15, 14,
3854 )?,
3855 permissions: Some(3284386755),
3856 large_file: true,
3857 encrypt_with: Some(ZipCrypto(
3858 ZipCryptoKeys::of(0xc3, 0x0, 0x0),
3859 PhantomData,
3860 )),
3861 extended_options: ExtendedFileOptions {
3862 extra_data: vec![].into(),
3863 central_extra_data: vec![].into(),
3864 file_comment: None,
3865 },
3866 alignment: 0,
3867 ..Default::default()
3868 };
3869 writer.add_directory_from_path("", options)?;
3870 writer.deep_copy_file_from_path("/", "")?;
3871 writer.shallow_copy_file_from_path("", "copy")?;
3872 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3873 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3874 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3875 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3876 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3877 assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
3878 writer
3879 };
3880 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3881 writer
3882 };
3883 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3884 writer
3885 };
3886 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3887 writer
3888 };
3889 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3890 writer
3891 };
3892 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3893 let _ = writer.finish_into_readable()?;
3894 Ok(())
3895 }
3896
3897 #[test]
3898 fn test_fuzz_crash_2024_06_17a() -> ZipResult<()> {
3899 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3900 writer.set_flush_on_finish_file(false);
3901 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}";
3902 let sub_writer = {
3903 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3904 writer.set_flush_on_finish_file(false);
3905 let sub_writer = {
3906 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3907 writer.set_flush_on_finish_file(false);
3908 let options = FileOptions {
3909 compression_method: Stored,
3910 compression_level: None,
3911 last_modified_time: DateTime::from_date_and_time(1981, 1, 1, 0, 24, 21)?,
3912 permissions: Some(16908288),
3913 large_file: false,
3914 encrypt_with: None,
3915 extended_options: ExtendedFileOptions {
3916 extra_data: vec![].into(),
3917 central_extra_data: vec![].into(),
3918 file_comment: None,
3919 },
3920 alignment: 20555,
3921 ..Default::default()
3922 };
3923 writer.start_file_from_path(
3924 "\0\u{7}\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{2};",
3925 options,
3926 )?;
3927 writer.write_all(
3928 &([
3929 255, 255, 255, 255, 253, 253, 253, 203, 203, 203, 253, 253, 253, 253, 255,
3930 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 249, 191, 225, 225,
3931 241, 197,
3932 ]),
3933 )?;
3934 writer.write_all(
3935 &([
3936 197, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
3937 255, 75, 0,
3938 ]),
3939 )?;
3940 writer
3941 };
3942 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3943 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3944 let options = FileOptions {
3945 compression_method: Stored,
3946 compression_level: None,
3947 last_modified_time: DateTime::from_date_and_time(1980, 11, 14, 10, 46, 47)?,
3948 permissions: None,
3949 large_file: false,
3950 encrypt_with: None,
3951 extended_options: ExtendedFileOptions {
3952 extra_data: vec![].into(),
3953 central_extra_data: vec![].into(),
3954 file_comment: None,
3955 },
3956 alignment: 0,
3957 ..Default::default()
3958 };
3959 writer.start_file_from_path(PATH_1, options)?;
3960 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}")?;
3961 writer
3962 };
3963 writer.merge_archive(sub_writer.finish_into_readable()?)?;
3964 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3965 writer.deep_copy_file_from_path(PATH_1, "")?;
3966 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3967 writer.shallow_copy_file_from_path("", "copy")?;
3968 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
3969 let _ = writer.finish_into_readable()?;
3970 Ok(())
3971 }
3972
3973 #[test]
3974 #[allow(clippy::octal_escapes)]
3975 #[cfg(all(feature = "_bzip2_any", not(miri)))]
3976 fn test_fuzz_crash_2024_06_17b() -> ZipResult<()> {
3977 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3978 writer.set_flush_on_finish_file(false);
3979 let sub_writer = {
3980 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3981 writer.set_flush_on_finish_file(false);
3982 let sub_writer = {
3983 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3984 writer.set_flush_on_finish_file(false);
3985 let sub_writer = {
3986 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3987 writer.set_flush_on_finish_file(false);
3988 let sub_writer = {
3989 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3990 writer.set_flush_on_finish_file(false);
3991 let sub_writer = {
3992 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3993 writer.set_flush_on_finish_file(false);
3994 let sub_writer = {
3995 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3996 writer.set_flush_on_finish_file(false);
3997 let sub_writer = {
3998 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
3999 writer.set_flush_on_finish_file(false);
4000 let sub_writer = {
4001 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4002 writer.set_flush_on_finish_file(false);
4003 let options = FileOptions {
4004 compression_method: Stored,
4005 compression_level: None,
4006 last_modified_time: DateTime::from_date_and_time(
4007 1981, 1, 1, 0, 0, 21,
4008 )?,
4009 permissions: Some(16908288),
4010 large_file: false,
4011 encrypt_with: None,
4012 extended_options: ExtendedFileOptions {
4013 extra_data: vec![].into(),
4014 central_extra_data: vec![].into(),
4015 file_comment: None,
4016 },
4017 alignment: 20555,
4018 ..Default::default()
4019 };
4020 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)?;
4021 writer
4022 };
4023 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4024 let options = FileOptions {
4025 compression_method: CompressionMethod::Bzip2,
4026 compression_level: Some(5),
4027 last_modified_time: DateTime::from_date_and_time(
4028 2055, 7, 7, 3, 6, 6,
4029 )?,
4030 permissions: None,
4031 large_file: false,
4032 encrypt_with: None,
4033 extended_options: ExtendedFileOptions {
4034 extra_data: vec![].into(),
4035 central_extra_data: vec![].into(),
4036 file_comment: None,
4037 },
4038 alignment: 0,
4039 ..Default::default()
4040 };
4041 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)?;
4042 writer = ZipWriter::new_append(
4043 writer.finish_into_readable()?.into_inner(),
4044 )?;
4045 writer
4046 };
4047 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4048 writer = ZipWriter::new_append(
4049 writer.finish_into_readable()?.into_inner(),
4050 )?;
4051 writer
4052 };
4053 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4054 writer =
4055 ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4056 writer
4057 };
4058 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4059 writer =
4060 ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4061 writer
4062 };
4063 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4064 writer
4065 };
4066 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4067 writer
4068 };
4069 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4070 writer
4071 };
4072 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4073 let _ = writer.finish_into_readable()?;
4074 Ok(())
4075 }
4076
4077 #[test]
4078 fn test_fuzz_crash_2024_06_18() -> ZipResult<()> {
4079 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4080 writer
4081 .set_raw_comment(Box::<[u8]>::from([
4082 80, 75, 5, 6, 255, 255, 255, 255, 255, 255, 80, 75, 5, 6, 255, 255, 255, 255, 255,
4083 255, 13, 0, 13, 13, 13, 13, 13, 255, 255, 255, 255, 255, 255, 255, 255,
4084 ]))
4085 .unwrap();
4086 let sub_writer = {
4087 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4088 writer.set_flush_on_finish_file(false);
4089 writer.set_raw_comment(Box::new([])).unwrap();
4090 writer
4091 };
4092 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4093 writer = ZipWriter::new_append(writer.finish()?)?;
4094 let _ = writer.finish_into_readable()?;
4095 Ok(())
4096 }
4097
4098 #[test]
4099 fn test_fuzz_crash_2024_06_18a() -> ZipResult<()> {
4100 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4101 writer.set_flush_on_finish_file(false);
4102 writer.set_raw_comment(Box::<[u8]>::from([])).unwrap();
4103 let sub_writer = {
4104 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4105 writer.set_flush_on_finish_file(false);
4106 let sub_writer = {
4107 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4108 writer.set_flush_on_finish_file(false);
4109 let sub_writer = {
4110 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4111 writer.set_flush_on_finish_file(false);
4112 let options = FullFileOptions {
4113 compression_method: Stored,
4114 compression_level: None,
4115 last_modified_time: DateTime::from_date_and_time(2107, 4, 8, 14, 0, 19)?,
4116 permissions: None,
4117 large_file: false,
4118 encrypt_with: None,
4119 extended_options: ExtendedFileOptions {
4120 extra_data: vec![
4121 182, 180, 1, 0, 180, 182, 74, 0, 0, 200, 0, 0, 0, 2, 0, 0, 0,
4122 ]
4123 .into(),
4124 central_extra_data: vec![].into(),
4125 file_comment: None,
4126 },
4127 alignment: 1542,
4128 ..Default::default()
4129 };
4130 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)?;
4131 let finished = writer.finish_into_readable()?;
4132 assert_eq!(1, finished.file_names().count());
4133 writer = ZipWriter::new_append(finished.into_inner())?;
4134 let options = FullFileOptions {
4135 compression_method: Stored,
4136 compression_level: Some(5),
4137 last_modified_time: DateTime::from_date_and_time(2107, 4, 1, 0, 0, 0)?,
4138 permissions: None,
4139 large_file: false,
4140 encrypt_with: Some(ZipCrypto(
4141 ZipCryptoKeys::of(0x0, 0x62e4b50, 0x100),
4142 PhantomData,
4143 )),
4144 ..Default::default()
4145 };
4146 writer.add_symlink_from_path(
4147 "\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}",
4148 "\u{8}\0\0\0\0/\0",
4149 options,
4150 )?;
4151 let finished = writer.finish_into_readable()?;
4152 assert_eq!(2, finished.file_names().count());
4153 writer = ZipWriter::new_append(finished.into_inner())?;
4154 assert_eq!(2, writer.files.len());
4155 writer
4156 };
4157 let finished = sub_writer.finish_into_readable()?;
4158 assert_eq!(2, finished.file_names().count());
4159 writer.merge_archive(finished)?;
4160 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4161 writer
4162 };
4163 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4164 writer
4165 };
4166 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4167 let _ = writer.finish_into_readable()?;
4168 Ok(())
4169 }
4170
4171 #[cfg(all(feature = "_bzip2_any", feature = "aes-crypto", not(miri)))]
4172 #[test]
4173 fn test_fuzz_crash_2024_06_18b() -> ZipResult<()> {
4174 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4175 writer.set_flush_on_finish_file(true);
4176 writer.set_raw_comment([0].into())?;
4177 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4178 assert_eq!(writer.get_raw_comment()[0], 0);
4179 let options = FileOptions {
4180 compression_method: CompressionMethod::Bzip2,
4181 compression_level: None,
4182 last_modified_time: DateTime::from_date_and_time(2009, 6, 3, 13, 37, 39)?,
4183 permissions: Some(2644352413),
4184 large_file: true,
4185 encrypt_with: Some(crate::write::EncryptWith::Aes {
4186 mode: crate::AesMode::Aes256,
4187 password: &[],
4188 salt: None,
4189 }),
4190 extended_options: ExtendedFileOptions {
4191 extra_data: vec![].into(),
4192 central_extra_data: vec![].into(),
4193 file_comment: None,
4194 },
4195 alignment: 255,
4196 ..Default::default()
4197 };
4198 writer.add_symlink_from_path("", "", options)?;
4199 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")?;
4200 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4201 assert_eq!(writer.get_raw_comment()[0], 0);
4202 writer.deep_copy_file_from_path(
4203 "PK\u{5}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\u{4}\0\0\0",
4204 "\u{2}yy\u{5}qu\0",
4205 )?;
4206 let finished = writer.finish()?;
4207 let archive = ZipArchive::new(finished.clone())?;
4208 assert_eq!(archive.comment(), [0]);
4209 writer = ZipWriter::new_append(finished)?;
4210 assert_eq!(writer.get_raw_comment()[0], 0);
4211 let _ = writer.finish_into_readable()?;
4212 Ok(())
4213 }
4214
4215 #[test]
4216 fn test_fuzz_crash_2024_06_19() -> ZipResult<()> {
4217 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4218 writer.set_flush_on_finish_file(false);
4219 let options = FileOptions {
4220 compression_method: Stored,
4221 compression_level: None,
4222 last_modified_time: DateTime::from_date_and_time(1980, 3, 1, 19, 55, 58)?,
4223 permissions: None,
4224 large_file: false,
4225 encrypt_with: None,
4226 extended_options: ExtendedFileOptions {
4227 extra_data: vec![].into(),
4228 central_extra_data: vec![].into(),
4229 file_comment: None,
4230 },
4231 alignment: 256,
4232 ..Default::default()
4233 };
4234 writer.start_file_from_path(
4235 "\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",
4236 options,
4237 )?;
4238 writer.set_flush_on_finish_file(false);
4239 writer.shallow_copy_file_from_path(
4240 "\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",
4241 "",
4242 )?;
4243 writer.set_flush_on_finish_file(false);
4244 writer.deep_copy_file_from_path("", "copy")?;
4245 writer.abort_file()?;
4246 writer.set_flush_on_finish_file(false);
4247 writer.set_raw_comment([255, 0].into())?;
4248 writer.abort_file()?;
4249 assert_eq!(writer.get_raw_comment(), [255, 0]);
4250 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4251 assert_eq!(writer.get_raw_comment(), [255, 0]);
4252 writer.set_flush_on_finish_file(false);
4253 let options = FileOptions {
4254 compression_method: Stored,
4255 compression_level: None,
4256 last_modified_time: DateTime::default(),
4257 permissions: None,
4258 large_file: false,
4259 encrypt_with: None,
4260 extended_options: ExtendedFileOptions {
4261 extra_data: vec![].into(),
4262 central_extra_data: vec![].into(),
4263 file_comment: None,
4264 },
4265 ..Default::default()
4266 };
4267 writer.start_file_from_path("", options)?;
4268 assert_eq!(writer.get_raw_comment(), [255, 0]);
4269 let archive = writer.finish_into_readable()?;
4270 assert_eq!(archive.comment(), [255, 0]);
4271 Ok(())
4272 }
4273
4274 #[test]
4275 fn fuzz_crash_2024_06_21() -> ZipResult<()> {
4276 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4277 writer.set_flush_on_finish_file(false);
4278 let options = FullFileOptions {
4279 compression_method: Stored,
4280 compression_level: None,
4281 last_modified_time: DateTime::from_date_and_time(1980, 2, 1, 0, 0, 0)?,
4282 permissions: None,
4283 large_file: false,
4284 encrypt_with: None,
4285 ..Default::default()
4286 };
4287 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";
4288 writer.start_file_from_path(LONG_PATH, options)?;
4289 writer = ZipWriter::new_append(writer.finish()?)?;
4290 writer.deep_copy_file_from_path(LONG_PATH, "oo\0\0\0")?;
4291 writer.abort_file()?;
4292 writer.set_raw_comment([33].into())?;
4293 let archive = writer.finish_into_readable()?;
4294 writer = ZipWriter::new_append(archive.into_inner())?;
4295 assert!(writer.get_raw_comment().starts_with(&[33]));
4296 let archive = writer.finish_into_readable()?;
4297 assert!(archive.comment().starts_with(&[33]));
4298 Ok(())
4299 }
4300
4301 #[test]
4302 #[cfg(all(feature = "_bzip2_any", not(miri)))]
4303 fn fuzz_crash_2024_07_17() -> ZipResult<()> {
4304 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4305 writer.set_flush_on_finish_file(false);
4306 let options = FileOptions {
4307 compression_method: CompressionMethod::Bzip2,
4308 compression_level: None,
4309 last_modified_time: DateTime::from_date_and_time(2095, 2, 16, 21, 0, 1)?,
4310 permissions: Some(84238341),
4311 large_file: true,
4312 encrypt_with: None,
4313 extended_options: ExtendedFileOptions {
4314 extra_data: vec![117, 99, 6, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 2, 0, 0, 0].into(),
4315 central_extra_data: vec![].into(),
4316 file_comment: None,
4317 },
4318 alignment: 0xFFFF,
4319 ..Default::default()
4320 };
4321 writer.start_file_from_path("", options)?;
4322 writer.deep_copy_file_from_path("", "copy")?;
4324 let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4325 Ok(())
4326 }
4327
4328 #[test]
4329 fn fuzz_crash_2024_07_19() -> ZipResult<()> {
4330 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4331 writer.set_flush_on_finish_file(false);
4332 let options = FileOptions {
4333 compression_method: Stored,
4334 compression_level: None,
4335 last_modified_time: DateTime::from_date_and_time(1980, 6, 1, 0, 34, 47)?,
4336 permissions: None,
4337 large_file: true,
4338 encrypt_with: None,
4339 extended_options: ExtendedFileOptions {
4340 extra_data: vec![].into(),
4341 central_extra_data: vec![].into(),
4342 file_comment: None,
4343 },
4344 alignment: 45232,
4345 ..Default::default()
4346 };
4347 writer.add_directory_from_path("", options)?;
4348 writer.deep_copy_file_from_path("/", "")?;
4349 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4350 writer.deep_copy_file_from_path("", "copy")?;
4351 let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4352 Ok(())
4353 }
4354
4355 #[test]
4356 #[cfg(feature = "aes-crypto")]
4357 fn fuzz_crash_2024_07_19a() -> ZipResult<()> {
4358 use crate::AesMode::Aes128;
4359 use crate::write::EncryptWith::Aes;
4360 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4361 writer.set_flush_on_finish_file(false);
4362 let options = FileOptions {
4363 compression_method: Stored,
4364 compression_level: None,
4365 last_modified_time: DateTime::from_date_and_time(2107, 6, 5, 13, 0, 21)?,
4366 permissions: None,
4367 large_file: true,
4368 encrypt_with: Some(Aes {
4369 mode: Aes128,
4370 password: &[],
4371 salt: None,
4372 }),
4373 extended_options: ExtendedFileOptions {
4374 extra_data: vec![3, 0, 4, 0, 209, 53, 53, 8, 2, 61, 0, 0].into(),
4375 central_extra_data: vec![].into(),
4376 file_comment: None,
4377 },
4378 alignment: 0xFFFF,
4379 ..Default::default()
4380 };
4381 writer.start_file_from_path("", options)?;
4382 let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4383 Ok(())
4384 }
4385
4386 #[test]
4387 fn fuzz_crash_2024_07_20() -> ZipResult<()> {
4388 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4389 writer.set_flush_on_finish_file(true);
4390 let options = FileOptions {
4391 compression_method: Stored,
4392 compression_level: None,
4393 last_modified_time: DateTime::from_date_and_time(2041, 8, 2, 19, 38, 0)?,
4394 permissions: None,
4395 large_file: false,
4396 encrypt_with: None,
4397 extended_options: ExtendedFileOptions {
4398 extra_data: vec![].into(),
4399 central_extra_data: vec![].into(),
4400 file_comment: None,
4401 },
4402 alignment: 0,
4403 ..Default::default()
4404 };
4405 writer.add_directory_from_path("\0\0\0\0\0\0\07黻", options)?;
4406 let sub_writer = {
4407 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4408 writer.set_flush_on_finish_file(false);
4409 let options = FileOptions {
4410 compression_method: Stored,
4411 compression_level: None,
4412 last_modified_time: DateTime::default(),
4413 permissions: None,
4414 large_file: false,
4415 encrypt_with: None,
4416 extended_options: ExtendedFileOptions {
4417 extra_data: vec![].into(),
4418 central_extra_data: vec![].into(),
4419 file_comment: None,
4420 },
4421 alignment: 4,
4422 ..Default::default()
4423 };
4424 writer.add_directory_from_path("\0\0\0黻", options)?;
4425 writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4426 writer.abort_file()?;
4427 let options = FileOptions {
4428 compression_method: Stored,
4429 compression_level: None,
4430 last_modified_time: DateTime::from_date_and_time(1980, 1, 1, 0, 7, 0)?,
4431 permissions: Some(2663103419),
4432 large_file: false,
4433 encrypt_with: None,
4434 extended_options: ExtendedFileOptions {
4435 extra_data: vec![].into(),
4436 central_extra_data: vec![].into(),
4437 file_comment: None,
4438 },
4439 alignment: 32256,
4440 ..Default::default()
4441 };
4442 writer.add_directory_from_path("\0", options)?;
4443 writer = ZipWriter::new_append(writer.finish()?)?;
4444 writer
4445 };
4446 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4447 let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
4448 Ok(())
4449 }
4450
4451 #[test]
4452 fn fuzz_crash_2024_07_21() -> ZipResult<()> {
4453 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4454 let sub_writer = {
4455 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4456 writer.add_directory_from_path(
4457 "",
4458 FileOptions {
4459 compression_method: Stored,
4460 compression_level: None,
4461 last_modified_time: DateTime::from_date_and_time(2105, 8, 1, 15, 0, 0)?,
4462 permissions: None,
4463 large_file: false,
4464 encrypt_with: None,
4465 extended_options: ExtendedFileOptions {
4466 extra_data: vec![].into(),
4467 central_extra_data: vec![].into(),
4468 file_comment: None,
4469 },
4470 alignment: 0,
4471 ..Default::default()
4472 },
4473 )?;
4474 writer.abort_file()?;
4475 let mut writer = ZipWriter::new_append(writer.finish()?)?;
4476 writer.add_directory_from_path(
4477 "",
4478 FileOptions {
4479 compression_method: Stored,
4480 compression_level: None,
4481 last_modified_time: DateTime::default(),
4482 permissions: None,
4483 large_file: false,
4484 encrypt_with: None,
4485 extended_options: ExtendedFileOptions {
4486 extra_data: vec![].into(),
4487 central_extra_data: vec![].into(),
4488 file_comment: None,
4489 },
4490 alignment: 16,
4491 ..Default::default()
4492 },
4493 )?;
4494 ZipWriter::new_append(writer.finish()?)?
4495 };
4496 writer.merge_archive(sub_writer.finish_into_readable()?)?;
4497 let writer = ZipWriter::new_append(writer.finish()?)?;
4498 let _ = writer.finish_into_readable()?;
4499
4500 Ok(())
4501 }
4502
4503 #[test]
4504 fn test_explicit_system_roundtrip() -> ZipResult<()> {
4505 use crate::read::HasZipMetadata;
4506 let systems = vec![System::Unix, System::Dos, System::WindowsNTFS];
4508
4509 for system in systems {
4510 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4511 let options = SimpleFileOptions::default()
4512 .compression_method(Stored)
4513 .system(system);
4514
4515 let filename = format!("test_{:?}.txt", system);
4516 writer.start_file(&filename, options)?;
4517 writer.write_all(b"content")?;
4518
4519 let bytes = writer.finish()?.into_inner();
4521 let mut reader = ZipArchive::new(Cursor::new(bytes))?;
4522
4523 let file = reader.by_index(0)?;
4524 assert_eq!(
4525 file.get_metadata().system,
4526 system,
4527 "System mismatch for {:?}",
4528 system
4529 );
4530 }
4531
4532 Ok(())
4533 }
4534
4535 #[test]
4536 fn test_system_default_behavior() -> ZipResult<()> {
4537 use crate::read::HasZipMetadata;
4538 let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
4540 let options = SimpleFileOptions::default().compression_method(Stored);
4541
4542 writer.start_file("test.txt", options)?;
4543 writer.write_all(b"test")?;
4544
4545 let bytes = writer.finish()?.into_inner();
4546 let mut reader = ZipArchive::new(Cursor::new(bytes))?;
4547
4548 let file = reader.by_index(0)?;
4549 let expected_system = if cfg!(windows) {
4550 System::Dos
4551 } else {
4552 System::Unix
4553 };
4554 assert_eq!(file.get_metadata().system, expected_system);
4555
4556 Ok(())
4557 }
4558}