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