zip/
write.rs

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