1use core::fmt::Debug;
4use std::marker::PhantomData;
5use std::sync::Arc;
6
7use crate::CompressionMethod;
8use crate::datetime::DateTime;
9use crate::extra_fields::CustomExtraField;
10use crate::format::ffi;
11use crate::format::system::System;
12use crate::result::{ZipResult, invalid};
13use crate::zipcrypto::ZipCryptoKeys;
14
15#[derive(Copy, Clone, Debug, Eq, PartialEq)]
16pub(crate) enum EncryptWith<'k> {
17 #[cfg(feature = "aes-crypto")]
18 Aes {
19 mode: crate::format::aes::AesMode,
20 vendor_version: crate::format::aes::AesVendorVersion,
21 password: Option<&'k [u8]>,
23 salt: Option<crate::aes::AesSalt>,
24 },
25 ZipCrypto(ZipCryptoKeys, PhantomData<&'k ()>),
26}
27
28#[cfg(feature = "_arbitrary")]
29impl<'a> arbitrary::Arbitrary<'a> for EncryptWith<'a> {
30 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
31 #[cfg(feature = "aes-crypto")]
32 if bool::arbitrary(u)? {
33 return Ok(EncryptWith::Aes {
34 mode: crate::format::aes::AesMode::arbitrary(u)?,
35 password: Some(u.arbitrary::<&[u8]>()?),
36 vendor_version: crate::format::aes::AesVendorVersion::Ae2,
37 salt: None, });
39 }
40
41 Ok(EncryptWith::ZipCrypto(
42 ZipCryptoKeys::arbitrary(u)?,
43 PhantomData,
44 ))
45 }
46}
47
48#[non_exhaustive]
50#[derive(Clone, Debug, Copy, Eq, PartialEq)]
51pub struct FileOptions<'k, 'n, T: sealed::FileOptionExtension> {
52 pub(crate) compression_method: CompressionMethod,
53 pub(crate) compression_level: Option<i64>,
54 pub(crate) external_attributes: Option<u32>,
55 pub(crate) last_modified_time: DateTime,
56 pub(crate) permissions: Option<u32>,
57 pub(crate) large_file: bool,
58 pub(crate) encrypt_with: Option<EncryptWith<'k>>,
59 pub(crate) extended_options: T,
60 pub(crate) alignment: u16,
61 #[cfg(feature = "deflate-zopfli")]
62 pub(super) zopfli_buffer_size: Option<usize>,
63 pub(crate) system: Option<System>,
64 pub(crate) name: Option<&'n [u8]>,
65}
66pub type SimpleFileOptions = FileOptions<'static, 'static, ()>;
68
69impl FileOptions<'static, 'static, ()> {
70 pub(crate) const DEFAULT_FILE_PERMISSION: u32 = 0o644 | ffi::S_IFREG;
71 pub(crate) const DEFAULT_DIR_PERMISSIONS: u32 = 0o755 | ffi::S_IFDIR; pub(crate) const DEFAULT_SYMLINK_PERMISSIONS: u32 = 0o777 | ffi::S_IFLNK;
73}
74
75pub(crate) mod sealed {
76 use super::ExtendedFileOptions;
77 use crate::extra_fields::CustomExtraField;
78 use std::sync::Arc;
79
80 pub trait Sealed {}
81 #[doc(hidden)]
83 pub trait FileOptionExtension: Default + Sealed {
84 fn extra_fields(&self) -> Option<&Arc<Vec<CustomExtraField>>>;
86 fn file_comment(&self) -> Option<&str>;
88 fn take_file_comment(&mut self) -> Option<Box<str>>;
90 }
91 impl Sealed for () {}
92 impl FileOptionExtension for () {
93 fn extra_fields(&self) -> Option<&Arc<Vec<CustomExtraField>>> {
94 None
95 }
96 fn file_comment(&self) -> Option<&str> {
97 None
98 }
99 fn take_file_comment(&mut self) -> Option<Box<str>> {
100 None
101 }
102 }
103 impl Sealed for ExtendedFileOptions {}
104
105 impl FileOptionExtension for ExtendedFileOptions {
106 fn extra_fields(&self) -> Option<&Arc<Vec<CustomExtraField>>> {
107 Some(&self.extra_fields)
108 }
109 fn file_comment(&self) -> Option<&str> {
110 self.file_comment.as_ref().map(Box::as_ref)
111 }
112 fn take_file_comment(&mut self) -> Option<Box<str>> {
113 self.file_comment.take()
114 }
115 }
116}
117
118pub type FullFileOptions<'k, 'n> = FileOptions<'k, 'n, ExtendedFileOptions>;
120#[cfg_attr(feature = "_arbitrary", derive(arbitrary::Arbitrary))]
122#[derive(Clone, Default, Eq, PartialEq)]
123pub struct ExtendedFileOptions {
124 pub(crate) extra_fields: Arc<Vec<CustomExtraField>>,
125 pub(crate) file_comment: Option<Box<str>>,
126}
127
128impl ExtendedFileOptions {
129 #[deprecated = "use add_extra_field()"]
153 pub fn add_extra_data<D: AsRef<[u8]>>(
154 &mut self,
155 header_id: u16,
156 data: D,
157 central_only: bool,
158 ) -> ZipResult<()> {
159 self.add_extra_field(header_id, data, central_only)
160 }
161 pub fn add_extra_field<D: AsRef<[u8]>>(
185 &mut self,
186 header_id: u16,
187 data: D,
188 central_only: bool,
189 ) -> ZipResult<()> {
190 let data = data.as_ref();
191 let len = data.len() + 4;
192 let extra_fields_len: usize = self.extra_fields.iter().map(|x| x.full_size(false)).sum();
193 if extra_fields_len + len > u16::MAX as usize {
194 Err(invalid!("Extra data field would be longer than allowed"))
195 } else {
196 Arc::make_mut(&mut self.extra_fields).push(CustomExtraField::new(
197 central_only,
198 header_id,
199 data.to_vec().into_boxed_slice(),
200 ));
201 Ok(())
202 }
203 }
204}
205
206impl Debug for ExtendedFileOptions {
207 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
208 f.debug_struct("ExtendedFileOptions")
209 .field("extra_fields", &self.extra_fields)
210 .field("file_comment", &self.file_comment)
211 .finish()
212 }
213}
214
215#[cfg(feature = "_arbitrary")]
216impl<'k, 'n, 'a: 'k + 'n> arbitrary::Arbitrary<'a> for FileOptions<'k, 'n, ExtendedFileOptions> {
217 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
218 use crate::write::ZipWriter;
219
220 let mut options = FullFileOptions {
221 compression_method: CompressionMethod::arbitrary(u)?,
222 compression_level: if bool::arbitrary(u)? {
223 Some(u.int_in_range(0..=24)?)
224 } else {
225 None
226 },
227 last_modified_time: DateTime::arbitrary(u)?,
228 permissions: Option::<u32>::arbitrary(u)?,
229 large_file: bool::arbitrary(u)?,
230 encrypt_with: Option::<EncryptWith<'_>>::arbitrary(u)?,
231 alignment: u16::arbitrary(u)?,
232 #[cfg(feature = "deflate-zopfli")]
233 zopfli_buffer_size: None,
234 ..Default::default()
235 };
236 #[cfg(feature = "deflate-zopfli")]
237 if options.compression_method == CompressionMethod::Deflated && bool::arbitrary(u)? {
238 options.zopfli_buffer_size =
239 Some(if bool::arbitrary(u)? { 2 } else { 3 } << u.int_in_range(8..=20)?);
240 }
241 u.arbitrary_loop(Some(0), Some(10), |u| {
242 options
243 .add_extra_field(
244 u.int_in_range(2..=u16::MAX)?,
245 Box::<[u8]>::arbitrary(u)?,
246 bool::arbitrary(u)?,
247 )
248 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
249 Ok(core::ops::ControlFlow::Continue(()))
250 })?;
251 let len = u.arbitrary_len::<u8>()?;
252 options.name = Some(u.bytes(len)?);
253 ZipWriter::new(std::io::Cursor::new(Vec::new()))
254 .start_file("", options.clone())
255 .map_err(|_| arbitrary::Error::IncorrectFormat)?;
256 Ok(options)
257 }
258}
259
260impl<'k, 'n, T: sealed::FileOptionExtension> FileOptions<'k, 'n, T> {
261 pub(crate) fn normalize(&mut self) {
262 if !self.last_modified_time.is_valid() {
263 self.last_modified_time = FileOptions::<T>::default().last_modified_time;
264 }
265 if let Some(perms) = &mut self.permissions
267 && (*perms & !0o777 == 0)
268 {
269 *perms |= ffi::S_IFREG;
270 }
271 }
272
273 pub const fn has_encryption(&self) -> bool {
275 self.encrypt_with.is_some()
276 }
277
278 #[must_use]
284 pub const fn compression_method(mut self, method: CompressionMethod) -> Self {
285 self.compression_method = method;
286 self
287 }
288
289 #[must_use]
293 pub const fn system(mut self, system: System) -> Self {
294 self.system = Some(system);
295 self
296 }
297
298 #[must_use]
309 pub const fn compression_level(mut self, level: Option<i64>) -> Self {
310 self.compression_level = level;
311 self
312 }
313
314 #[must_use]
319 pub const fn last_modified_time(mut self, mod_time: DateTime) -> Self {
320 self.last_modified_time = mod_time;
321 self
322 }
323
324 #[must_use]
334 pub const fn unix_permissions(mut self, mode: u32) -> Self {
335 self.permissions = Some(mode & 0o777);
336 self
337 }
338
339 #[must_use]
344 pub const fn external_attributes(mut self, external_perms: u32) -> Self {
345 self.external_attributes = Some(external_perms);
346 self
347 }
348
349 #[must_use]
355 pub const fn large_file(mut self, large: bool) -> Self {
356 self.large_file = large;
357 self
358 }
359
360 pub(crate) fn with_deprecated_encryption(self, password: &'k [u8]) -> FileOptions<'k, 'n, T> {
361 FileOptions {
362 encrypt_with: Some(EncryptWith::ZipCrypto(
363 ZipCryptoKeys::derive(password),
364 PhantomData,
365 )),
366 ..self
367 }
368 }
369
370 #[cfg(feature = "aes-crypto")]
375 pub fn with_aes_encryption_and_salt(
376 self,
377 password: &'k [u8],
378 salt: crate::aes::AesSalt,
379 ) -> FileOptions<'k, 'n, T> {
380 FileOptions {
381 encrypt_with: Some(EncryptWith::Aes {
382 mode: salt.mode(),
383 password: Some(password),
384 vendor_version: crate::format::aes::AesVendorVersion::Ae2,
385 salt: Some(salt),
386 }),
387 ..self
388 }
389 }
390
391 #[cfg(feature = "aes-crypto")]
393 pub fn with_aes_encryption(
394 self,
395 mode: crate::AesMode,
396 password: &'k str,
397 ) -> FileOptions<'k, 'n, T> {
398 self.with_aes_encryption_bytes(mode, password.as_bytes())
399 }
400
401 #[cfg(feature = "aes-crypto")]
403 pub fn with_aes_encryption_bytes(
404 self,
405 mode: crate::AesMode,
406 password: &'k [u8],
407 ) -> FileOptions<'k, 'n, T> {
408 FileOptions {
409 encrypt_with: Some(EncryptWith::Aes {
410 mode,
411 password: Some(password),
412 vendor_version: crate::format::aes::AesVendorVersion::Ae2,
413 salt: None,
414 }),
415 ..self
416 }
417 }
418
419 #[must_use]
424 #[cfg(feature = "deflate-zopfli")]
425 pub const fn with_zopfli_buffer(mut self, size: Option<usize>) -> Self {
426 self.zopfli_buffer_size = size;
427 self
428 }
429
430 pub const fn get_compression_level(&self) -> Option<i64> {
432 self.compression_level
433 }
434 #[must_use]
436 pub const fn with_alignment(mut self, alignment: u16) -> Self {
437 self.alignment = alignment;
438 self
439 }
440}
441impl FileOptions<'_, '_, ExtendedFileOptions> {
442 #[must_use]
444 pub fn with_file_comment<S: Into<Box<str>>>(mut self, comment: S) -> Self {
445 self.extended_options.file_comment = Some(comment.into());
446 self
447 }
448
449 #[deprecated = "use add_extra_field()"]
451 pub fn add_extra_data<D: AsRef<[u8]>>(
452 &mut self,
453 header_id: u16,
454 data: D,
455 central_only: bool,
456 ) -> ZipResult<()> {
457 self.add_extra_field(header_id, data, central_only)
458 }
459
460 pub fn add_extra_field<D: AsRef<[u8]>>(
462 &mut self,
463 header_id: u16,
464 data: D,
465 central_only: bool,
466 ) -> ZipResult<()> {
467 self.extended_options
468 .add_extra_field(header_id, data, central_only)
469 }
470
471 #[must_use]
473 #[deprecated = "use clear_extra_fields"]
474 pub fn clear_extra_data(self) -> Self {
475 self.clear_extra_fields()
476 }
477
478 #[must_use]
480 pub fn clear_extra_fields(mut self) -> Self {
481 if !self.extended_options.extra_fields.is_empty() {
482 self.extended_options.extra_fields = Arc::new(vec![]);
483 }
484 self
485 }
486}
487impl FileOptions<'static, 'static, ()> {
488 pub const DEFAULT: Self = Self {
494 compression_method: CompressionMethod::DEFAULT,
495 compression_level: None,
496 last_modified_time: DateTime::DEFAULT,
497 large_file: false,
498 permissions: None,
499 encrypt_with: None,
500 extended_options: (),
501 alignment: 1,
502 #[cfg(feature = "deflate-zopfli")]
503 zopfli_buffer_size: Some(1 << 15),
504 system: None,
505 name: None,
506 external_attributes: None,
507 };
508}
509
510impl<'k, 'n> FileOptions<'k, 'n, ()> {
511 #[must_use]
513 pub fn into_full_options(self) -> FullFileOptions<'k, 'n> {
514 FileOptions {
515 compression_method: self.compression_method,
516 compression_level: self.compression_level,
517 last_modified_time: self.last_modified_time,
518 permissions: self.permissions,
519 large_file: self.large_file,
520 encrypt_with: self.encrypt_with,
521 extended_options: ExtendedFileOptions::default(),
522 alignment: self.alignment,
523 #[cfg(feature = "deflate-zopfli")]
524 zopfli_buffer_size: self.zopfli_buffer_size,
525 system: self.system,
526 name: self.name,
527 external_attributes: self.external_attributes,
528 }
529 }
530}
531
532impl<T: sealed::FileOptionExtension> Default for FileOptions<'_, '_, T> {
533 fn default() -> Self {
535 Self {
536 compression_method: CompressionMethod::default(),
537 compression_level: None,
538 last_modified_time: DateTime::default_for_write(),
539 permissions: None,
540 large_file: false,
541 encrypt_with: None,
542 extended_options: T::default(),
543 alignment: 1,
544 #[cfg(feature = "deflate-zopfli")]
545 zopfli_buffer_size: Some(1 << 15),
546 system: None,
547 name: None,
548 external_attributes: None,
549 }
550 }
551}