zip/
write.rs

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