Skip to main content

zip/
write.rs

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