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