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