/rust/registry/src/index.crates.io-1949cf8c6b5b557f/zip-9.5.1/src/write/options.rs
Line | Count | Source |
1 | | //! Write Options |
2 | | |
3 | | use crate::extra_fields::ExtendedTimestamp; |
4 | | use core::fmt::Debug; |
5 | | use core::marker::PhantomData; |
6 | | use std::sync::Arc; |
7 | | |
8 | | use crate::CompressionMethod; |
9 | | use crate::datetime::DateTime; |
10 | | use crate::extra_fields::CustomExtraField; |
11 | | use crate::format::extra_fields::ExtraFieldId; |
12 | | use crate::format::ffi; |
13 | | use crate::format::system::System; |
14 | | use crate::result::{ZipResult, invalid}; |
15 | | use crate::zipcrypto::ZipCryptoKeys; |
16 | | |
17 | | #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
18 | | pub(crate) enum EncryptWith<'k> { |
19 | | #[cfg(feature = "aes-crypto")] |
20 | | Aes { |
21 | | mode: crate::format::aes::AesMode, |
22 | | vendor_version: crate::format::aes::AesVendorVersion, |
23 | | // When the password is None, it means that we are reusing the previous encryption |
24 | | password: Option<&'k [u8]>, |
25 | | salt: Option<crate::aes::AesSalt>, |
26 | | }, |
27 | | ZipCrypto(ZipCryptoKeys, PhantomData<&'k ()>), |
28 | | } |
29 | | |
30 | | #[cfg(feature = "_arbitrary")] |
31 | | impl<'a> arbitrary::Arbitrary<'a> for EncryptWith<'a> { |
32 | | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { |
33 | | #[cfg(feature = "aes-crypto")] |
34 | | if bool::arbitrary(u)? { |
35 | | return Ok(EncryptWith::Aes { |
36 | | mode: crate::format::aes::AesMode::arbitrary(u)?, |
37 | | password: Some(u.arbitrary::<&[u8]>()?), |
38 | | vendor_version: crate::format::aes::AesVendorVersion::Ae2, |
39 | | salt: None, // We don't need to test with random salt. It's only for testing or reproducible zips |
40 | | }); |
41 | | } |
42 | | |
43 | | Ok(EncryptWith::ZipCrypto( |
44 | | ZipCryptoKeys::arbitrary(u)?, |
45 | | PhantomData, |
46 | | )) |
47 | | } |
48 | | } |
49 | | |
50 | | /// Metadata for a file to be written |
51 | | #[non_exhaustive] |
52 | | #[derive(Clone, Debug, Copy, Eq, PartialEq)] |
53 | | pub struct FileOptions<'k, 'n, T: sealed::FileOptionExtension> { |
54 | | pub(crate) compression_method: CompressionMethod, |
55 | | pub(crate) compression_level: Option<i64>, |
56 | | pub(crate) external_attributes: Option<u32>, |
57 | | pub(crate) last_modified_time: DateTime, |
58 | | pub(crate) permissions: Option<u32>, |
59 | | pub(crate) large_file: bool, |
60 | | pub(crate) encrypt_with: Option<EncryptWith<'k>>, |
61 | | pub(crate) extended_options: T, |
62 | | pub(crate) alignment: u16, |
63 | | pub(crate) alignment_header_id: u16, |
64 | | #[cfg(feature = "deflate-zopfli")] |
65 | | pub(super) zopfli_buffer_size: Option<usize>, |
66 | | pub(crate) system: Option<System>, |
67 | | pub(crate) name: Option<&'n [u8]>, |
68 | | } |
69 | | /// Simple File Options. Can be copied and good for simple writing zip files |
70 | | pub type SimpleFileOptions = FileOptions<'static, 'static, ()>; |
71 | | |
72 | | impl FileOptions<'static, 'static, ()> { |
73 | | pub(crate) const DEFAULT_FILE_PERMISSION: u32 = 0o644 | ffi::S_IFREG; |
74 | | pub(crate) const DEFAULT_DIR_PERMISSIONS: u32 = 0o755 | ffi::S_IFDIR; // rwxr-xr-x default for directories |
75 | | pub(crate) const DEFAULT_SYMLINK_PERMISSIONS: u32 = 0o777 | ffi::S_IFLNK; |
76 | | } |
77 | | |
78 | | pub(crate) mod sealed { |
79 | | use super::ExtendedFileOptions; |
80 | | use crate::extra_fields::CustomExtraField; |
81 | | use std::sync::Arc; |
82 | | |
83 | | pub trait Sealed {} |
84 | | /// File options Extensions |
85 | | #[doc(hidden)] |
86 | | pub trait FileOptionExtension: Default + Sealed { |
87 | | /// Extra Data |
88 | | fn extra_fields(&self) -> Option<&Arc<Vec<CustomExtraField>>>; |
89 | | /// File Comment |
90 | | fn file_comment(&self) -> Option<&str>; |
91 | | /// Take File Comment (moves ownership) |
92 | | fn take_file_comment(&mut self) -> Option<Box<str>>; |
93 | | /// Whether to check the extra fields with the reader's parser before writing |
94 | 0 | fn validate_extra_fields(&self) -> bool { |
95 | 0 | false |
96 | 0 | } Unexecuted instantiation: <_ as zip::write::options::sealed::FileOptionExtension>::validate_extra_fields Unexecuted instantiation: <_ as zip::write::options::sealed::FileOptionExtension>::validate_extra_fields |
97 | | } |
98 | | impl Sealed for () {} |
99 | | impl FileOptionExtension for () { |
100 | 0 | fn extra_fields(&self) -> Option<&Arc<Vec<CustomExtraField>>> { |
101 | 0 | None |
102 | 0 | } Unexecuted instantiation: <() as zip::write::options::sealed::FileOptionExtension>::extra_fields Unexecuted instantiation: <() as zip::write::options::sealed::FileOptionExtension>::extra_fields |
103 | 0 | fn file_comment(&self) -> Option<&str> { |
104 | 0 | None |
105 | 0 | } Unexecuted instantiation: <() as zip::write::options::sealed::FileOptionExtension>::file_comment Unexecuted instantiation: <() as zip::write::options::sealed::FileOptionExtension>::file_comment |
106 | 0 | fn take_file_comment(&mut self) -> Option<Box<str>> { |
107 | 0 | None |
108 | 0 | } Unexecuted instantiation: <() as zip::write::options::sealed::FileOptionExtension>::take_file_comment Unexecuted instantiation: <() as zip::write::options::sealed::FileOptionExtension>::take_file_comment |
109 | | } |
110 | | impl Sealed for ExtendedFileOptions {} |
111 | | |
112 | | impl FileOptionExtension for ExtendedFileOptions { |
113 | 0 | fn extra_fields(&self) -> Option<&Arc<Vec<CustomExtraField>>> { |
114 | 0 | Some(&self.extra_fields) |
115 | 0 | } Unexecuted instantiation: <zip::write::options::ExtendedFileOptions as zip::write::options::sealed::FileOptionExtension>::extra_fields Unexecuted instantiation: <zip::write::options::ExtendedFileOptions as zip::write::options::sealed::FileOptionExtension>::extra_fields |
116 | 0 | fn file_comment(&self) -> Option<&str> { |
117 | 0 | self.file_comment.as_ref().map(Box::as_ref) |
118 | 0 | } Unexecuted instantiation: <zip::write::options::ExtendedFileOptions as zip::write::options::sealed::FileOptionExtension>::file_comment Unexecuted instantiation: <zip::write::options::ExtendedFileOptions as zip::write::options::sealed::FileOptionExtension>::file_comment |
119 | 0 | fn take_file_comment(&mut self) -> Option<Box<str>> { |
120 | 0 | self.file_comment.take() |
121 | 0 | } Unexecuted instantiation: <zip::write::options::ExtendedFileOptions as zip::write::options::sealed::FileOptionExtension>::take_file_comment Unexecuted instantiation: <zip::write::options::ExtendedFileOptions as zip::write::options::sealed::FileOptionExtension>::take_file_comment |
122 | 0 | fn validate_extra_fields(&self) -> bool { |
123 | 0 | self.validate_extra_fields |
124 | 0 | } Unexecuted instantiation: <zip::write::options::ExtendedFileOptions as zip::write::options::sealed::FileOptionExtension>::validate_extra_fields Unexecuted instantiation: <zip::write::options::ExtendedFileOptions as zip::write::options::sealed::FileOptionExtension>::validate_extra_fields |
125 | | } |
126 | | } |
127 | | |
128 | | /// Adds Extra Data and Central Extra Data. It does not implement copy. |
129 | | pub type FullFileOptions<'k, 'n> = FileOptions<'k, 'n, ExtendedFileOptions>; |
130 | | /// The Extension for Extra Data and Central Extra Data |
131 | | #[cfg_attr(feature = "_arbitrary", derive(arbitrary::Arbitrary))] |
132 | | #[derive(Clone, Default, Eq, PartialEq)] |
133 | | pub struct ExtendedFileOptions { |
134 | | pub(crate) extra_fields: Arc<Vec<CustomExtraField>>, |
135 | | pub(crate) file_comment: Option<Box<str>>, |
136 | | pub(crate) validate_extra_fields: bool, |
137 | | } |
138 | | |
139 | | impl ExtendedFileOptions { |
140 | | /// Adds an extra data field, unless we detect that it's invalid. |
141 | | /// |
142 | | /// # Parameters |
143 | | /// |
144 | | /// * `header_id` – The 2‑byte identifier of the ZIP extra field to add. |
145 | | /// This value determines the type/format of `data` and should either be |
146 | | /// one of the standard ZIP extra field IDs defined by the ZIP |
147 | | /// specification or an application‑specific (vendor) ID. |
148 | | /// * `data` – The raw payload for the extra field, without the leading |
149 | | /// header ID or length; those are derived from `header_id` and |
150 | | /// `data.len()` and written automatically. |
151 | | /// * `central_only` – Controls where the extra field is stored: |
152 | | /// * When `true`, the field is appended only to the central directory |
153 | | /// extra data (`central_extra_data`), and the corresponding local file |
154 | | /// header is left unchanged. |
155 | | /// * When `false`, the field is appended to the local file header extra |
156 | | /// data (`extra_data`) and may also be reflected in the central |
157 | | /// directory, depending on how the ZIP is written. |
158 | | /// |
159 | | /// The combined size of all extra data (local + central) must not exceed |
160 | | /// `u16::MAX`. If adding this field would exceed that limit or produce an |
161 | | /// invalid extra data structure, an error is returned and no data is |
162 | | /// added. |
163 | | #[deprecated = "use add_extra_field()"] |
164 | 0 | pub fn add_extra_data<D: AsRef<[u8]>>( |
165 | 0 | &mut self, |
166 | 0 | header_id: u16, |
167 | 0 | data: D, |
168 | 0 | central_only: bool, |
169 | 0 | ) -> ZipResult<()> { |
170 | 0 | self.add_extra_field(header_id, data, central_only) |
171 | 0 | } Unexecuted instantiation: <zip::write::options::ExtendedFileOptions>::add_extra_data::<_> Unexecuted instantiation: <zip::write::options::ExtendedFileOptions>::add_extra_data::<_> |
172 | | /// Whether the reader's own extra-field parser accepts this field in every header it will be |
173 | | /// written to (local and central, or central only). |
174 | 0 | pub(crate) fn extra_field_parses(header_id: u16, data: &[u8], central_only: bool) -> bool { |
175 | | use crate::extra_fields::{ExtraField, UsedExtraField}; |
176 | | use crate::format::blocks::{Pod, ZipLocalEntryBlock}; |
177 | | use std::io::Cursor; |
178 | | |
179 | 0 | let Ok(size) = u16::try_from(data.len()) else { |
180 | 0 | return false; |
181 | | }; |
182 | 0 | let mut serialized = Vec::with_capacity(data.len() + 4); |
183 | 0 | serialized.extend_from_slice(&header_id.to_le_bytes()); |
184 | 0 | serialized.extend_from_slice(&size.to_le_bytes()); |
185 | 0 | serialized.extend_from_slice(data); |
186 | 0 | let block = ZipLocalEntryBlock::zeroed(); |
187 | 0 | if ExtraField::parse(&mut Cursor::new(&serialized), &block, false).is_err() { |
188 | 0 | return false; |
189 | 0 | } |
190 | | // Only extended timestamps interpret their payload differently in local and central headers. |
191 | 0 | central_only |
192 | 0 | || header_id != UsedExtraField::ExtendedTimestamp.as_u16() |
193 | 0 | || ExtendedTimestamp::try_from_reader(&mut Cursor::new(data), size, true).is_ok() |
194 | 0 | } Unexecuted instantiation: <zip::write::options::ExtendedFileOptions>::extra_field_parses Unexecuted instantiation: <zip::write::options::ExtendedFileOptions>::extra_field_parses |
195 | | |
196 | | /// Adds an extra field, unless we detect that it's invalid. |
197 | | /// |
198 | | /// # Parameters |
199 | | /// |
200 | | /// * `header_id` – The 2‑byte identifier of the ZIP extra field to add. |
201 | | /// This value determines the type/format of `data` and should either be |
202 | | /// one of the standard ZIP extra field IDs defined by the ZIP |
203 | | /// specification or an application‑specific (vendor) ID. |
204 | | /// * `data` – The raw payload for the extra field, without the leading |
205 | | /// header ID or length; those are derived from `header_id` and |
206 | | /// `data.len()` and written automatically. |
207 | | /// * `central_only` – Controls where the extra field is stored: |
208 | | /// * When `true`, the field is appended only to the central directory |
209 | | /// extra data (`central_extra_data`), and the corresponding local file |
210 | | /// header is left unchanged. |
211 | | /// * When `false`, the field is appended to the local file header extra |
212 | | /// data (`extra_data`) and may also be reflected in the central |
213 | | /// directory, depending on how the ZIP is written. |
214 | | /// |
215 | | /// The combined size of all extra data (local + central) must not exceed |
216 | | /// `u16::MAX`; if adding this field would exceed that limit, an error is |
217 | | /// returned and no data is added. |
218 | | /// |
219 | | /// By default `data` is otherwise stored verbatim, even for a header ID |
220 | | /// that this crate's reader interprets (such as `0x7075`, Info-ZIP Unicode |
221 | | /// Path) and would reject when the archive is read back. Opt in to |
222 | | /// checking it with [`FileOptions::validate_extra_fields`]. |
223 | 0 | pub fn add_extra_field<D: AsRef<[u8]>>( |
224 | 0 | &mut self, |
225 | 0 | header_id: u16, |
226 | 0 | data: D, |
227 | 0 | central_only: bool, |
228 | 0 | ) -> ZipResult<()> { |
229 | 0 | let data = data.as_ref(); |
230 | 0 | let len = data.len() + 4; |
231 | 0 | let extra_fields_len: usize = self.extra_fields.iter().map(|x| x.full_size(false)).sum(); Unexecuted instantiation: <zip::write::options::ExtendedFileOptions>::add_extra_field::<_>::{closure#0}Unexecuted instantiation: <zip::write::options::ExtendedFileOptions>::add_extra_field::<_>::{closure#0} |
232 | 0 | if extra_fields_len + len > u16::MAX as usize { |
233 | 0 | Err(invalid!("Extra data field would be longer than allowed")) |
234 | 0 | } else if self.validate_extra_fields |
235 | 0 | && !Self::extra_field_parses(header_id, data, central_only) |
236 | | { |
237 | | // Otherwise the archive is written, but `ZipArchive` (and `ZipWriter::new_append`) |
238 | | // reject it, e.g. an Info-ZIP Unicode Path field shorter than its 5-byte header. |
239 | 0 | Err(invalid!( |
240 | 0 | "Extra field {header_id:#06x} would not be readable: malformed data" |
241 | 0 | )) |
242 | | } else { |
243 | 0 | Arc::make_mut(&mut self.extra_fields).push(CustomExtraField::new( |
244 | 0 | central_only, |
245 | 0 | header_id, |
246 | 0 | data.to_vec().into_boxed_slice(), |
247 | | )); |
248 | 0 | Ok(()) |
249 | | } |
250 | 0 | } Unexecuted instantiation: <zip::write::options::ExtendedFileOptions>::add_extra_field::<_> Unexecuted instantiation: <zip::write::options::ExtendedFileOptions>::add_extra_field::<_> |
251 | | } |
252 | | |
253 | | impl Debug for ExtendedFileOptions { |
254 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { |
255 | 0 | f.debug_struct("ExtendedFileOptions") |
256 | 0 | .field("extra_fields", &self.extra_fields) |
257 | 0 | .field("file_comment", &self.file_comment) |
258 | 0 | .field("validate_extra_fields", &self.validate_extra_fields) |
259 | 0 | .finish() |
260 | 0 | } Unexecuted instantiation: <zip::write::options::ExtendedFileOptions as core::fmt::Debug>::fmt Unexecuted instantiation: <zip::write::options::ExtendedFileOptions as core::fmt::Debug>::fmt |
261 | | } |
262 | | |
263 | | #[cfg(feature = "_arbitrary")] |
264 | | impl<'k, 'n, 'a: 'k + 'n> arbitrary::Arbitrary<'a> for FileOptions<'k, 'n, ExtendedFileOptions> { |
265 | | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { |
266 | | use crate::extra_fields::EXTRA_FIELD_MAPPING; |
267 | | use crate::extra_fields::UsedExtraField; |
268 | | use crate::format::blocks::ZipLocalEntryBlock; |
269 | | use crate::format::magic::Magic; |
270 | | |
271 | | let mut options = FullFileOptions { |
272 | | compression_method: CompressionMethod::arbitrary(u)?, |
273 | | compression_level: if bool::arbitrary(u)? { |
274 | | Some(u.int_in_range(0..=24)?) |
275 | | } else { |
276 | | None |
277 | | }, |
278 | | last_modified_time: DateTime::arbitrary(u)?, |
279 | | permissions: Option::<u32>::arbitrary(u)?, |
280 | | large_file: bool::arbitrary(u)?, |
281 | | encrypt_with: Option::<EncryptWith<'_>>::arbitrary(u)?, |
282 | | alignment: u16::arbitrary(u)?, |
283 | | #[cfg(feature = "deflate-zopfli")] |
284 | | zopfli_buffer_size: None, |
285 | | ..Default::default() |
286 | | }; |
287 | | #[cfg(feature = "deflate-zopfli")] |
288 | | if options.compression_method == CompressionMethod::Deflated && bool::arbitrary(u)? { |
289 | | options.zopfli_buffer_size = |
290 | | Some(if bool::arbitrary(u)? { 2 } else { 3 } << u.int_in_range(8..=20)?); |
291 | | } |
292 | | u.arbitrary_loop(Some(0), Some(10), |u| { |
293 | | let header_id = u.int_in_range(2..=u16::MAX)?; |
294 | | if UsedExtraField::try_from(header_id).is_err() |
295 | | && EXTRA_FIELD_MAPPING.contains(&header_id) |
296 | | { |
297 | | return Err(arbitrary::Error::IncorrectFormat); |
298 | | } |
299 | | let data = Box::<[u8]>::arbitrary(u)?; |
300 | | options |
301 | | .add_extra_field(header_id, data, bool::arbitrary(u)?) |
302 | | .map_err(|_| arbitrary::Error::IncorrectFormat)?; |
303 | | Ok(core::ops::ControlFlow::Continue(())) |
304 | | })?; |
305 | | let entry_len: usize = size_of::<Magic>() |
306 | | + size_of::<ZipLocalEntryBlock>() |
307 | | + options |
308 | | .extended_options |
309 | | .extra_fields |
310 | | .iter() |
311 | | .map(|x| x.full_size(true)) |
312 | | .sum::<usize>(); |
313 | | let Some(max_name_len) = (u16::MAX as usize).checked_sub(entry_len) else { |
314 | | return Err(arbitrary::Error::IncorrectFormat); |
315 | | }; |
316 | | let len = u.arbitrary_len::<u8>()?.min(max_name_len); |
317 | | options.name = Some(u.bytes(len)?); |
318 | | Ok(options) |
319 | | } |
320 | | } |
321 | | |
322 | | impl<'k, 'n, T: sealed::FileOptionExtension> FileOptions<'k, 'n, T> { |
323 | 0 | pub(crate) fn normalize(&mut self) { |
324 | 0 | if !self.last_modified_time.is_valid() { |
325 | 0 | self.last_modified_time = FileOptions::<T>::default().last_modified_time; |
326 | 0 | } |
327 | | // The permissions set by the user |
328 | 0 | if let Some(perms) = &mut self.permissions |
329 | 0 | && (*perms & !0o777 == 0) |
330 | 0 | { |
331 | 0 | *perms |= ffi::S_IFREG; |
332 | 0 | } |
333 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::normalize Unexecuted instantiation: <zip::write::options::FileOptions<_>>::normalize |
334 | | |
335 | | /// Set the raw name for the new file. The name need not be UTF-8. |
336 | | #[must_use] |
337 | 0 | pub fn name<'name>(self, name: &'name [u8]) -> FileOptions<'k, 'name, T> { |
338 | 0 | FileOptions { |
339 | 0 | name: Some(name), |
340 | 0 | ..self |
341 | 0 | } |
342 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::name Unexecuted instantiation: <zip::write::options::FileOptions<_>>::name |
343 | | |
344 | | /// Indicates whether this file will be encrypted (whether with AES or `ZipCrypto`). |
345 | 0 | pub const fn has_encryption(&self) -> bool { |
346 | 0 | self.encrypt_with.is_some() |
347 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::has_encryption Unexecuted instantiation: <zip::write::options::FileOptions<_>>::has_encryption |
348 | | |
349 | | /// Set the compression method for the new file |
350 | | /// |
351 | | /// The default is `CompressionMethod::Deflated` if it is enabled. If not, |
352 | | /// `CompressionMethod::Bzip2` is the default if it is enabled. If neither `bzip2` nor `deflate` |
353 | | /// is enabled, [`CompressionMethod::Stored`] becomes the default and files are written uncompressed. |
354 | | #[must_use] |
355 | 0 | pub const fn compression_method(mut self, method: CompressionMethod) -> Self { |
356 | 0 | self.compression_method = method; |
357 | 0 | self |
358 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::compression_method Unexecuted instantiation: <zip::write::options::FileOptions<_>>::compression_method |
359 | | |
360 | | /// Set the `system` field for the new file |
361 | | /// |
362 | | /// If not set, the `zip` crate will use the current system |
363 | | #[must_use] |
364 | 0 | pub const fn system(mut self, system: System) -> Self { |
365 | 0 | self.system = Some(system); |
366 | 0 | self |
367 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::system Unexecuted instantiation: <zip::write::options::FileOptions<_>>::system |
368 | | |
369 | | /// Set the compression level for the new file |
370 | | /// |
371 | | /// `None` value specifies default compression level. |
372 | | /// |
373 | | /// Range of values depends on compression method: |
374 | | /// * `Deflated`: 10 - 264 for Zopfli, 1 - 9 for other encoders. Default is 24 if Zopfli is the |
375 | | /// only encoder, or 6 otherwise. |
376 | | /// * `Bzip2`: 1 - 9. Default is 6 |
377 | | /// * `Zstd`: -7 - 22, with zero being mapped to default level. Default is 3 |
378 | | /// * others: only `None` is allowed |
379 | | #[must_use] |
380 | 0 | pub const fn compression_level(mut self, level: Option<i64>) -> Self { |
381 | 0 | self.compression_level = level; |
382 | 0 | self |
383 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::compression_level Unexecuted instantiation: <zip::write::options::FileOptions<_>>::compression_level |
384 | | |
385 | | /// Set the last modified time |
386 | | /// |
387 | | /// The default is the current timestamp if the 'time' feature is enabled, and 1980-01-01 |
388 | | /// otherwise |
389 | | #[must_use] |
390 | 0 | pub const fn last_modified_time(mut self, mod_time: DateTime) -> Self { |
391 | 0 | self.last_modified_time = mod_time; |
392 | 0 | self |
393 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<()>>::last_modified_time Unexecuted instantiation: <zip::write::options::FileOptions<_>>::last_modified_time Unexecuted instantiation: <zip::write::options::FileOptions<()>>::last_modified_time Unexecuted instantiation: <zip::write::options::FileOptions<_>>::last_modified_time |
394 | | |
395 | | /// Set the permissions for the new file. |
396 | | /// |
397 | | /// The format is represented with unix-style permissions. |
398 | | /// The default is `0o644`, which represents `rw-r--r--` for files, |
399 | | /// and `0o755`, which represents `rwxr-xr-x` for directories. |
400 | | /// |
401 | | /// This method only preserves the file permissions bits (via a `& 0o777`) and discards |
402 | | /// higher file mode bits. So it cannot be used to denote an entry as a directory, |
403 | | /// symlink, or other special file type. |
404 | | #[must_use] |
405 | 0 | pub const fn unix_permissions(mut self, mode: u32) -> Self { |
406 | 0 | self.permissions = Some(mode & 0o777); |
407 | 0 | self |
408 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::unix_permissions Unexecuted instantiation: <zip::write::options::FileOptions<_>>::unix_permissions |
409 | | |
410 | | /// Set the external attributes for the file. |
411 | | /// |
412 | | /// If you use both [`Self::unix_permissions`] and [`Self::external_attributes`], only external |
413 | | /// attributes are going to be used |
414 | | #[must_use] |
415 | 0 | pub const fn external_attributes(mut self, external_perms: u32) -> Self { |
416 | 0 | self.external_attributes = Some(external_perms); |
417 | 0 | self |
418 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::external_attributes Unexecuted instantiation: <zip::write::options::FileOptions<_>>::external_attributes |
419 | | |
420 | | /// Set whether the new file's compressed and uncompressed size is less than 4 GiB. |
421 | | /// |
422 | | /// If set to `false` and the file exceeds the limit, an I/O error is thrown and the file is |
423 | | /// aborted. If set to `true`, readers will require ZIP64 support and if the file does not |
424 | | /// exceed the limit, 20 B are wasted. The default is `false`. |
425 | | #[must_use] |
426 | 0 | pub const fn large_file(mut self, large: bool) -> Self { |
427 | 0 | self.large_file = large; |
428 | 0 | self |
429 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::large_file Unexecuted instantiation: <zip::write::options::FileOptions<_>>::large_file |
430 | | |
431 | 0 | pub(crate) fn with_deprecated_encryption(self, password: &'k [u8]) -> FileOptions<'k, 'n, T> { |
432 | 0 | FileOptions { |
433 | 0 | encrypt_with: Some(EncryptWith::ZipCrypto( |
434 | 0 | ZipCryptoKeys::derive(password), |
435 | 0 | PhantomData, |
436 | 0 | )), |
437 | 0 | ..self |
438 | 0 | } |
439 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::with_deprecated_encryption Unexecuted instantiation: <zip::write::options::FileOptions<_>>::with_deprecated_encryption |
440 | | |
441 | | /// Set the AES encryption parameters. |
442 | | /// The `salt` must be at least 8 bytes long for AES-128, and at least 16 bytes long for AES-256. |
443 | | /// This method is not recommended, since having a fixed salt is not secure. |
444 | | /// Consider using `with_aes_encryption` instead, which uses a random salt and is more secure. |
445 | | /// |
446 | | /// # Security Warning |
447 | | /// Reusing a fixed salt across multiple files (or cloning a [`FileOptions`] configured with a fixed salt) |
448 | | /// causes keystream reuse in AES-CTR mode, enabling two-time pad / crib-dragging attacks if plaintexts |
449 | | /// share common prefixes or predictable headers. Note that because ZIP archives compress each file |
450 | | /// independently without sharing a dictionary across entries, the pseudorandomness of compressed data |
451 | | /// cannot be relied upon to prevent keystream correlation attacks across multiple files. |
452 | | #[cfg(feature = "aes-crypto")] |
453 | | pub fn with_aes_encryption_and_salt( |
454 | | self, |
455 | | password: &'k [u8], |
456 | | salt: crate::aes::AesSalt, |
457 | | ) -> FileOptions<'k, 'n, T> { |
458 | | FileOptions { |
459 | | encrypt_with: Some(EncryptWith::Aes { |
460 | | mode: salt.mode(), |
461 | | password: Some(password), |
462 | | vendor_version: crate::format::aes::AesVendorVersion::Ae2, |
463 | | salt: Some(salt), |
464 | | }), |
465 | | ..self |
466 | | } |
467 | | } |
468 | | |
469 | | /// Set the AES encryption parameters. |
470 | | #[cfg(feature = "aes-crypto")] |
471 | | pub fn with_aes_encryption( |
472 | | self, |
473 | | mode: crate::AesMode, |
474 | | password: &'k str, |
475 | | ) -> FileOptions<'k, 'n, T> { |
476 | | self.with_aes_encryption_bytes(mode, password.as_bytes()) |
477 | | } |
478 | | |
479 | | /// Set the AES encryption parameters. |
480 | | #[cfg(feature = "aes-crypto")] |
481 | | pub fn with_aes_encryption_bytes( |
482 | | self, |
483 | | mode: crate::AesMode, |
484 | | password: &'k [u8], |
485 | | ) -> FileOptions<'k, 'n, T> { |
486 | | FileOptions { |
487 | | encrypt_with: Some(EncryptWith::Aes { |
488 | | mode, |
489 | | password: Some(password), |
490 | | vendor_version: crate::format::aes::AesVendorVersion::Ae2, |
491 | | salt: None, |
492 | | }), |
493 | | ..self |
494 | | } |
495 | | } |
496 | | |
497 | | /// Sets the size of the buffer used to hold the next block that Zopfli will compress. The |
498 | | /// larger the buffer, the more effective the compression, but the more memory is required. |
499 | | /// A value of `None` indicates no buffer, which is recommended only when all non-empty writes |
500 | | /// are larger than about 32 KiB. |
501 | | #[must_use] |
502 | | #[cfg(feature = "deflate-zopfli")] |
503 | 0 | pub const fn with_zopfli_buffer(mut self, size: Option<usize>) -> Self { |
504 | 0 | self.zopfli_buffer_size = size; |
505 | 0 | self |
506 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::with_zopfli_buffer Unexecuted instantiation: <zip::write::options::FileOptions<_>>::with_zopfli_buffer |
507 | | |
508 | | /// Returns the compression level currently set. |
509 | 0 | pub const fn get_compression_level(&self) -> Option<i64> { |
510 | 0 | self.compression_level |
511 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::get_compression_level Unexecuted instantiation: <zip::write::options::FileOptions<_>>::get_compression_level |
512 | | |
513 | | /// Sets the alignment to the given number of bytes using the default header ID (`0xa11e`). |
514 | | #[must_use] |
515 | 0 | pub const fn with_alignment(mut self, alignment: u16) -> Self { |
516 | 0 | self.alignment = alignment; |
517 | 0 | self.alignment_header_id = ExtraFieldId::DataStreamAlignment.as_u16(); |
518 | 0 | self |
519 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::with_alignment Unexecuted instantiation: <zip::write::options::FileOptions<_>>::with_alignment |
520 | | |
521 | | /// Sets the alignment to the given number of bytes using Android's alignment extra field header ID (`0xd935`). |
522 | | #[must_use] |
523 | 0 | pub const fn with_android_alignment(mut self, alignment: u16) -> Self { |
524 | 0 | self.alignment = alignment; |
525 | 0 | self.alignment_header_id = ExtraFieldId::AndroidZipAlignment.as_u16(); |
526 | 0 | self |
527 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::with_android_alignment Unexecuted instantiation: <zip::write::options::FileOptions<_>>::with_android_alignment |
528 | | |
529 | | /// Sets the alignment to the given number of bytes with a specified extra field header ID. |
530 | | #[must_use] |
531 | 0 | pub const fn with_alignment_and_header_id(mut self, alignment: u16, header_id: u16) -> Self { |
532 | 0 | self.alignment = alignment; |
533 | 0 | self.alignment_header_id = header_id; |
534 | 0 | self |
535 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::with_alignment_and_header_id Unexecuted instantiation: <zip::write::options::FileOptions<_>>::with_alignment_and_header_id |
536 | | |
537 | | /// Returns the alignment multiple. |
538 | | #[must_use] |
539 | 0 | pub const fn alignment(&self) -> u16 { |
540 | 0 | self.alignment |
541 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::alignment Unexecuted instantiation: <zip::write::options::FileOptions<_>>::alignment |
542 | | |
543 | | /// Returns the alignment extra field header ID. |
544 | | #[must_use] |
545 | 0 | pub const fn alignment_header_id(&self) -> u16 { |
546 | 0 | self.alignment_header_id |
547 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<_>>::alignment_header_id Unexecuted instantiation: <zip::write::options::FileOptions<_>>::alignment_header_id |
548 | | } |
549 | | impl FileOptions<'_, '_, ExtendedFileOptions> { |
550 | | /// Check every extra field with this crate's own extra-field parser, and |
551 | | /// return an error instead of writing a field that [`ZipArchive`] would |
552 | | /// reject (for example an Info-ZIP Unicode Path field `0x7075` shorter than |
553 | | /// its 5-byte version and CRC-32 header). |
554 | | /// |
555 | | /// Opt-in and off by default, so fields are written verbatim as before: |
556 | | /// the parser rejects some fields that other software may accept. This |
557 | | /// is likely to become the default in 10.0.0. |
558 | | /// |
559 | | /// When enabled, [`Self::add_extra_field`] rejects a malformed field |
560 | | /// immediately, and starting an entry (for example with |
561 | | /// [`ZipWriter::start_file`]) rejects any malformed field added before |
562 | | /// this was enabled. |
563 | | /// |
564 | | /// [`ZipArchive`]: crate::ZipArchive |
565 | | /// [`ZipWriter::start_file`]: crate::ZipWriter::start_file |
566 | | #[must_use] |
567 | 0 | pub fn validate_extra_fields(mut self, validate: bool) -> Self { |
568 | 0 | self.extended_options.validate_extra_fields = validate; |
569 | 0 | self |
570 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::validate_extra_fields Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::validate_extra_fields |
571 | | |
572 | | /// Set the file comment. |
573 | | #[must_use] |
574 | 0 | pub fn with_file_comment<S: Into<Box<str>>>(mut self, comment: S) -> Self { |
575 | 0 | self.extended_options.file_comment = Some(comment.into()); |
576 | 0 | self |
577 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::with_file_comment::<_> Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::with_file_comment::<_> |
578 | | |
579 | | /// Adds an extra data field. |
580 | | #[deprecated = "use add_extra_field()"] |
581 | 0 | pub fn add_extra_data<D: AsRef<[u8]>>( |
582 | 0 | &mut self, |
583 | 0 | header_id: u16, |
584 | 0 | data: D, |
585 | 0 | central_only: bool, |
586 | 0 | ) -> ZipResult<()> { |
587 | 0 | self.add_extra_field(header_id, data, central_only) |
588 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::add_extra_data::<_> Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::add_extra_data::<_> |
589 | | |
590 | | /// Adds an extra field; see [`ExtendedFileOptions::add_extra_field`] for |
591 | | /// what is checked. |
592 | 0 | pub fn add_extra_field<D: AsRef<[u8]>>( |
593 | 0 | &mut self, |
594 | 0 | header_id: u16, |
595 | 0 | data: D, |
596 | 0 | central_only: bool, |
597 | 0 | ) -> ZipResult<()> { |
598 | 0 | self.extended_options |
599 | 0 | .add_extra_field(header_id, data, central_only) |
600 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::add_extra_field::<_> Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::add_extra_field::<_> |
601 | | |
602 | | /// Removes the extra fields. |
603 | | #[must_use] |
604 | | #[deprecated = "use clear_extra_fields"] |
605 | 0 | pub fn clear_extra_data(self) -> Self { |
606 | 0 | self.clear_extra_fields() |
607 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::clear_extra_data Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::clear_extra_data |
608 | | |
609 | | /// Removes the extra fields. |
610 | | #[must_use] |
611 | 0 | pub fn clear_extra_fields(mut self) -> Self { |
612 | 0 | if !self.extended_options.extra_fields.is_empty() { |
613 | 0 | self.extended_options.extra_fields = Arc::new(vec![]); |
614 | 0 | } |
615 | 0 | self |
616 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::clear_extra_fields Unexecuted instantiation: <zip::write::options::FileOptions<zip::write::options::ExtendedFileOptions>>::clear_extra_fields |
617 | | } |
618 | | impl FileOptions<'static, 'static, ()> { |
619 | | /// Constructs a const `FileOptions` object. |
620 | | /// |
621 | | /// Note: This value is different than the return value of [`FileOptions::default()`]: |
622 | | /// |
623 | | /// - The `last_modified_time` is [`DateTime::DEFAULT`]. This corresponds to 1980-01-01 00:00:00 |
624 | | pub const DEFAULT: Self = Self { |
625 | | compression_method: CompressionMethod::DEFAULT, |
626 | | compression_level: None, |
627 | | last_modified_time: DateTime::DEFAULT, |
628 | | large_file: false, |
629 | | permissions: None, |
630 | | encrypt_with: None, |
631 | | extended_options: (), |
632 | | alignment: 1, |
633 | | alignment_header_id: ExtraFieldId::DataStreamAlignment.as_u16(), |
634 | | #[cfg(feature = "deflate-zopfli")] |
635 | | zopfli_buffer_size: Some(1 << 15), |
636 | | system: None, |
637 | | name: None, |
638 | | external_attributes: None, |
639 | | }; |
640 | | } |
641 | | |
642 | | impl<'k, 'n> FileOptions<'k, 'n, ()> { |
643 | | /// Convert to `FullFileOptions`. |
644 | | #[must_use] |
645 | 0 | pub fn into_full_options(self) -> FullFileOptions<'k, 'n> { |
646 | 0 | FileOptions { |
647 | 0 | compression_method: self.compression_method, |
648 | 0 | compression_level: self.compression_level, |
649 | 0 | last_modified_time: self.last_modified_time, |
650 | 0 | permissions: self.permissions, |
651 | 0 | large_file: self.large_file, |
652 | 0 | encrypt_with: self.encrypt_with, |
653 | 0 | extended_options: ExtendedFileOptions::default(), |
654 | 0 | alignment: self.alignment, |
655 | 0 | alignment_header_id: self.alignment_header_id, |
656 | 0 | #[cfg(feature = "deflate-zopfli")] |
657 | 0 | zopfli_buffer_size: self.zopfli_buffer_size, |
658 | 0 | system: self.system, |
659 | 0 | name: self.name, |
660 | 0 | external_attributes: self.external_attributes, |
661 | 0 | } |
662 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<()>>::into_full_options Unexecuted instantiation: <zip::write::options::FileOptions<()>>::into_full_options |
663 | | } |
664 | | |
665 | | impl<T: sealed::FileOptionExtension> Default for FileOptions<'_, '_, T> { |
666 | | /// Construct a new `FileOptions` object |
667 | 0 | fn default() -> Self { |
668 | 0 | Self { |
669 | 0 | compression_method: CompressionMethod::default(), |
670 | 0 | compression_level: None, |
671 | 0 | last_modified_time: DateTime::default_for_write(), |
672 | 0 | permissions: None, |
673 | 0 | large_file: false, |
674 | 0 | encrypt_with: None, |
675 | 0 | extended_options: T::default(), |
676 | 0 | alignment: 1, |
677 | 0 | alignment_header_id: ExtraFieldId::DataStreamAlignment.as_u16(), |
678 | 0 | #[cfg(feature = "deflate-zopfli")] |
679 | 0 | zopfli_buffer_size: Some(1 << 15), |
680 | 0 | system: None, |
681 | 0 | name: None, |
682 | 0 | external_attributes: None, |
683 | 0 | } |
684 | 0 | } Unexecuted instantiation: <zip::write::options::FileOptions<()> as core::default::Default>::default Unexecuted instantiation: <zip::write::options::FileOptions<_> as core::default::Default>::default Unexecuted instantiation: <zip::write::options::FileOptions<()> as core::default::Default>::default Unexecuted instantiation: <zip::write::options::FileOptions<_> as core::default::Default>::default |
685 | | } |