zip_merge/
write.rs

1//! Types for creating ZIP archives
2
3use crate::compression::CompressionMethod;
4use crate::read::{central_header_to_zip_file, ZipArchive, ZipFile};
5use crate::result::{ZipError, ZipResult};
6use crate::spec;
7use crate::types::{AtomicU64, DateTime, System, ZipFileData, DEFAULT_VERSION};
8use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
9use cfg_if::cfg_if;
10use crc32fast::Hasher;
11use std::convert::TryInto;
12use std::default::Default;
13use std::io;
14use std::io::prelude::*;
15use std::mem;
16
17#[cfg(any(
18    feature = "deflate",
19    feature = "deflate-miniz",
20    feature = "deflate-zlib"
21))]
22use flate2::write::DeflateEncoder;
23
24#[cfg(feature = "bzip2")]
25use bzip2::write::BzEncoder;
26
27#[cfg(feature = "time")]
28use time::OffsetDateTime;
29
30#[cfg(feature = "zstd")]
31use zstd::stream::write::Encoder as ZstdEncoder;
32
33enum MaybeEncrypted<W> {
34    Unencrypted(W),
35    Encrypted(crate::zipcrypto::ZipCryptoWriter<W>),
36}
37impl<W: Write> Write for MaybeEncrypted<W> {
38    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
39        match self {
40            MaybeEncrypted::Unencrypted(w) => w.write(buf),
41            MaybeEncrypted::Encrypted(w) => w.write(buf),
42        }
43    }
44    fn flush(&mut self) -> io::Result<()> {
45        match self {
46            MaybeEncrypted::Unencrypted(w) => w.flush(),
47            MaybeEncrypted::Encrypted(w) => w.flush(),
48        }
49    }
50}
51enum GenericZipWriter<W: Write + io::Seek> {
52    Closed,
53    Storer(MaybeEncrypted<W>),
54    #[cfg(any(
55        feature = "deflate",
56        feature = "deflate-miniz",
57        feature = "deflate-zlib"
58    ))]
59    Deflater(DeflateEncoder<MaybeEncrypted<W>>),
60    #[cfg(feature = "bzip2")]
61    Bzip2(BzEncoder<MaybeEncrypted<W>>),
62    #[cfg(feature = "zstd")]
63    Zstd(ZstdEncoder<'static, MaybeEncrypted<W>>),
64}
65// Put the struct declaration in a private module to convince rustdoc to display ZipWriter nicely
66pub(crate) mod zip_writer {
67    use super::*;
68    /// ZIP archive generator
69    ///
70    /// Handles the bookkeeping involved in building an archive, and provides an
71    /// API to edit its contents.
72    ///
73    /// ```
74    /// # fn doit() -> zip::result::ZipResult<()>
75    /// # {
76    /// # use zip::ZipWriter;
77    /// use std::io::Write;
78    /// use zip::write::FileOptions;
79    ///
80    /// // We use a buffer here, though you'd normally use a `File`
81    /// let mut buf = [0; 65536];
82    /// let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf[..]));
83    ///
84    /// let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
85    /// zip.start_file("hello_world.txt", options)?;
86    /// zip.write(b"Hello, World!")?;
87    ///
88    /// // Apply the changes you've made.
89    /// // Dropping the `ZipWriter` will have the same effect, but may silently fail
90    /// zip.finish()?;
91    ///
92    /// # Ok(())
93    /// # }
94    /// # doit().unwrap();
95    /// ```
96    pub struct ZipWriter<W: Write + io::Seek> {
97        pub(super) inner: GenericZipWriter<W>,
98        pub(super) files: Vec<ZipFileData>,
99        pub(super) stats: ZipWriterStats,
100        pub(super) writing_to_file: bool,
101        pub(super) writing_to_extra_field: bool,
102        pub(super) writing_to_central_extra_field_only: bool,
103        pub(super) writing_raw: bool,
104        pub(super) comment: Vec<u8>,
105    }
106}
107pub use zip_writer::ZipWriter;
108
109#[derive(Default)]
110struct ZipWriterStats {
111    hasher: Hasher,
112    start: u64,
113    bytes_written: u64,
114}
115
116struct ZipRawValues {
117    crc32: u32,
118    compressed_size: u64,
119    uncompressed_size: u64,
120}
121
122/// Metadata for a file to be written
123#[derive(Copy, Clone)]
124pub struct FileOptions {
125    compression_method: CompressionMethod,
126    compression_level: Option<i32>,
127    last_modified_time: DateTime,
128    permissions: Option<u32>,
129    large_file: bool,
130    encrypt_with: Option<crate::zipcrypto::ZipCryptoKeys>,
131}
132
133impl FileOptions {
134    /// Set the compression method for the new file
135    ///
136    /// The default is `CompressionMethod::Deflated`. If the deflate compression feature is
137    /// disabled, `CompressionMethod::Stored` becomes the default.
138    #[must_use]
139    pub fn compression_method(mut self, method: CompressionMethod) -> FileOptions {
140        self.compression_method = method;
141        self
142    }
143
144    /// Set the compression level for the new file
145    ///
146    /// `None` value specifies default compression level.
147    ///
148    /// Range of values depends on compression method:
149    /// * `Deflated`: 0 - 9. Default is 6
150    /// * `Bzip2`: 0 - 9. Default is 6
151    /// * `Zstd`: -7 - 22, with zero being mapped to default level. Default is 3
152    /// * others: only `None` is allowed
153    #[must_use]
154    pub fn compression_level(mut self, level: Option<i32>) -> FileOptions {
155        self.compression_level = level;
156        self
157    }
158
159    /// Set the last modified time
160    ///
161    /// The default is the current timestamp if the 'time' feature is enabled, and 1980-01-01
162    /// otherwise
163    #[must_use]
164    pub fn last_modified_time(mut self, mod_time: DateTime) -> FileOptions {
165        self.last_modified_time = mod_time;
166        self
167    }
168
169    /// Set the permissions for the new file.
170    ///
171    /// The format is represented with unix-style permissions.
172    /// The default is `0o644`, which represents `rw-r--r--` for files,
173    /// and `0o755`, which represents `rwxr-xr-x` for directories.
174    ///
175    /// This method only preserves the file permissions bits (via a `& 0o777`) and discards
176    /// higher file mode bits. So it cannot be used to denote an entry as a directory,
177    /// symlink, or other special file type.
178    #[must_use]
179    pub fn unix_permissions(mut self, mode: u32) -> FileOptions {
180        self.permissions = Some(mode & 0o777);
181        self
182    }
183
184    /// Set whether the new file's compressed and uncompressed size is less than 4 GiB.
185    ///
186    /// If set to `false` and the file exceeds the limit, an I/O error is thrown. If set to `true`,
187    /// readers will require ZIP64 support and if the file does not exceed the limit, 20 B are
188    /// wasted. The default is `false`.
189    #[must_use]
190    pub fn large_file(mut self, large: bool) -> FileOptions {
191        self.large_file = large;
192        self
193    }
194    pub(crate) fn with_deprecated_encryption(mut self, password: &[u8]) -> FileOptions {
195        self.encrypt_with = Some(crate::zipcrypto::ZipCryptoKeys::derive(password));
196        self
197    }
198}
199
200impl Default for FileOptions {
201    /// Construct a new FileOptions object
202    fn default() -> Self {
203        cfg_if! {
204                if #[cfg(any(
205                    feature = "deflate",
206                    feature = "deflate-miniz",
207                    feature = "deflate-zlib"
208                ))] {
209                    let compression_method = CompressionMethod::Deflated;
210                } else {
211                    let compression_method = CompressionMethod::Stored;
212                }
213        }
214        cfg_if! {
215            if #[cfg(feature = "time")] {
216                let last_modified_time = OffsetDateTime::now_utc().try_into().unwrap_or_default();
217            } else {
218                let last_modified_time = DateTime::default();
219            }
220        }
221        Self {
222            compression_method,
223            compression_level: None,
224            last_modified_time,
225            permissions: None,
226            large_file: false,
227            encrypt_with: None,
228        }
229    }
230}
231
232impl<W: Write + io::Seek> Write for ZipWriter<W> {
233    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
234        if !self.writing_to_file {
235            return Err(io::Error::new(
236                io::ErrorKind::Other,
237                "No file has been started",
238            ));
239        }
240        match self.inner.ref_mut() {
241            Some(ref mut w) => {
242                if self.writing_to_extra_field {
243                    self.files.last_mut().unwrap().extra_field.write(buf)
244                } else {
245                    let write_result = w.write(buf);
246                    if let Ok(count) = write_result {
247                        self.stats.update(&buf[0..count]);
248                        if self.stats.bytes_written > spec::ZIP64_BYTES_THR
249                            && !self.files.last_mut().unwrap().large_file
250                        {
251                            let _inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
252                            return Err(io::Error::new(
253                                io::ErrorKind::Other,
254                                "Large file option has not been set",
255                            ));
256                        }
257                    }
258                    write_result
259                }
260            }
261            None => Err(io::Error::new(
262                io::ErrorKind::BrokenPipe,
263                "ZipWriter was already closed",
264            )),
265        }
266    }
267
268    fn flush(&mut self) -> io::Result<()> {
269        match self.inner.ref_mut() {
270            Some(ref mut w) => w.flush(),
271            None => Err(io::Error::new(
272                io::ErrorKind::BrokenPipe,
273                "ZipWriter was already closed",
274            )),
275        }
276    }
277}
278
279impl ZipWriterStats {
280    fn update(&mut self, buf: &[u8]) {
281        self.hasher.update(buf);
282        self.bytes_written += buf.len() as u64;
283    }
284}
285
286impl<A: Read + Write + io::Seek> ZipWriter<A> {
287    /// Initializes the archive from an existing ZIP archive, making it ready for append.
288    pub fn new_append(mut readwriter: A) -> ZipResult<ZipWriter<A>> {
289        let (footer, cde_end_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut readwriter)?;
290
291        if footer.disk_number != footer.disk_with_central_directory {
292            return Err(ZipError::UnsupportedArchive(
293                "Support for multi-disk files is not implemented",
294            ));
295        }
296
297        let (archive_offset, directory_start, number_of_files) =
298            ZipArchive::get_directory_counts(&mut readwriter, &footer, cde_end_pos)?;
299
300        if readwriter
301            .seek(io::SeekFrom::Start(directory_start))
302            .is_err()
303        {
304            return Err(ZipError::InvalidArchive(
305                "Could not seek to start of central directory",
306            ));
307        }
308
309        let files = (0..number_of_files)
310            .map(|_| central_header_to_zip_file(&mut readwriter, archive_offset))
311            .collect::<Result<Vec<_>, _>>()?;
312
313        let _ = readwriter.seek(io::SeekFrom::Start(directory_start)); // seek directory_start to overwrite it
314
315        Ok(ZipWriter {
316            inner: GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(readwriter)),
317            files,
318            stats: Default::default(),
319            writing_to_file: false,
320            writing_to_extra_field: false,
321            writing_to_central_extra_field_only: false,
322            comment: footer.zip_file_comment,
323            writing_raw: true, // avoid recomputing the last file's header
324        })
325    }
326
327    /// Write the zip file into the backing stream, then produce a readable archive of that data.
328    ///
329    /// This method avoids parsing the central directory records at the end of the stream for
330    /// a slight performance improvement over running [`ZipArchive::new()`] on the output of
331    /// [`Self::finish()`].
332    #[cfg(feature = "merge")]
333    #[cfg_attr(docsrs, doc(cfg(feature = "merge")))]
334    pub fn finish_into_readable(&mut self) -> ZipResult<ZipArchive<A>> {
335        let cde_start = self.finalize()?;
336        let inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
337        let comment = mem::take(&mut self.comment);
338        let files = mem::take(&mut self.files);
339        let archive = ZipArchive::from_finalized_writer(cde_start, files, comment, inner.unwrap());
340        Ok(archive)
341    }
342}
343
344impl<W: Write + io::Seek> ZipWriter<W> {
345    /// Initializes the archive.
346    ///
347    /// Before writing to this object, the [`ZipWriter::start_file`] function should be called.
348    pub fn new(inner: W) -> ZipWriter<W> {
349        ZipWriter {
350            inner: GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(inner)),
351            files: Vec::new(),
352            stats: Default::default(),
353            writing_to_file: false,
354            writing_to_extra_field: false,
355            writing_to_central_extra_field_only: false,
356            writing_raw: false,
357            comment: Vec::new(),
358        }
359    }
360
361    /// Set ZIP archive comment.
362    pub fn set_comment<S>(&mut self, comment: S)
363    where
364        S: Into<String>,
365    {
366        self.set_raw_comment(comment.into().into())
367    }
368
369    /// Set ZIP archive comment.
370    ///
371    /// This sets the raw bytes of the comment. The comment
372    /// is typically expected to be encoded in UTF-8
373    pub fn set_raw_comment(&mut self, comment: Vec<u8>) {
374        self.comment = comment;
375    }
376
377    /// Start a new file for with the requested options.
378    fn start_entry<S>(
379        &mut self,
380        name: S,
381        options: FileOptions,
382        raw_values: Option<ZipRawValues>,
383    ) -> ZipResult<()>
384    where
385        S: Into<String>,
386    {
387        self.finish_file()?;
388
389        let raw_values = raw_values.unwrap_or(ZipRawValues {
390            crc32: 0,
391            compressed_size: 0,
392            uncompressed_size: 0,
393        });
394
395        {
396            let writer = self.inner.get_plain();
397            let header_start = writer.stream_position()?;
398
399            let permissions = options.permissions.unwrap_or(0o100644);
400            let mut file = ZipFileData {
401                system: System::Unix,
402                version_made_by: DEFAULT_VERSION,
403                encrypted: options.encrypt_with.is_some(),
404                using_data_descriptor: false,
405                compression_method: options.compression_method,
406                compression_level: options.compression_level,
407                last_modified_time: options.last_modified_time,
408                crc32: raw_values.crc32,
409                compressed_size: raw_values.compressed_size,
410                uncompressed_size: raw_values.uncompressed_size,
411                file_name: name.into(),
412                file_name_raw: Vec::new(), // Never used for saving
413                extra_field: Vec::new(),
414                file_comment: String::new(),
415                header_start,
416                data_start: AtomicU64::new(0),
417                central_header_start: 0,
418                external_attributes: permissions << 16,
419                large_file: options.large_file,
420                aes_mode: None,
421            };
422            write_local_file_header(writer, &file)?;
423
424            let header_end = writer.stream_position()?;
425            self.stats.start = header_end;
426            *file.data_start.get_mut() = header_end;
427
428            self.stats.bytes_written = 0;
429            self.stats.hasher = Hasher::new();
430
431            self.files.push(file);
432        }
433        if let Some(keys) = options.encrypt_with {
434            let mut zipwriter = crate::zipcrypto::ZipCryptoWriter {
435                writer: core::mem::replace(&mut self.inner, GenericZipWriter::Closed).unwrap(),
436                buffer: vec![],
437                keys,
438            };
439            let crypto_header = [0u8; 12];
440
441            zipwriter.write_all(&crypto_header)?;
442            self.inner = GenericZipWriter::Storer(MaybeEncrypted::Encrypted(zipwriter));
443        }
444        Ok(())
445    }
446
447    fn finish_file(&mut self) -> ZipResult<()> {
448        if self.writing_to_extra_field {
449            // Implicitly calling [`ZipWriter::end_extra_data`] for empty files.
450            self.end_extra_data()?;
451        }
452        self.inner.switch_to(CompressionMethod::Stored, None)?;
453        match core::mem::replace(&mut self.inner, GenericZipWriter::Closed) {
454            GenericZipWriter::Storer(MaybeEncrypted::Encrypted(writer)) => {
455                let crc32 = self.stats.hasher.clone().finalize();
456                self.inner =
457                    GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(writer.finish(crc32)?))
458            }
459            GenericZipWriter::Storer(w) => self.inner = GenericZipWriter::Storer(w),
460            _ => unreachable!(),
461        }
462        let writer = self.inner.get_plain();
463
464        if !self.writing_raw {
465            let file = match self.files.last_mut() {
466                None => return Ok(()),
467                Some(f) => f,
468            };
469            file.crc32 = self.stats.hasher.clone().finalize();
470            file.uncompressed_size = self.stats.bytes_written;
471
472            let file_end = writer.stream_position()?;
473            file.compressed_size = file_end - self.stats.start;
474
475            update_local_file_header(writer, file)?;
476            writer.seek(io::SeekFrom::Start(file_end))?;
477        }
478
479        self.writing_to_file = false;
480        self.writing_raw = false;
481        Ok(())
482    }
483
484    /// Copy over the entire contents of another archive verbatim.
485    ///
486    /// This method extracts file metadata from the `source` archive, then simply performs a single
487    /// big [`io::copy()`](io::copy) to transfer all the actual file contents without any
488    /// decompression or decryption.
489    #[cfg(feature = "merge")]
490    #[cfg_attr(docsrs, doc(cfg(feature = "merge")))]
491    pub fn merge_archive<R>(&mut self, mut source: ZipArchive<R>) -> ZipResult<()>
492    where
493        R: Read + io::Seek,
494    {
495        self.finish_file()?;
496
497        /* Ensure we accept the file contents on faith (and avoid overwriting the data).
498         * See raw_copy_file_rename(). */
499        self.writing_to_file = true;
500        self.writing_raw = true;
501
502        let writer = self.inner.get_plain();
503        /* Get the file entries from the source archive. */
504        let new_files = source.merge_contents(writer)?;
505        /* These file entries are now ours! */
506        self.files
507            .extend(new_files.into_iter().map(|(_, entry)| entry));
508
509        Ok(())
510    }
511
512    /// Create a file in the archive and start writing its' contents.
513    ///
514    /// The data should be written using the [`io::Write`] implementation on this [`ZipWriter`]
515    pub fn start_file<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
516    where
517        S: Into<String>,
518    {
519        if options.permissions.is_none() {
520            options.permissions = Some(0o644);
521        }
522        *options.permissions.as_mut().unwrap() |= 0o100000;
523        self.start_entry(name, options, None)?;
524        self.inner
525            .switch_to(options.compression_method, options.compression_level)?;
526        self.writing_to_file = true;
527        Ok(())
528    }
529
530    /// Starts a file, taking a Path as argument.
531    ///
532    /// This function ensures that the '/' path separator is used. It also ignores all non 'Normal'
533    /// Components, such as a starting '/' or '..' and '.'.
534    #[deprecated(
535        since = "0.5.7",
536        note = "by stripping `..`s from the path, the meaning of paths can change. Use `start_file` instead."
537    )]
538    pub fn start_file_from_path(
539        &mut self,
540        path: &std::path::Path,
541        options: FileOptions,
542    ) -> ZipResult<()> {
543        self.start_file(path_to_string(path), options)
544    }
545
546    /// Create an aligned file in the archive and start writing its' contents.
547    ///
548    /// Returns the number of padding bytes required to align the file.
549    ///
550    /// The data should be written using the [`io::Write`] implementation on this [`ZipWriter`]
551    pub fn start_file_aligned<S>(
552        &mut self,
553        name: S,
554        options: FileOptions,
555        align: u16,
556    ) -> Result<u64, ZipError>
557    where
558        S: Into<String>,
559    {
560        let data_start = self.start_file_with_extra_data(name, options)?;
561        let align = align as u64;
562        if align > 1 && data_start % align != 0 {
563            let pad_length = (align - (data_start + 4) % align) % align;
564            let pad = vec![0; pad_length as usize];
565            self.write_all(b"za").map_err(ZipError::from)?; // 0x617a
566            self.write_u16::<LittleEndian>(pad.len() as u16)
567                .map_err(ZipError::from)?;
568            self.write_all(&pad).map_err(ZipError::from)?;
569            assert_eq!(self.end_local_start_central_extra_data()? % align, 0);
570        }
571        let extra_data_end = self.end_extra_data()?;
572        Ok(extra_data_end - data_start)
573    }
574
575    /// Create a file in the archive and start writing its extra data first.
576    ///
577    /// Finish writing extra data and start writing file data with [`ZipWriter::end_extra_data`].
578    /// Optionally, distinguish local from central extra data with
579    /// [`ZipWriter::end_local_start_central_extra_data`].
580    ///
581    /// Returns the preliminary starting offset of the file data without any extra data allowing to
582    /// align the file data by calculating a pad length to be prepended as part of the extra data.
583    ///
584    /// The data should be written using the [`io::Write`] implementation on this [`ZipWriter`]
585    ///
586    /// ```
587    /// use byteorder::{LittleEndian, WriteBytesExt};
588    /// use zip::{ZipArchive, ZipWriter, result::ZipResult};
589    /// use zip::{write::FileOptions, CompressionMethod};
590    /// use std::io::{Write, Cursor};
591    ///
592    /// # fn main() -> ZipResult<()> {
593    /// let mut archive = Cursor::new(Vec::new());
594    ///
595    /// {
596    ///     let mut zip = ZipWriter::new(&mut archive);
597    ///     let options = FileOptions::default()
598    ///         .compression_method(CompressionMethod::Stored);
599    ///
600    ///     zip.start_file_with_extra_data("identical_extra_data.txt", options)?;
601    ///     let extra_data = b"local and central extra data";
602    ///     zip.write_u16::<LittleEndian>(0xbeef)?;
603    ///     zip.write_u16::<LittleEndian>(extra_data.len() as u16)?;
604    ///     zip.write_all(extra_data)?;
605    ///     zip.end_extra_data()?;
606    ///     zip.write_all(b"file data")?;
607    ///
608    ///     let data_start = zip.start_file_with_extra_data("different_extra_data.txt", options)?;
609    ///     let extra_data = b"local extra data";
610    ///     zip.write_u16::<LittleEndian>(0xbeef)?;
611    ///     zip.write_u16::<LittleEndian>(extra_data.len() as u16)?;
612    ///     zip.write_all(extra_data)?;
613    ///     let data_start = data_start as usize + 4 + extra_data.len() + 4;
614    ///     let align = 64;
615    ///     let pad_length = (align - data_start % align) % align;
616    ///     assert_eq!(pad_length, 19);
617    ///     zip.write_u16::<LittleEndian>(0xdead)?;
618    ///     zip.write_u16::<LittleEndian>(pad_length as u16)?;
619    ///     zip.write_all(&vec![0; pad_length])?;
620    ///     let data_start = zip.end_local_start_central_extra_data()?;
621    ///     assert_eq!(data_start as usize % align, 0);
622    ///     let extra_data = b"central extra data";
623    ///     zip.write_u16::<LittleEndian>(0xbeef)?;
624    ///     zip.write_u16::<LittleEndian>(extra_data.len() as u16)?;
625    ///     zip.write_all(extra_data)?;
626    ///     zip.end_extra_data()?;
627    ///     zip.write_all(b"file data")?;
628    ///
629    ///     zip.finish()?;
630    /// }
631    ///
632    /// let mut zip = ZipArchive::new(archive)?;
633    /// assert_eq!(&zip.by_index(0)?.extra_data()[4..], b"local and central extra data");
634    /// assert_eq!(&zip.by_index(1)?.extra_data()[4..], b"central extra data");
635    /// # Ok(())
636    /// # }
637    /// ```
638    pub fn start_file_with_extra_data<S>(
639        &mut self,
640        name: S,
641        mut options: FileOptions,
642    ) -> ZipResult<u64>
643    where
644        S: Into<String>,
645    {
646        if options.permissions.is_none() {
647            options.permissions = Some(0o644);
648        }
649        *options.permissions.as_mut().unwrap() |= 0o100000;
650        self.start_entry(name, options, None)?;
651        self.writing_to_file = true;
652        self.writing_to_extra_field = true;
653        Ok(self.files.last().unwrap().data_start.load())
654    }
655
656    /// End local and start central extra data. Requires [`ZipWriter::start_file_with_extra_data`].
657    ///
658    /// Returns the final starting offset of the file data.
659    pub fn end_local_start_central_extra_data(&mut self) -> ZipResult<u64> {
660        let data_start = self.end_extra_data()?;
661        self.files.last_mut().unwrap().extra_field.clear();
662        self.writing_to_extra_field = true;
663        self.writing_to_central_extra_field_only = true;
664        Ok(data_start)
665    }
666
667    /// End extra data and start file data. Requires [`ZipWriter::start_file_with_extra_data`].
668    ///
669    /// Returns the final starting offset of the file data.
670    pub fn end_extra_data(&mut self) -> ZipResult<u64> {
671        // Require `start_file_with_extra_data()`. Ensures `file` is some.
672        if !self.writing_to_extra_field {
673            return Err(ZipError::Io(io::Error::new(
674                io::ErrorKind::Other,
675                "Not writing to extra field",
676            )));
677        }
678        let file = self.files.last_mut().unwrap();
679
680        validate_extra_data(file)?;
681
682        let data_start = file.data_start.get_mut();
683
684        if !self.writing_to_central_extra_field_only {
685            let writer = self.inner.get_plain();
686
687            // Append extra data to local file header and keep it for central file header.
688            writer.write_all(&file.extra_field)?;
689
690            // Update final `data_start`.
691            let header_end = *data_start + file.extra_field.len() as u64;
692            self.stats.start = header_end;
693            *data_start = header_end;
694
695            // Update extra field length in local file header.
696            let extra_field_length =
697                if file.large_file { 20 } else { 0 } + file.extra_field.len() as u16;
698            writer.seek(io::SeekFrom::Start(file.header_start + 28))?;
699            writer.write_u16::<LittleEndian>(extra_field_length)?;
700            writer.seek(io::SeekFrom::Start(header_end))?;
701
702            self.inner
703                .switch_to(file.compression_method, file.compression_level)?;
704        }
705
706        self.writing_to_extra_field = false;
707        self.writing_to_central_extra_field_only = false;
708        Ok(*data_start)
709    }
710
711    /// Add a new file using the already compressed data from a ZIP file being read and renames it, this
712    /// allows faster copies of the `ZipFile` since there is no need to decompress and compress it again.
713    /// Any `ZipFile` metadata is copied and not checked, for example the file CRC.
714
715    /// ```no_run
716    /// use std::fs::File;
717    /// use std::io::{Read, Seek, Write};
718    /// use zip::{ZipArchive, ZipWriter};
719    ///
720    /// fn copy_rename<R, W>(
721    ///     src: &mut ZipArchive<R>,
722    ///     dst: &mut ZipWriter<W>,
723    /// ) -> zip::result::ZipResult<()>
724    /// where
725    ///     R: Read + Seek,
726    ///     W: Write + Seek,
727    /// {
728    ///     // Retrieve file entry by name
729    ///     let file = src.by_name("src_file.txt")?;
730    ///
731    ///     // Copy and rename the previously obtained file entry to the destination zip archive
732    ///     dst.raw_copy_file_rename(file, "new_name.txt")?;
733    ///
734    ///     Ok(())
735    /// }
736    /// ```
737    pub fn raw_copy_file_rename<S>(&mut self, mut file: ZipFile, name: S) -> ZipResult<()>
738    where
739        S: Into<String>,
740    {
741        let mut options = FileOptions::default()
742            .large_file(file.compressed_size().max(file.size()) > spec::ZIP64_BYTES_THR)
743            .last_modified_time(file.last_modified())
744            .compression_method(file.compression());
745        if let Some(perms) = file.unix_mode() {
746            options = options.unix_permissions(perms);
747        }
748
749        let raw_values = ZipRawValues {
750            crc32: file.crc32(),
751            compressed_size: file.compressed_size(),
752            uncompressed_size: file.size(),
753        };
754
755        self.start_entry(name, options, Some(raw_values))?;
756        self.writing_to_file = true;
757        self.writing_raw = true;
758
759        io::copy(file.get_raw_reader(), self)?;
760
761        Ok(())
762    }
763
764    /// Add a new file using the already compressed data from a ZIP file being read, this allows faster
765    /// copies of the `ZipFile` since there is no need to decompress and compress it again. Any `ZipFile`
766    /// metadata is copied and not checked, for example the file CRC.
767    ///
768    /// ```no_run
769    /// use std::fs::File;
770    /// use std::io::{Read, Seek, Write};
771    /// use zip::{ZipArchive, ZipWriter};
772    ///
773    /// fn copy<R, W>(src: &mut ZipArchive<R>, dst: &mut ZipWriter<W>) -> zip::result::ZipResult<()>
774    /// where
775    ///     R: Read + Seek,
776    ///     W: Write + Seek,
777    /// {
778    ///     // Retrieve file entry by name
779    ///     let file = src.by_name("src_file.txt")?;
780    ///
781    ///     // Copy the previously obtained file entry to the destination zip archive
782    ///     dst.raw_copy_file(file)?;
783    ///
784    ///     Ok(())
785    /// }
786    /// ```
787    pub fn raw_copy_file(&mut self, file: ZipFile) -> ZipResult<()> {
788        let name = file.name().to_owned();
789        self.raw_copy_file_rename(file, name)
790    }
791
792    /// Add a directory entry.
793    ///
794    /// As directories have no content, you must not call [`ZipWriter::write`] before adding a new file.
795    pub fn add_directory<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
796    where
797        S: Into<String>,
798    {
799        if options.permissions.is_none() {
800            options.permissions = Some(0o755);
801        }
802        *options.permissions.as_mut().unwrap() |= 0o40000;
803        options.compression_method = CompressionMethod::Stored;
804
805        let name_as_string = name.into();
806        // Append a slash to the filename if it does not end with it.
807        let name_with_slash = match name_as_string.chars().last() {
808            Some('/') | Some('\\') => name_as_string,
809            _ => name_as_string + "/",
810        };
811
812        self.start_entry(name_with_slash, options, None)?;
813        self.writing_to_file = false;
814        Ok(())
815    }
816
817    /// Add a directory entry, taking a Path as argument.
818    ///
819    /// This function ensures that the '/' path separator is used. It also ignores all non 'Normal'
820    /// Components, such as a starting '/' or '..' and '.'.
821    #[deprecated(
822        since = "0.5.7",
823        note = "by stripping `..`s from the path, the meaning of paths can change. Use `add_directory` instead."
824    )]
825    pub fn add_directory_from_path(
826        &mut self,
827        path: &std::path::Path,
828        options: FileOptions,
829    ) -> ZipResult<()> {
830        self.add_directory(path_to_string(path), options)
831    }
832
833    /// Finish the last file and write all other zip-structures
834    ///
835    /// This will return the writer, but one should normally not append any data to the end of the file.
836    /// Note that the zipfile will also be finished on drop.
837    pub fn finish(&mut self) -> ZipResult<W> {
838        let _ = self.finalize()?;
839        let inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
840        Ok(inner.unwrap())
841    }
842
843    /// Add a symlink entry.
844    ///
845    /// The zip archive will contain an entry for path `name` which is a symlink to `target`.
846    ///
847    /// No validation or normalization of the paths is performed. For best results,
848    /// callers should normalize `\` to `/` and ensure symlinks are relative to other
849    /// paths within the zip archive.
850    ///
851    /// WARNING: not all zip implementations preserve symlinks on extract. Some zip
852    /// implementations may materialize a symlink as a regular file, possibly with the
853    /// content incorrectly set to the symlink target. For maximum portability, consider
854    /// storing a regular file instead.
855    pub fn add_symlink<N, T>(
856        &mut self,
857        name: N,
858        target: T,
859        mut options: FileOptions,
860    ) -> ZipResult<()>
861    where
862        N: Into<String>,
863        T: Into<String>,
864    {
865        if options.permissions.is_none() {
866            options.permissions = Some(0o777);
867        }
868        *options.permissions.as_mut().unwrap() |= 0o120000;
869        // The symlink target is stored as file content. And compressing the target path
870        // likely wastes space. So always store.
871        options.compression_method = CompressionMethod::Stored;
872
873        self.start_entry(name, options, None)?;
874        self.writing_to_file = true;
875        self.write_all(target.into().as_bytes())?;
876        self.writing_to_file = false;
877
878        Ok(())
879    }
880
881    fn finalize(&mut self) -> ZipResult<u64> {
882        self.finish_file()?;
883
884        let central_start = {
885            let writer = self.inner.get_plain();
886
887            let central_start = writer.stream_position()?;
888            for file in self.files.iter() {
889                write_central_directory_header(writer, file)?;
890            }
891            let central_size = writer.stream_position()? - central_start;
892
893            if self.files.len() > spec::ZIP64_ENTRY_THR
894                || central_size.max(central_start) > spec::ZIP64_BYTES_THR
895            {
896                let zip64_footer = spec::Zip64CentralDirectoryEnd {
897                    version_made_by: DEFAULT_VERSION as u16,
898                    version_needed_to_extract: DEFAULT_VERSION as u16,
899                    disk_number: 0,
900                    disk_with_central_directory: 0,
901                    number_of_files_on_this_disk: self.files.len() as u64,
902                    number_of_files: self.files.len() as u64,
903                    central_directory_size: central_size,
904                    central_directory_offset: central_start,
905                };
906
907                zip64_footer.write(writer)?;
908
909                let zip64_footer = spec::Zip64CentralDirectoryEndLocator {
910                    disk_with_central_directory: 0,
911                    end_of_central_directory_offset: central_start + central_size,
912                    number_of_disks: 1,
913                };
914
915                zip64_footer.write(writer)?;
916            }
917
918            let number_of_files = self.files.len().min(spec::ZIP64_ENTRY_THR) as u16;
919            let footer = spec::CentralDirectoryEnd {
920                disk_number: 0,
921                disk_with_central_directory: 0,
922                zip_file_comment: self.comment.clone(),
923                number_of_files_on_this_disk: number_of_files,
924                number_of_files,
925                central_directory_size: central_size.min(spec::ZIP64_BYTES_THR) as u32,
926                central_directory_offset: central_start.min(spec::ZIP64_BYTES_THR) as u32,
927            };
928
929            footer.write(writer)?;
930
931            central_start
932        };
933
934        Ok(central_start)
935    }
936}
937
938impl<W: Write + io::Seek> Drop for ZipWriter<W> {
939    fn drop(&mut self) {
940        if !self.inner.is_closed() {
941            if let Err(e) = self.finalize() {
942                let _ = write!(io::stderr(), "ZipWriter drop failed: {e:?}");
943            }
944        }
945    }
946}
947
948impl<W: Write + io::Seek> GenericZipWriter<W> {
949    fn switch_to(
950        &mut self,
951        compression: CompressionMethod,
952        compression_level: Option<i32>,
953    ) -> ZipResult<()> {
954        match self.current_compression() {
955            Some(method) if method == compression => return Ok(()),
956            None => {
957                return Err(io::Error::new(
958                    io::ErrorKind::BrokenPipe,
959                    "ZipWriter was already closed",
960                )
961                .into())
962            }
963            _ => {}
964        }
965
966        let bare = match mem::replace(self, GenericZipWriter::Closed) {
967            GenericZipWriter::Storer(w) => w,
968            #[cfg(any(
969                feature = "deflate",
970                feature = "deflate-miniz",
971                feature = "deflate-zlib"
972            ))]
973            GenericZipWriter::Deflater(w) => w.finish()?,
974            #[cfg(feature = "bzip2")]
975            GenericZipWriter::Bzip2(w) => w.finish()?,
976            #[cfg(feature = "zstd")]
977            GenericZipWriter::Zstd(w) => w.finish()?,
978            GenericZipWriter::Closed => {
979                return Err(io::Error::new(
980                    io::ErrorKind::BrokenPipe,
981                    "ZipWriter was already closed",
982                )
983                .into())
984            }
985        };
986
987        *self = {
988            #[allow(deprecated)]
989            match compression {
990                CompressionMethod::Stored => {
991                    if compression_level.is_some() {
992                        return Err(ZipError::UnsupportedArchive(
993                            "Unsupported compression level",
994                        ));
995                    }
996
997                    GenericZipWriter::Storer(bare)
998                }
999                #[cfg(any(
1000                    feature = "deflate",
1001                    feature = "deflate-miniz",
1002                    feature = "deflate-zlib"
1003                ))]
1004                CompressionMethod::Deflated => GenericZipWriter::Deflater(DeflateEncoder::new(
1005                    bare,
1006                    flate2::Compression::new(
1007                        clamp_opt(
1008                            compression_level
1009                                .unwrap_or(flate2::Compression::default().level() as i32),
1010                            deflate_compression_level_range(),
1011                        )
1012                        .ok_or(ZipError::UnsupportedArchive(
1013                            "Unsupported compression level",
1014                        ))? as u32,
1015                    ),
1016                )),
1017                #[cfg(feature = "bzip2")]
1018                CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzEncoder::new(
1019                    bare,
1020                    bzip2::Compression::new(
1021                        clamp_opt(
1022                            compression_level
1023                                .unwrap_or(bzip2::Compression::default().level() as i32),
1024                            bzip2_compression_level_range(),
1025                        )
1026                        .ok_or(ZipError::UnsupportedArchive(
1027                            "Unsupported compression level",
1028                        ))? as u32,
1029                    ),
1030                )),
1031                CompressionMethod::AES => {
1032                    return Err(ZipError::UnsupportedArchive(
1033                        "AES compression is not supported for writing",
1034                    ))
1035                }
1036                #[cfg(feature = "zstd")]
1037                CompressionMethod::Zstd => GenericZipWriter::Zstd(
1038                    ZstdEncoder::new(
1039                        bare,
1040                        clamp_opt(
1041                            compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL),
1042                            zstd::compression_level_range(),
1043                        )
1044                        .ok_or(ZipError::UnsupportedArchive(
1045                            "Unsupported compression level",
1046                        ))?,
1047                    )
1048                    .unwrap(),
1049                ),
1050                CompressionMethod::Unsupported(..) => {
1051                    return Err(ZipError::UnsupportedArchive("Unsupported compression"))
1052                }
1053            }
1054        };
1055
1056        Ok(())
1057    }
1058
1059    fn ref_mut(&mut self) -> Option<&mut dyn Write> {
1060        match *self {
1061            GenericZipWriter::Storer(ref mut w) => Some(w as &mut dyn Write),
1062            #[cfg(any(
1063                feature = "deflate",
1064                feature = "deflate-miniz",
1065                feature = "deflate-zlib"
1066            ))]
1067            GenericZipWriter::Deflater(ref mut w) => Some(w as &mut dyn Write),
1068            #[cfg(feature = "bzip2")]
1069            GenericZipWriter::Bzip2(ref mut w) => Some(w as &mut dyn Write),
1070            #[cfg(feature = "zstd")]
1071            GenericZipWriter::Zstd(ref mut w) => Some(w as &mut dyn Write),
1072            GenericZipWriter::Closed => None,
1073        }
1074    }
1075
1076    fn is_closed(&self) -> bool {
1077        matches!(*self, GenericZipWriter::Closed)
1078    }
1079
1080    fn get_plain(&mut self) -> &mut W {
1081        match *self {
1082            GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(ref mut w)) => w,
1083            _ => panic!("Should have switched to stored and unencrypted beforehand"),
1084        }
1085    }
1086
1087    fn current_compression(&self) -> Option<CompressionMethod> {
1088        match *self {
1089            GenericZipWriter::Storer(..) => Some(CompressionMethod::Stored),
1090            #[cfg(any(
1091                feature = "deflate",
1092                feature = "deflate-miniz",
1093                feature = "deflate-zlib"
1094            ))]
1095            GenericZipWriter::Deflater(..) => Some(CompressionMethod::Deflated),
1096            #[cfg(feature = "bzip2")]
1097            GenericZipWriter::Bzip2(..) => Some(CompressionMethod::Bzip2),
1098            #[cfg(feature = "zstd")]
1099            GenericZipWriter::Zstd(..) => Some(CompressionMethod::Zstd),
1100            GenericZipWriter::Closed => None,
1101        }
1102    }
1103
1104    fn unwrap(self) -> W {
1105        match self {
1106            GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(w)) => w,
1107            _ => panic!("Should have switched to stored and unencrypted beforehand"),
1108        }
1109    }
1110}
1111
1112#[cfg(any(
1113    feature = "deflate",
1114    feature = "deflate-miniz",
1115    feature = "deflate-zlib"
1116))]
1117fn deflate_compression_level_range() -> std::ops::RangeInclusive<i32> {
1118    let min = flate2::Compression::none().level() as i32;
1119    let max = flate2::Compression::best().level() as i32;
1120    min..=max
1121}
1122
1123#[cfg(feature = "bzip2")]
1124fn bzip2_compression_level_range() -> std::ops::RangeInclusive<i32> {
1125    let min = bzip2::Compression::none().level() as i32;
1126    let max = bzip2::Compression::best().level() as i32;
1127    min..=max
1128}
1129
1130#[cfg(any(
1131    feature = "deflate",
1132    feature = "deflate-miniz",
1133    feature = "deflate-zlib",
1134    feature = "bzip2",
1135    feature = "zstd"
1136))]
1137fn clamp_opt<T: Ord + Copy>(value: T, range: std::ops::RangeInclusive<T>) -> Option<T> {
1138    if range.contains(&value) {
1139        Some(value)
1140    } else {
1141        None
1142    }
1143}
1144
1145fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
1146    // local file header signature
1147    writer.write_u32::<LittleEndian>(spec::LOCAL_FILE_HEADER_SIGNATURE)?;
1148    // version needed to extract
1149    writer.write_u16::<LittleEndian>(file.version_needed())?;
1150    // general purpose bit flag
1151    let flag = if !file.file_name.is_ascii() {
1152        1u16 << 11
1153    } else {
1154        0
1155    } | if file.encrypted { 1u16 << 0 } else { 0 };
1156    writer.write_u16::<LittleEndian>(flag)?;
1157    // Compression method
1158    #[allow(deprecated)]
1159    writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
1160    // last mod file time and last mod file date
1161    writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
1162    writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
1163    // crc-32
1164    writer.write_u32::<LittleEndian>(file.crc32)?;
1165    // compressed size and uncompressed size
1166    if file.large_file {
1167        writer.write_u32::<LittleEndian>(spec::ZIP64_BYTES_THR as u32)?;
1168        writer.write_u32::<LittleEndian>(spec::ZIP64_BYTES_THR as u32)?;
1169    } else {
1170        writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
1171        writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
1172    }
1173    // file name length
1174    writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
1175    // extra field length
1176    let extra_field_length = if file.large_file { 20 } else { 0 } + file.extra_field.len() as u16;
1177    writer.write_u16::<LittleEndian>(extra_field_length)?;
1178    // file name
1179    writer.write_all(file.file_name.as_bytes())?;
1180    // zip64 extra field
1181    if file.large_file {
1182        write_local_zip64_extra_field(writer, file)?;
1183    }
1184
1185    Ok(())
1186}
1187
1188fn update_local_file_header<T: Write + io::Seek>(
1189    writer: &mut T,
1190    file: &ZipFileData,
1191) -> ZipResult<()> {
1192    const CRC32_OFFSET: u64 = 14;
1193    writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
1194    writer.write_u32::<LittleEndian>(file.crc32)?;
1195    if file.large_file {
1196        update_local_zip64_extra_field(writer, file)?;
1197    } else {
1198        // check compressed size as well as it can also be slightly larger than uncompressed size
1199        if file.compressed_size > spec::ZIP64_BYTES_THR {
1200            return Err(ZipError::Io(io::Error::new(
1201                io::ErrorKind::Other,
1202                "Large file option has not been set",
1203            )));
1204        }
1205        writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
1206        // uncompressed size is already checked on write to catch it as soon as possible
1207        writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
1208    }
1209    Ok(())
1210}
1211
1212fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
1213    // buffer zip64 extra field to determine its variable length
1214    let mut zip64_extra_field = [0; 28];
1215    let zip64_extra_field_length =
1216        write_central_zip64_extra_field(&mut zip64_extra_field.as_mut(), file)?;
1217
1218    // central file header signature
1219    writer.write_u32::<LittleEndian>(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)?;
1220    // version made by
1221    let version_made_by = (file.system as u16) << 8 | (file.version_made_by as u16);
1222    writer.write_u16::<LittleEndian>(version_made_by)?;
1223    // version needed to extract
1224    writer.write_u16::<LittleEndian>(file.version_needed())?;
1225    // general puprose bit flag
1226    let flag = if !file.file_name.is_ascii() {
1227        1u16 << 11
1228    } else {
1229        0
1230    } | if file.encrypted { 1u16 << 0 } else { 0 };
1231    writer.write_u16::<LittleEndian>(flag)?;
1232    // compression method
1233    #[allow(deprecated)]
1234    writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
1235    // last mod file time + date
1236    writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
1237    writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
1238    // crc-32
1239    writer.write_u32::<LittleEndian>(file.crc32)?;
1240    // compressed size
1241    writer.write_u32::<LittleEndian>(file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32)?;
1242    // uncompressed size
1243    writer.write_u32::<LittleEndian>(file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32)?;
1244    // file name length
1245    writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
1246    // extra field length
1247    writer.write_u16::<LittleEndian>(zip64_extra_field_length + file.extra_field.len() as u16)?;
1248    // file comment length
1249    writer.write_u16::<LittleEndian>(0)?;
1250    // disk number start
1251    writer.write_u16::<LittleEndian>(0)?;
1252    // internal file attribytes
1253    writer.write_u16::<LittleEndian>(0)?;
1254    // external file attributes
1255    writer.write_u32::<LittleEndian>(file.external_attributes)?;
1256    // relative offset of local header
1257    writer.write_u32::<LittleEndian>(file.header_start.min(spec::ZIP64_BYTES_THR) as u32)?;
1258    // file name
1259    writer.write_all(file.file_name.as_bytes())?;
1260    // zip64 extra field
1261    writer.write_all(&zip64_extra_field[..zip64_extra_field_length as usize])?;
1262    // extra field
1263    writer.write_all(&file.extra_field)?;
1264    // file comment
1265    // <none>
1266
1267    Ok(())
1268}
1269
1270fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> {
1271    let mut data = file.extra_field.as_slice();
1272
1273    if data.len() > spec::ZIP64_ENTRY_THR {
1274        return Err(ZipError::Io(io::Error::new(
1275            io::ErrorKind::InvalidData,
1276            "Extra data exceeds extra field",
1277        )));
1278    }
1279
1280    while !data.is_empty() {
1281        let left = data.len();
1282        if left < 4 {
1283            return Err(ZipError::Io(io::Error::new(
1284                io::ErrorKind::Other,
1285                "Incomplete extra data header",
1286            )));
1287        }
1288        let kind = data.read_u16::<LittleEndian>()?;
1289        let size = data.read_u16::<LittleEndian>()? as usize;
1290        let left = left - 4;
1291
1292        if kind == 0x0001 {
1293            return Err(ZipError::Io(io::Error::new(
1294                io::ErrorKind::Other,
1295                "No custom ZIP64 extra data allowed",
1296            )));
1297        }
1298
1299        #[cfg(not(feature = "unreserved"))]
1300        {
1301            if kind <= 31 || EXTRA_FIELD_MAPPING.iter().any(|&mapped| mapped == kind) {
1302                return Err(ZipError::Io(io::Error::new(
1303                    io::ErrorKind::Other,
1304                    format!(
1305                        "Extra data header ID {kind:#06} requires crate feature \"unreserved\"",
1306                    ),
1307                )));
1308            }
1309        }
1310
1311        if size > left {
1312            return Err(ZipError::Io(io::Error::new(
1313                io::ErrorKind::Other,
1314                "Extra data size exceeds extra field",
1315            )));
1316        }
1317
1318        data = &data[size..];
1319    }
1320
1321    Ok(())
1322}
1323
1324fn write_local_zip64_extra_field<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
1325    // This entry in the Local header MUST include BOTH original
1326    // and compressed file size fields.
1327    writer.write_u16::<LittleEndian>(0x0001)?;
1328    writer.write_u16::<LittleEndian>(16)?;
1329    writer.write_u64::<LittleEndian>(file.uncompressed_size)?;
1330    writer.write_u64::<LittleEndian>(file.compressed_size)?;
1331    // Excluded fields:
1332    // u32: disk start number
1333    Ok(())
1334}
1335
1336fn update_local_zip64_extra_field<T: Write + io::Seek>(
1337    writer: &mut T,
1338    file: &ZipFileData,
1339) -> ZipResult<()> {
1340    let zip64_extra_field = file.header_start + 30 + file.file_name.as_bytes().len() as u64;
1341    writer.seek(io::SeekFrom::Start(zip64_extra_field + 4))?;
1342    writer.write_u64::<LittleEndian>(file.uncompressed_size)?;
1343    writer.write_u64::<LittleEndian>(file.compressed_size)?;
1344    // Excluded fields:
1345    // u32: disk start number
1346    Ok(())
1347}
1348
1349fn write_central_zip64_extra_field<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<u16> {
1350    // The order of the fields in the zip64 extended
1351    // information record is fixed, but the fields MUST
1352    // only appear if the corresponding Local or Central
1353    // directory record field is set to 0xFFFF or 0xFFFFFFFF.
1354    let mut size = 0;
1355    let uncompressed_size = file.uncompressed_size > spec::ZIP64_BYTES_THR;
1356    let compressed_size = file.compressed_size > spec::ZIP64_BYTES_THR;
1357    let header_start = file.header_start > spec::ZIP64_BYTES_THR;
1358    if uncompressed_size {
1359        size += 8;
1360    }
1361    if compressed_size {
1362        size += 8;
1363    }
1364    if header_start {
1365        size += 8;
1366    }
1367    if size > 0 {
1368        writer.write_u16::<LittleEndian>(0x0001)?;
1369        writer.write_u16::<LittleEndian>(size)?;
1370        size += 4;
1371
1372        if uncompressed_size {
1373            writer.write_u64::<LittleEndian>(file.uncompressed_size)?;
1374        }
1375        if compressed_size {
1376            writer.write_u64::<LittleEndian>(file.compressed_size)?;
1377        }
1378        if header_start {
1379            writer.write_u64::<LittleEndian>(file.header_start)?;
1380        }
1381        // Excluded fields:
1382        // u32: disk start number
1383    }
1384    Ok(size)
1385}
1386
1387fn path_to_string(path: &std::path::Path) -> String {
1388    let mut path_str = String::new();
1389    for component in path.components() {
1390        if let std::path::Component::Normal(os_str) = component {
1391            if !path_str.is_empty() {
1392                path_str.push('/');
1393            }
1394            path_str.push_str(&os_str.to_string_lossy());
1395        }
1396    }
1397    path_str
1398}
1399
1400#[cfg(test)]
1401mod test {
1402    use super::{FileOptions, ZipWriter};
1403    use crate::compression::CompressionMethod;
1404    use crate::types::DateTime;
1405    use std::io;
1406    use std::io::Write;
1407
1408    #[test]
1409    fn write_empty_zip() {
1410        let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
1411        writer.set_comment("ZIP");
1412        let result = writer.finish().unwrap();
1413        assert_eq!(result.get_ref().len(), 25);
1414        assert_eq!(
1415            *result.get_ref(),
1416            [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]
1417        );
1418    }
1419
1420    #[test]
1421    fn unix_permissions_bitmask() {
1422        // unix_permissions() throws away upper bits.
1423        let options = FileOptions::default().unix_permissions(0o120777);
1424        assert_eq!(options.permissions, Some(0o777));
1425    }
1426
1427    #[test]
1428    fn write_zip_dir() {
1429        let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
1430        writer
1431            .add_directory(
1432                "test",
1433                FileOptions::default().last_modified_time(
1434                    DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
1435                ),
1436            )
1437            .unwrap();
1438        assert!(writer
1439            .write(b"writing to a directory is not allowed, and will not write any data")
1440            .is_err());
1441        let result = writer.finish().unwrap();
1442        assert_eq!(result.get_ref().len(), 108);
1443        assert_eq!(
1444            *result.get_ref(),
1445            &[
1446                80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1447                0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 1, 2, 46, 3, 20, 0, 0, 0, 0, 0,
1448                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,
1449                0, 0, 237, 65, 0, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0,
1450                1, 0, 51, 0, 0, 0, 35, 0, 0, 0, 0, 0,
1451            ] as &[u8]
1452        );
1453    }
1454
1455    #[test]
1456    fn write_symlink_simple() {
1457        let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
1458        writer
1459            .add_symlink(
1460                "name",
1461                "target",
1462                FileOptions::default().last_modified_time(
1463                    DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
1464                ),
1465            )
1466            .unwrap();
1467        assert!(writer
1468            .write(b"writing to a symlink is not allowed and will not write any data")
1469            .is_err());
1470        let result = writer.finish().unwrap();
1471        assert_eq!(result.get_ref().len(), 112);
1472        assert_eq!(
1473            *result.get_ref(),
1474            &[
1475                80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0,
1476                6, 0, 0, 0, 4, 0, 0, 0, 110, 97, 109, 101, 116, 97, 114, 103, 101, 116, 80, 75, 1,
1477                2, 46, 3, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0, 6, 0,
1478                0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 161, 0, 0, 0, 0, 110, 97, 109, 101,
1479                80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 50, 0, 0, 0, 40, 0, 0, 0, 0, 0
1480            ] as &[u8],
1481        );
1482    }
1483
1484    #[test]
1485    fn write_symlink_wonky_paths() {
1486        let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
1487        writer
1488            .add_symlink(
1489                "directory\\link",
1490                "/absolute/symlink\\with\\mixed/slashes",
1491                FileOptions::default().last_modified_time(
1492                    DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
1493                ),
1494            )
1495            .unwrap();
1496        assert!(writer
1497            .write(b"writing to a symlink is not allowed and will not write any data")
1498            .is_err());
1499        let result = writer.finish().unwrap();
1500        assert_eq!(result.get_ref().len(), 162);
1501        assert_eq!(
1502            *result.get_ref(),
1503            &[
1504                80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95, 41, 81, 245, 36, 0, 0, 0,
1505                36, 0, 0, 0, 14, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105,
1506                110, 107, 47, 97, 98, 115, 111, 108, 117, 116, 101, 47, 115, 121, 109, 108, 105,
1507                110, 107, 92, 119, 105, 116, 104, 92, 109, 105, 120, 101, 100, 47, 115, 108, 97,
1508                115, 104, 101, 115, 80, 75, 1, 2, 46, 3, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95,
1509                41, 81, 245, 36, 0, 0, 0, 36, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
1510                161, 0, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105, 110,
1511                107, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 60, 0, 0, 0, 80, 0, 0, 0, 0, 0
1512            ] as &[u8],
1513        );
1514    }
1515
1516    #[test]
1517    fn write_mimetype_zip() {
1518        let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
1519        let options = FileOptions {
1520            compression_method: CompressionMethod::Stored,
1521            compression_level: None,
1522            last_modified_time: DateTime::default(),
1523            permissions: Some(33188),
1524            large_file: false,
1525            encrypt_with: None,
1526        };
1527        writer.start_file("mimetype", options).unwrap();
1528        writer
1529            .write_all(b"application/vnd.oasis.opendocument.text")
1530            .unwrap();
1531        let result = writer.finish().unwrap();
1532
1533        assert_eq!(result.get_ref().len(), 153);
1534        let mut v = Vec::new();
1535        v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
1536        assert_eq!(result.get_ref(), &v);
1537    }
1538
1539    #[test]
1540    fn path_to_string() {
1541        let mut path = std::path::PathBuf::new();
1542        #[cfg(windows)]
1543        path.push(r"C:\");
1544        #[cfg(unix)]
1545        path.push("/");
1546        path.push("windows");
1547        path.push("..");
1548        path.push(".");
1549        path.push("system32");
1550        let path_str = super::path_to_string(&path);
1551        assert_eq!(path_str, "windows/system32");
1552    }
1553}
1554
1555#[cfg(not(feature = "unreserved"))]
1556const EXTRA_FIELD_MAPPING: [u16; 49] = [
1557    0x0001, 0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016,
1558    0x0017, 0x0018, 0x0019, 0x0020, 0x0021, 0x0022, 0x0023, 0x0065, 0x0066, 0x4690, 0x07c8, 0x2605,
1559    0x2705, 0x2805, 0x334d, 0x4341, 0x4453, 0x4704, 0x470f, 0x4b46, 0x4c41, 0x4d49, 0x4f4c, 0x5356,
1560    0x5455, 0x554e, 0x5855, 0x6375, 0x6542, 0x7075, 0x756e, 0x7855, 0xa11e, 0xa220, 0xfd4a, 0x9901,
1561    0x9902,
1562];