Skip to main content

zip/
write.rs

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