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