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