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