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