zip/
write.rs

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