Coverage Report

Created: 2025-12-31 06:51

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