1use crate::cp437::FromCp437;
3use crate::write::{FileOptionExtension, FileOptions};
4use path::{Component, Path, PathBuf};
5use std::cmp::Ordering;
6use std::ffi::OsStr;
7use std::fmt;
8use std::fmt::{Debug, Formatter};
9use std::mem;
10use std::path;
11use std::sync::{Arc, OnceLock};
12
13#[cfg(feature = "chrono")]
14use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
15
16use crate::result::{ZipError, ZipResult};
17use crate::spec::{self, FixedSizeBlock, Pod};
18
19pub(crate) mod ffi {
20 pub const S_IFDIR: u32 = 0o0040000;
21 pub const S_IFREG: u32 = 0o0100000;
22 pub const S_IFLNK: u32 = 0o0120000;
23}
24
25use crate::extra_fields::ExtraField;
26use crate::result::DateTimeRangeError;
27use crate::spec::is_dir;
28use crate::types::ffi::S_IFDIR;
29use crate::{CompressionMethod, ZIP64_BYTES_THR};
30#[cfg(feature = "time")]
31use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
32
33pub(crate) struct ZipRawValues {
34 pub(crate) crc32: u32,
35 pub(crate) compressed_size: u64,
36 pub(crate) uncompressed_size: u64,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
40#[repr(u8)]
41pub enum System {
42 Dos = 0,
43 Unix = 3,
44 #[default]
45 Unknown,
46}
47
48impl From<u8> for System {
49 fn from(system: u8) -> Self {
50 match system {
51 0 => Self::Dos,
52 3 => Self::Unix,
53 _ => Self::Unknown,
54 }
55 }
56}
57
58impl From<System> for u8 {
59 fn from(system: System) -> Self {
60 match system {
61 System::Dos => 0,
62 System::Unix => 3,
63 System::Unknown => 4,
64 }
65 }
66}
67
68#[derive(Clone, Copy, Eq, Hash, PartialEq)]
85pub struct DateTime {
86 datepart: u16,
87 timepart: u16,
88}
89
90impl Debug for DateTime {
91 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
92 if *self == Self::default() {
93 return f.write_str("DateTime::default()");
94 }
95 f.write_fmt(format_args!(
96 "DateTime::from_date_and_time({}, {}, {}, {}, {}, {})?",
97 self.year(),
98 self.month(),
99 self.day(),
100 self.hour(),
101 self.minute(),
102 self.second()
103 ))
104 }
105}
106
107impl Ord for DateTime {
108 fn cmp(&self, other: &Self) -> Ordering {
109 if let ord @ (Ordering::Less | Ordering::Greater) = self.year().cmp(&other.year()) {
110 return ord;
111 }
112 if let ord @ (Ordering::Less | Ordering::Greater) = self.month().cmp(&other.month()) {
113 return ord;
114 }
115 if let ord @ (Ordering::Less | Ordering::Greater) = self.day().cmp(&other.day()) {
116 return ord;
117 }
118 if let ord @ (Ordering::Less | Ordering::Greater) = self.hour().cmp(&other.hour()) {
119 return ord;
120 }
121 if let ord @ (Ordering::Less | Ordering::Greater) = self.minute().cmp(&other.minute()) {
122 return ord;
123 }
124 self.second().cmp(&other.second())
125 }
126}
127
128impl PartialOrd for DateTime {
129 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
130 Some(self.cmp(other))
131 }
132}
133
134impl DateTime {
135 #[cfg(feature = "time")]
137 pub fn default_for_write() -> Self {
138 OffsetDateTime::now_utc()
139 .try_into()
140 .unwrap_or_else(|_| DateTime::default())
141 }
142
143 #[cfg(not(feature = "time"))]
145 pub fn default_for_write() -> Self {
146 DateTime::default()
147 }
148}
149
150#[cfg(fuzzing)]
151impl arbitrary::Arbitrary<'_> for DateTime {
152 fn arbitrary(u: &mut arbitrary::Unstructured) -> arbitrary::Result<Self> {
153 let year: u16 = u.int_in_range(1980..=2107)?;
154 let month: u16 = u.int_in_range(1..=12)?;
155 let day: u16 = u.int_in_range(1..=31)?;
156 let datepart = day | (month << 5) | ((year - 1980) << 9);
157 let hour: u16 = u.int_in_range(0..=23)?;
158 let minute: u16 = u.int_in_range(0..=59)?;
159 let second: u16 = u.int_in_range(0..=58)?;
160 let timepart = (second >> 1) | (minute << 5) | (hour << 11);
161 Ok(DateTime { datepart, timepart })
162 }
163}
164
165#[cfg(feature = "chrono")]
166impl TryFrom<NaiveDateTime> for DateTime {
167 type Error = DateTimeRangeError;
168
169 fn try_from(value: NaiveDateTime) -> Result<Self, Self::Error> {
170 DateTime::from_date_and_time(
171 value.year().try_into()?,
172 value.month().try_into()?,
173 value.day().try_into()?,
174 value.hour().try_into()?,
175 value.minute().try_into()?,
176 value.second().try_into()?,
177 )
178 }
179}
180
181#[cfg(feature = "chrono")]
182impl TryFrom<DateTime> for NaiveDateTime {
183 type Error = DateTimeRangeError;
184
185 fn try_from(value: DateTime) -> Result<Self, Self::Error> {
186 let date = NaiveDate::from_ymd_opt(
187 value.year().into(),
188 value.month().into(),
189 value.day().into(),
190 )
191 .ok_or(DateTimeRangeError)?;
192 let time = NaiveTime::from_hms_opt(
193 value.hour().into(),
194 value.minute().into(),
195 value.second().into(),
196 )
197 .ok_or(DateTimeRangeError)?;
198 Ok(NaiveDateTime::new(date, time))
199 }
200}
201
202impl TryFrom<(u16, u16)> for DateTime {
203 type Error = DateTimeRangeError;
204
205 #[inline]
206 fn try_from(values: (u16, u16)) -> Result<Self, Self::Error> {
207 Self::try_from_msdos(values.0, values.1)
208 }
209}
210
211impl From<DateTime> for (u16, u16) {
212 #[inline]
213 fn from(dt: DateTime) -> Self {
214 (dt.datepart(), dt.timepart())
215 }
216}
217
218impl Default for DateTime {
219 fn default() -> DateTime {
221 DateTime {
222 datepart: 0b0000000000100001,
223 timepart: 0,
224 }
225 }
226}
227
228impl fmt::Display for DateTime {
229 #[inline]
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 write!(
232 f,
233 "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
234 self.year(),
235 self.month(),
236 self.day(),
237 self.hour(),
238 self.minute(),
239 self.second()
240 )
241 }
242}
243
244impl DateTime {
245 pub const unsafe fn from_msdos_unchecked(datepart: u16, timepart: u16) -> DateTime {
250 DateTime { datepart, timepart }
251 }
252
253 pub fn try_from_msdos(datepart: u16, timepart: u16) -> Result<DateTime, DateTimeRangeError> {
256 let seconds = (timepart & 0b0000000000011111) << 1;
257 let minutes = (timepart & 0b0000011111100000) >> 5;
258 let hours = (timepart & 0b1111100000000000) >> 11;
259 let days = datepart & 0b0000000000011111;
260 let months = (datepart & 0b0000000111100000) >> 5;
261 let years = (datepart & 0b1111111000000000) >> 9;
262 Self::from_date_and_time(
263 years.checked_add(1980).ok_or(DateTimeRangeError)?,
264 months.try_into()?,
265 days.try_into()?,
266 hours.try_into()?,
267 minutes.try_into()?,
268 seconds.try_into()?,
269 )
270 }
271
272 pub fn from_date_and_time(
282 year: u16,
283 month: u8,
284 day: u8,
285 hour: u8,
286 minute: u8,
287 second: u8,
288 ) -> Result<DateTime, DateTimeRangeError> {
289 fn is_leap_year(year: u16) -> bool {
290 (year % 4 == 0) && ((year % 25 != 0) || (year % 16 == 0))
291 }
292
293 if (1980..=2107).contains(&year)
294 && (1..=12).contains(&month)
295 && (1..=31).contains(&day)
296 && hour <= 23
297 && minute <= 59
298 && second <= 60
299 {
300 let second = second.min(58); let max_day = match month {
302 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
303 4 | 6 | 9 | 11 => 30,
304 2 if is_leap_year(year) => 29,
305 2 => 28,
306 _ => unreachable!(),
307 };
308 if day > max_day {
309 return Err(DateTimeRangeError);
310 }
311 let datepart = (day as u16) | ((month as u16) << 5) | ((year - 1980) << 9);
312 let timepart = ((second as u16) >> 1) | ((minute as u16) << 5) | ((hour as u16) << 11);
313 Ok(DateTime { datepart, timepart })
314 } else {
315 Err(DateTimeRangeError)
316 }
317 }
318
319 pub fn is_valid(&self) -> bool {
321 Self::try_from_msdos(self.datepart, self.timepart).is_ok()
322 }
323
324 #[cfg(feature = "time")]
325 #[deprecated(since = "0.6.4", note = "use `DateTime::try_from()` instead")]
329 pub fn from_time(dt: OffsetDateTime) -> Result<DateTime, DateTimeRangeError> {
330 dt.try_into()
331 }
332
333 pub const fn timepart(&self) -> u16 {
335 self.timepart
336 }
337
338 pub const fn datepart(&self) -> u16 {
340 self.datepart
341 }
342
343 #[cfg(feature = "time")]
344 #[deprecated(since = "1.3.1", note = "use `OffsetDateTime::try_from()` instead")]
346 pub fn to_time(&self) -> Result<OffsetDateTime, ComponentRange> {
347 (*self).try_into()
348 }
349
350 pub const fn year(&self) -> u16 {
352 (self.datepart >> 9) + 1980
353 }
354
355 pub const fn month(&self) -> u8 {
361 ((self.datepart & 0b0000000111100000) >> 5) as u8
362 }
363
364 pub const fn day(&self) -> u8 {
370 (self.datepart & 0b0000000000011111) as u8
371 }
372
373 pub const fn hour(&self) -> u8 {
379 (self.timepart >> 11) as u8
380 }
381
382 pub const fn minute(&self) -> u8 {
388 ((self.timepart & 0b0000011111100000) >> 5) as u8
389 }
390
391 pub const fn second(&self) -> u8 {
397 ((self.timepart & 0b0000000000011111) << 1) as u8
398 }
399}
400
401#[cfg(feature = "time")]
402impl TryFrom<OffsetDateTime> for DateTime {
403 type Error = DateTimeRangeError;
404
405 fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
406 Self::from_date_and_time(
407 dt.year().try_into()?,
408 dt.month().into(),
409 dt.day(),
410 dt.hour(),
411 dt.minute(),
412 dt.second(),
413 )
414 }
415}
416
417#[cfg(feature = "time")]
418impl TryFrom<DateTime> for OffsetDateTime {
419 type Error = ComponentRange;
420
421 fn try_from(dt: DateTime) -> Result<Self, Self::Error> {
422 let date =
423 Date::from_calendar_date(dt.year() as i32, Month::try_from(dt.month())?, dt.day())?;
424 let time = Time::from_hms(dt.hour(), dt.minute(), dt.second())?;
425 Ok(PrimitiveDateTime::new(date, time).assume_utc())
426 }
427}
428
429pub const MIN_VERSION: u8 = 10;
430pub const DEFAULT_VERSION: u8 = 45;
431
432#[derive(Debug, Clone, Default)]
434pub struct ZipFileData {
435 pub system: System,
437 pub version_made_by: u8,
439 pub encrypted: bool,
441 pub is_utf8: bool,
443 pub using_data_descriptor: bool,
445 pub compression_method: crate::compression::CompressionMethod,
447 pub compression_level: Option<i64>,
449 pub last_modified_time: Option<DateTime>,
451 pub crc32: u32,
453 pub compressed_size: u64,
455 pub uncompressed_size: u64,
457 pub file_name: Box<str>,
459 pub file_name_raw: Box<[u8]>,
461 pub extra_field: Option<Arc<Vec<u8>>>,
463 pub central_extra_field: Option<Arc<Vec<u8>>>,
465 pub file_comment: Box<str>,
467 pub header_start: u64,
469 pub extra_data_start: Option<u64>,
471 pub central_header_start: u64,
475 pub data_start: OnceLock<u64>,
477 pub external_attributes: u32,
479 pub large_file: bool,
481 pub aes_mode: Option<(AesMode, AesVendorVersion, CompressionMethod)>,
483 pub aes_extra_data_start: u64,
485
486 pub extra_fields: Vec<ExtraField>,
488}
489
490impl ZipFileData {
491 pub fn data_start(&self) -> u64 {
493 *self.data_start.get().unwrap()
494 }
495
496 #[allow(dead_code)]
497 pub fn is_dir(&self) -> bool {
498 is_dir(&self.file_name)
499 }
500
501 pub fn file_name_sanitized(&self) -> PathBuf {
502 let no_null_filename = match self.file_name.find('\0') {
503 Some(index) => &self.file_name[0..index],
504 None => &self.file_name,
505 }
506 .to_string();
507
508 let separator = path::MAIN_SEPARATOR;
512 let opposite_separator = match separator {
513 '/' => '\\',
514 _ => '/',
515 };
516 let filename =
517 no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string());
518
519 Path::new(&filename)
520 .components()
521 .filter(|component| matches!(*component, Component::Normal(..)))
522 .fold(PathBuf::new(), |mut path, ref cur| {
523 path.push(cur.as_os_str());
524 path
525 })
526 }
527
528 pub(crate) fn simplified_components(&self) -> Option<Vec<&OsStr>> {
530 if self.file_name.contains('\0') {
531 return None;
532 }
533 let input = Path::new(OsStr::new(&*self.file_name));
534 crate::path::simplified_components(input)
535 }
536
537 pub(crate) fn enclosed_name(&self) -> Option<PathBuf> {
538 if self.file_name.contains('\0') {
539 return None;
540 }
541 let path = PathBuf::from(self.file_name.to_string());
542 let mut depth = 0usize;
543 for component in path.components() {
544 match component {
545 Component::Prefix(_) | Component::RootDir => return None,
546 Component::ParentDir => depth = depth.checked_sub(1)?,
547 Component::Normal(_) => depth += 1,
548 Component::CurDir => (),
549 }
550 }
551 Some(path)
552 }
553
554 pub(crate) const fn unix_mode(&self) -> Option<u32> {
556 if self.external_attributes == 0 {
557 return None;
558 }
559
560 match self.system {
561 System::Unix => Some(self.external_attributes >> 16),
562 System::Dos => {
563 let mut mode = if 0x10 == (self.external_attributes & 0x10) {
565 ffi::S_IFDIR | 0o0775
566 } else {
567 ffi::S_IFREG | 0o0664
568 };
569 if 0x01 == (self.external_attributes & 0x01) {
570 mode &= 0o0555;
572 }
573 Some(mode)
574 }
575 _ => None,
576 }
577 }
578
579 pub fn version_needed(&self) -> u16 {
581 let compression_version: u16 = match self.compression_method {
582 CompressionMethod::Stored => MIN_VERSION.into(),
583 #[cfg(feature = "_deflate-any")]
584 CompressionMethod::Deflated => 20,
585 #[cfg(feature = "bzip2")]
586 CompressionMethod::Bzip2 => 46,
587 #[cfg(feature = "deflate64")]
588 CompressionMethod::Deflate64 => 21,
589 #[cfg(feature = "lzma")]
590 CompressionMethod::Lzma => 63,
591 #[cfg(feature = "xz")]
592 CompressionMethod::Xz => 63,
593 _ => DEFAULT_VERSION as u16,
595 };
596 let crypto_version: u16 = if self.aes_mode.is_some() {
597 51
598 } else if self.encrypted {
599 20
600 } else {
601 10
602 };
603 let misc_feature_version: u16 = if self.large_file {
604 45
605 } else if self
606 .unix_mode()
607 .is_some_and(|mode| mode & S_IFDIR == S_IFDIR)
608 {
609 20
611 } else {
612 10
613 };
614 compression_version
615 .max(crypto_version)
616 .max(misc_feature_version)
617 }
618 #[inline(always)]
619 pub(crate) fn extra_field_len(&self) -> usize {
620 self.extra_field
621 .as_ref()
622 .map(|v| v.len())
623 .unwrap_or_default()
624 }
625 #[inline(always)]
626 pub(crate) fn central_extra_field_len(&self) -> usize {
627 self.central_extra_field
628 .as_ref()
629 .map(|v| v.len())
630 .unwrap_or_default()
631 }
632
633 #[allow(clippy::too_many_arguments)]
634 pub(crate) fn initialize_local_block<S, T: FileOptionExtension>(
635 name: S,
636 options: &FileOptions<T>,
637 raw_values: ZipRawValues,
638 header_start: u64,
639 extra_data_start: Option<u64>,
640 aes_extra_data_start: u64,
641 compression_method: crate::compression::CompressionMethod,
642 aes_mode: Option<(AesMode, AesVendorVersion, CompressionMethod)>,
643 extra_field: &[u8],
644 ) -> Self
645 where
646 S: ToString,
647 {
648 let permissions = options.permissions.unwrap_or(0o100644);
649 let file_name: Box<str> = name.to_string().into_boxed_str();
650 let file_name_raw: Box<[u8]> = file_name.bytes().collect();
651 let mut local_block = ZipFileData {
652 system: System::Unix,
653 version_made_by: DEFAULT_VERSION,
654 encrypted: options.encrypt_with.is_some(),
655 using_data_descriptor: false,
656 is_utf8: !file_name.is_ascii(),
657 compression_method,
658 compression_level: options.compression_level,
659 last_modified_time: Some(options.last_modified_time),
660 crc32: raw_values.crc32,
661 compressed_size: raw_values.compressed_size,
662 uncompressed_size: raw_values.uncompressed_size,
663 file_name, file_name_raw,
665 extra_field: Some(extra_field.to_vec().into()),
666 central_extra_field: options.extended_options.central_extra_data().cloned(),
667 file_comment: String::with_capacity(0).into_boxed_str(),
668 header_start,
669 data_start: OnceLock::new(),
670 central_header_start: 0,
671 external_attributes: permissions << 16,
672 large_file: options.large_file,
673 aes_mode,
674 extra_fields: Vec::new(),
675 extra_data_start,
676 aes_extra_data_start,
677 };
678 local_block.version_made_by = local_block.version_needed() as u8;
679 local_block
680 }
681
682 pub(crate) fn from_local_block<R: std::io::Read>(
683 block: ZipLocalEntryBlock,
684 reader: &mut R,
685 ) -> ZipResult<Self> {
686 let ZipLocalEntryBlock {
687 version_made_by,
689 flags,
690 compression_method,
691 last_mod_time,
692 last_mod_date,
693 crc32,
694 compressed_size,
695 uncompressed_size,
696 file_name_length,
697 extra_field_length,
698 ..
699 } = block;
700
701 let encrypted: bool = flags & 1 == 1;
702 if encrypted {
703 return Err(ZipError::UnsupportedArchive(
704 "Encrypted files are not supported",
705 ));
706 }
707
708 let using_data_descriptor: bool = flags & (1 << 3) == 1 << 3;
711 if using_data_descriptor {
712 return Err(ZipError::UnsupportedArchive(
713 "The file length is not available in the local header",
714 ));
715 }
716
717 let is_utf8: bool = flags & (1 << 11) != 0;
719 let compression_method = crate::CompressionMethod::parse_from_u16(compression_method);
720 let file_name_length: usize = file_name_length.into();
721 let extra_field_length: usize = extra_field_length.into();
722
723 let mut file_name_raw = vec![0u8; file_name_length];
724 reader.read_exact(&mut file_name_raw)?;
725 let mut extra_field = vec![0u8; extra_field_length];
726 reader.read_exact(&mut extra_field)?;
727
728 let file_name: Box<str> = match is_utf8 {
729 true => String::from_utf8_lossy(&file_name_raw).into(),
730 false => file_name_raw.clone().from_cp437().into(),
731 };
732
733 let system: u8 = (version_made_by >> 8).try_into().unwrap();
734 Ok(ZipFileData {
735 system: System::from(system),
736 version_made_by: version_made_by as u8,
738 encrypted,
739 using_data_descriptor,
740 is_utf8,
741 compression_method,
742 compression_level: None,
743 last_modified_time: DateTime::try_from_msdos(last_mod_date, last_mod_time).ok(),
744 crc32,
745 compressed_size: compressed_size.into(),
746 uncompressed_size: uncompressed_size.into(),
747 file_name,
748 file_name_raw: file_name_raw.into(),
749 extra_field: Some(Arc::new(extra_field)),
750 central_extra_field: None,
751 file_comment: String::with_capacity(0).into_boxed_str(), header_start: 0,
755 data_start: OnceLock::new(),
756 central_header_start: 0,
757 external_attributes: 0,
761 large_file: false,
762 aes_mode: None,
763 extra_fields: Vec::new(),
764 extra_data_start: None,
765 aes_extra_data_start: 0,
766 })
767 }
768
769 fn is_utf8(&self) -> bool {
770 std::str::from_utf8(&self.file_name_raw).is_ok()
771 }
772
773 fn is_ascii(&self) -> bool {
774 self.file_name_raw.is_ascii()
775 }
776
777 fn flags(&self) -> u16 {
778 let utf8_bit: u16 = if self.is_utf8() && !self.is_ascii() {
779 1u16 << 11
780 } else {
781 0
782 };
783 let encrypted_bit: u16 = if self.encrypted { 1u16 << 0 } else { 0 };
784
785 utf8_bit | encrypted_bit
786 }
787
788 fn clamp_size_field(&self, field: u64) -> u32 {
789 if self.large_file {
790 spec::ZIP64_BYTES_THR as u32
791 } else {
792 field.min(spec::ZIP64_BYTES_THR).try_into().unwrap()
793 }
794 }
795
796 pub(crate) fn local_block(&self) -> ZipResult<ZipLocalEntryBlock> {
797 let compressed_size: u32 = self.clamp_size_field(self.compressed_size);
798 let uncompressed_size: u32 = self.clamp_size_field(self.uncompressed_size);
799 let extra_field_length: u16 = self
800 .extra_field_len()
801 .try_into()
802 .map_err(|_| ZipError::InvalidArchive("Extra data field is too large"))?;
803
804 let last_modified_time = self
805 .last_modified_time
806 .unwrap_or_else(DateTime::default_for_write);
807 Ok(ZipLocalEntryBlock {
808 magic: ZipLocalEntryBlock::MAGIC,
809 version_made_by: self.version_needed(),
810 flags: self.flags(),
811 compression_method: self.compression_method.serialize_to_u16(),
812 last_mod_time: last_modified_time.timepart(),
813 last_mod_date: last_modified_time.datepart(),
814 crc32: self.crc32,
815 compressed_size,
816 uncompressed_size,
817 file_name_length: self.file_name_raw.len().try_into().unwrap(),
818 extra_field_length,
819 })
820 }
821
822 pub(crate) fn block(&self) -> ZipResult<ZipCentralEntryBlock> {
823 let extra_field_len: u16 = self.extra_field_len().try_into().unwrap();
824 let central_extra_field_len: u16 = self.central_extra_field_len().try_into().unwrap();
825 let last_modified_time = self
826 .last_modified_time
827 .unwrap_or_else(DateTime::default_for_write);
828 let version_to_extract = self.version_needed();
829 let version_made_by = (self.version_made_by as u16).max(version_to_extract);
830 Ok(ZipCentralEntryBlock {
831 magic: ZipCentralEntryBlock::MAGIC,
832 version_made_by: ((self.system as u16) << 8) | version_made_by,
833 version_to_extract,
834 flags: self.flags(),
835 compression_method: self.compression_method.serialize_to_u16(),
836 last_mod_time: last_modified_time.timepart(),
837 last_mod_date: last_modified_time.datepart(),
838 crc32: self.crc32,
839 compressed_size: self
840 .compressed_size
841 .min(spec::ZIP64_BYTES_THR)
842 .try_into()
843 .unwrap(),
844 uncompressed_size: self
845 .uncompressed_size
846 .min(spec::ZIP64_BYTES_THR)
847 .try_into()
848 .unwrap(),
849 file_name_length: self.file_name_raw.len().try_into().unwrap(),
850 extra_field_length: extra_field_len.checked_add(central_extra_field_len).ok_or(
851 ZipError::InvalidArchive("Extra field length in central directory exceeds 64KiB"),
852 )?,
853 file_comment_length: self.file_comment.len().try_into().unwrap(),
854 disk_number: 0,
855 internal_file_attributes: 0,
856 external_file_attributes: self.external_attributes,
857 offset: self
858 .header_start
859 .min(spec::ZIP64_BYTES_THR)
860 .try_into()
861 .unwrap(),
862 })
863 }
864
865 pub(crate) fn zip64_extra_field_block(&self) -> Option<Zip64ExtraFieldBlock> {
866 Zip64ExtraFieldBlock::maybe_new(
867 self.large_file,
868 self.uncompressed_size,
869 self.compressed_size,
870 self.header_start,
871 )
872 }
873}
874
875#[derive(Copy, Clone, Debug)]
876#[repr(packed, C)]
877pub(crate) struct ZipCentralEntryBlock {
878 magic: spec::Magic,
879 pub version_made_by: u16,
880 pub version_to_extract: u16,
881 pub flags: u16,
882 pub compression_method: u16,
883 pub last_mod_time: u16,
884 pub last_mod_date: u16,
885 pub crc32: u32,
886 pub compressed_size: u32,
887 pub uncompressed_size: u32,
888 pub file_name_length: u16,
889 pub extra_field_length: u16,
890 pub file_comment_length: u16,
891 pub disk_number: u16,
892 pub internal_file_attributes: u16,
893 pub external_file_attributes: u32,
894 pub offset: u32,
895}
896
897unsafe impl Pod for ZipCentralEntryBlock {}
898
899impl FixedSizeBlock for ZipCentralEntryBlock {
900 const MAGIC: spec::Magic = spec::Magic::CENTRAL_DIRECTORY_HEADER_SIGNATURE;
901
902 #[inline(always)]
903 fn magic(self) -> spec::Magic {
904 self.magic
905 }
906
907 const WRONG_MAGIC_ERROR: ZipError =
908 ZipError::InvalidArchive("Invalid Central Directory header");
909
910 to_and_from_le![
911 (magic, spec::Magic),
912 (version_made_by, u16),
913 (version_to_extract, u16),
914 (flags, u16),
915 (compression_method, u16),
916 (last_mod_time, u16),
917 (last_mod_date, u16),
918 (crc32, u32),
919 (compressed_size, u32),
920 (uncompressed_size, u32),
921 (file_name_length, u16),
922 (extra_field_length, u16),
923 (file_comment_length, u16),
924 (disk_number, u16),
925 (internal_file_attributes, u16),
926 (external_file_attributes, u32),
927 (offset, u32),
928 ];
929}
930
931#[derive(Copy, Clone, Debug)]
932#[repr(packed, C)]
933pub(crate) struct ZipLocalEntryBlock {
934 magic: spec::Magic,
935 pub version_made_by: u16,
936 pub flags: u16,
937 pub compression_method: u16,
938 pub last_mod_time: u16,
939 pub last_mod_date: u16,
940 pub crc32: u32,
941 pub compressed_size: u32,
942 pub uncompressed_size: u32,
943 pub file_name_length: u16,
944 pub extra_field_length: u16,
945}
946
947unsafe impl Pod for ZipLocalEntryBlock {}
948
949impl FixedSizeBlock for ZipLocalEntryBlock {
950 const MAGIC: spec::Magic = spec::Magic::LOCAL_FILE_HEADER_SIGNATURE;
951
952 #[inline(always)]
953 fn magic(self) -> spec::Magic {
954 self.magic
955 }
956
957 const WRONG_MAGIC_ERROR: ZipError = ZipError::InvalidArchive("Invalid local file header");
958
959 to_and_from_le![
960 (magic, spec::Magic),
961 (version_made_by, u16),
962 (flags, u16),
963 (compression_method, u16),
964 (last_mod_time, u16),
965 (last_mod_date, u16),
966 (crc32, u32),
967 (compressed_size, u32),
968 (uncompressed_size, u32),
969 (file_name_length, u16),
970 (extra_field_length, u16),
971 ];
972}
973
974#[derive(Copy, Clone, Debug)]
975pub(crate) struct Zip64ExtraFieldBlock {
976 magic: spec::ExtraFieldMagic,
977 size: u16,
978 uncompressed_size: Option<u64>,
979 compressed_size: Option<u64>,
980 header_start: Option<u64>,
981 }
984
985impl Zip64ExtraFieldBlock {
986 pub(crate) fn maybe_new(
987 large_file: bool,
988 uncompressed_size: u64,
989 compressed_size: u64,
990 header_start: u64,
991 ) -> Option<Zip64ExtraFieldBlock> {
992 let mut size: u16 = 0;
993 let uncompressed_size = if uncompressed_size >= ZIP64_BYTES_THR || large_file {
994 size += mem::size_of::<u64>() as u16;
995 Some(uncompressed_size)
996 } else {
997 None
998 };
999 let compressed_size = if compressed_size >= ZIP64_BYTES_THR || large_file {
1000 size += mem::size_of::<u64>() as u16;
1001 Some(compressed_size)
1002 } else {
1003 None
1004 };
1005 let header_start = if header_start >= ZIP64_BYTES_THR {
1006 size += mem::size_of::<u64>() as u16;
1007 Some(header_start)
1008 } else {
1009 None
1010 };
1011 if size == 0 {
1012 return None;
1013 }
1014
1015 Some(Zip64ExtraFieldBlock {
1016 magic: spec::ExtraFieldMagic::ZIP64_EXTRA_FIELD_TAG,
1017 size,
1018 uncompressed_size,
1019 compressed_size,
1020 header_start,
1021 })
1022 }
1023}
1024
1025impl Zip64ExtraFieldBlock {
1026 pub fn full_size(&self) -> usize {
1027 assert!(self.size > 0);
1028 self.size as usize + mem::size_of::<spec::ExtraFieldMagic>() + mem::size_of::<u16>()
1029 }
1030
1031 pub fn serialize(self) -> Box<[u8]> {
1032 let Self {
1033 magic,
1034 size,
1035 uncompressed_size,
1036 compressed_size,
1037 header_start,
1038 } = self;
1039
1040 let full_size = self.full_size();
1041
1042 let mut ret = Vec::with_capacity(full_size);
1043 ret.extend(magic.to_le_bytes());
1044 ret.extend(u16::to_le_bytes(size));
1045
1046 if let Some(uncompressed_size) = uncompressed_size {
1047 ret.extend(u64::to_le_bytes(uncompressed_size));
1048 }
1049 if let Some(compressed_size) = compressed_size {
1050 ret.extend(u64::to_le_bytes(compressed_size));
1051 }
1052 if let Some(header_start) = header_start {
1053 ret.extend(u64::to_le_bytes(header_start));
1054 }
1055 debug_assert_eq!(ret.len(), full_size);
1056
1057 ret.into_boxed_slice()
1058 }
1059}
1060
1061#[derive(Copy, Clone, Debug)]
1066#[repr(u16)]
1067pub enum AesVendorVersion {
1068 Ae1 = 0x0001,
1069 Ae2 = 0x0002,
1070}
1071
1072#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1074#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
1075#[repr(u8)]
1076pub enum AesMode {
1077 Aes128 = 0x01,
1079 Aes192 = 0x02,
1081 Aes256 = 0x03,
1083}
1084
1085#[cfg(feature = "aes-crypto")]
1086impl AesMode {
1087 pub const fn salt_length(&self) -> usize {
1089 self.key_length() / 2
1090 }
1091
1092 pub const fn key_length(&self) -> usize {
1094 match self {
1095 Self::Aes128 => 16,
1096 Self::Aes192 => 24,
1097 Self::Aes256 => 32,
1098 }
1099 }
1100}
1101
1102#[cfg(test)]
1103mod test {
1104 #[test]
1105 fn system() {
1106 use super::System;
1107 assert_eq!(u8::from(System::Dos), 0u8);
1108 assert_eq!(System::Dos as u8, 0u8);
1109 assert_eq!(System::Unix as u8, 3u8);
1110 assert_eq!(u8::from(System::Unix), 3u8);
1111 assert_eq!(System::from(0), System::Dos);
1112 assert_eq!(System::from(3), System::Unix);
1113 assert_eq!(u8::from(System::Unknown), 4u8);
1114 assert_eq!(System::Unknown as u8, 4u8);
1115 }
1116
1117 #[test]
1118 fn sanitize() {
1119 use super::*;
1120 let file_name = "/path/../../../../etc/./passwd\0/etc/shadow".to_string();
1121 let data = ZipFileData {
1122 system: System::Dos,
1123 version_made_by: 0,
1124 encrypted: false,
1125 using_data_descriptor: false,
1126 is_utf8: true,
1127 compression_method: crate::compression::CompressionMethod::Stored,
1128 compression_level: None,
1129 last_modified_time: None,
1130 crc32: 0,
1131 compressed_size: 0,
1132 uncompressed_size: 0,
1133 file_name: file_name.clone().into_boxed_str(),
1134 file_name_raw: file_name.into_bytes().into_boxed_slice(),
1135 extra_field: None,
1136 central_extra_field: None,
1137 file_comment: String::with_capacity(0).into_boxed_str(),
1138 header_start: 0,
1139 extra_data_start: None,
1140 data_start: OnceLock::new(),
1141 central_header_start: 0,
1142 external_attributes: 0,
1143 large_file: false,
1144 aes_mode: None,
1145 aes_extra_data_start: 0,
1146 extra_fields: Vec::new(),
1147 };
1148 assert_eq!(data.file_name_sanitized(), PathBuf::from("path/etc/passwd"));
1149 }
1150
1151 #[test]
1152 #[allow(clippy::unusual_byte_groupings)]
1153 fn datetime_default() {
1154 use super::DateTime;
1155 let dt = DateTime::default();
1156 assert_eq!(dt.timepart(), 0);
1157 assert_eq!(dt.datepart(), 0b0000000_0001_00001);
1158 }
1159
1160 #[test]
1161 #[allow(clippy::unusual_byte_groupings)]
1162 fn datetime_max() {
1163 use super::DateTime;
1164 let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 58).unwrap();
1165 assert_eq!(dt.timepart(), 0b10111_111011_11101);
1166 assert_eq!(dt.datepart(), 0b1111111_1100_11111);
1167 }
1168
1169 #[test]
1170 fn datetime_equality() {
1171 use super::DateTime;
1172
1173 let dt = DateTime::from_date_and_time(2018, 11, 17, 10, 38, 30).unwrap();
1174 assert_eq!(
1175 dt,
1176 DateTime::from_date_and_time(2018, 11, 17, 10, 38, 30).unwrap()
1177 );
1178 assert_ne!(dt, DateTime::default());
1179 }
1180
1181 #[test]
1182 fn datetime_order() {
1183 use std::cmp::Ordering;
1184
1185 use super::DateTime;
1186
1187 let dt = DateTime::from_date_and_time(2018, 11, 17, 10, 38, 30).unwrap();
1188 assert_eq!(
1189 dt.cmp(&DateTime::from_date_and_time(2018, 11, 17, 10, 38, 30).unwrap()),
1190 Ordering::Equal
1191 );
1192 assert!(dt < DateTime::from_date_and_time(2019, 11, 17, 10, 38, 30).unwrap());
1194 assert!(dt > DateTime::from_date_and_time(2017, 11, 17, 10, 38, 30).unwrap());
1195 assert!(dt < DateTime::from_date_and_time(2018, 12, 17, 10, 38, 30).unwrap());
1197 assert!(dt > DateTime::from_date_and_time(2018, 10, 17, 10, 38, 30).unwrap());
1198 assert!(dt < DateTime::from_date_and_time(2018, 11, 18, 10, 38, 30).unwrap());
1200 assert!(dt > DateTime::from_date_and_time(2018, 11, 16, 10, 38, 30).unwrap());
1201 assert!(dt < DateTime::from_date_and_time(2018, 11, 17, 11, 38, 30).unwrap());
1203 assert!(dt > DateTime::from_date_and_time(2018, 11, 17, 9, 38, 30).unwrap());
1204 assert!(dt < DateTime::from_date_and_time(2018, 11, 17, 10, 39, 30).unwrap());
1206 assert!(dt > DateTime::from_date_and_time(2018, 11, 17, 10, 37, 30).unwrap());
1207 assert!(dt < DateTime::from_date_and_time(2018, 11, 17, 10, 38, 32).unwrap());
1209 assert_eq!(
1210 dt.cmp(&DateTime::from_date_and_time(2018, 11, 17, 10, 38, 31).unwrap()),
1211 Ordering::Equal
1212 );
1213 assert!(dt > DateTime::from_date_and_time(2018, 11, 17, 10, 38, 29).unwrap());
1214 assert!(dt > DateTime::from_date_and_time(2018, 11, 17, 10, 38, 28).unwrap());
1215 }
1216
1217 #[test]
1218 fn datetime_display() {
1219 use super::DateTime;
1220
1221 assert_eq!(format!("{}", DateTime::default()), "1980-01-01 00:00:00");
1222 assert_eq!(
1223 format!(
1224 "{}",
1225 DateTime::from_date_and_time(2018, 11, 17, 10, 38, 30).unwrap()
1226 ),
1227 "2018-11-17 10:38:30"
1228 );
1229 assert_eq!(
1230 format!(
1231 "{}",
1232 DateTime::from_date_and_time(2107, 12, 31, 23, 59, 58).unwrap()
1233 ),
1234 "2107-12-31 23:59:58"
1235 );
1236 }
1237
1238 #[test]
1239 fn datetime_bounds() {
1240 use super::DateTime;
1241
1242 assert!(DateTime::from_date_and_time(2000, 1, 1, 23, 59, 60).is_ok());
1243 assert!(DateTime::from_date_and_time(2000, 1, 1, 24, 0, 0).is_err());
1244 assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 60, 0).is_err());
1245 assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 0, 61).is_err());
1246
1247 assert!(DateTime::from_date_and_time(2107, 12, 31, 0, 0, 0).is_ok());
1248 assert!(DateTime::from_date_and_time(1980, 1, 1, 0, 0, 0).is_ok());
1249 assert!(DateTime::from_date_and_time(1979, 1, 1, 0, 0, 0).is_err());
1250 assert!(DateTime::from_date_and_time(1980, 0, 1, 0, 0, 0).is_err());
1251 assert!(DateTime::from_date_and_time(1980, 1, 0, 0, 0, 0).is_err());
1252 assert!(DateTime::from_date_and_time(2108, 12, 31, 0, 0, 0).is_err());
1253 assert!(DateTime::from_date_and_time(2107, 13, 31, 0, 0, 0).is_err());
1254 assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err());
1255
1256 assert!(DateTime::from_date_and_time(2018, 1, 31, 0, 0, 0).is_ok());
1257 assert!(DateTime::from_date_and_time(2018, 2, 28, 0, 0, 0).is_ok());
1258 assert!(DateTime::from_date_and_time(2018, 2, 29, 0, 0, 0).is_err());
1259 assert!(DateTime::from_date_and_time(2018, 3, 31, 0, 0, 0).is_ok());
1260 assert!(DateTime::from_date_and_time(2018, 4, 30, 0, 0, 0).is_ok());
1261 assert!(DateTime::from_date_and_time(2018, 4, 31, 0, 0, 0).is_err());
1262 assert!(DateTime::from_date_and_time(2018, 5, 31, 0, 0, 0).is_ok());
1263 assert!(DateTime::from_date_and_time(2018, 6, 30, 0, 0, 0).is_ok());
1264 assert!(DateTime::from_date_and_time(2018, 6, 31, 0, 0, 0).is_err());
1265 assert!(DateTime::from_date_and_time(2018, 7, 31, 0, 0, 0).is_ok());
1266 assert!(DateTime::from_date_and_time(2018, 8, 31, 0, 0, 0).is_ok());
1267 assert!(DateTime::from_date_and_time(2018, 9, 30, 0, 0, 0).is_ok());
1268 assert!(DateTime::from_date_and_time(2018, 9, 31, 0, 0, 0).is_err());
1269 assert!(DateTime::from_date_and_time(2018, 10, 31, 0, 0, 0).is_ok());
1270 assert!(DateTime::from_date_and_time(2018, 11, 30, 0, 0, 0).is_ok());
1271 assert!(DateTime::from_date_and_time(2018, 11, 31, 0, 0, 0).is_err());
1272 assert!(DateTime::from_date_and_time(2018, 12, 31, 0, 0, 0).is_ok());
1273
1274 assert!(DateTime::from_date_and_time(2024, 2, 29, 0, 0, 0).is_ok());
1276 assert!(DateTime::from_date_and_time(2000, 2, 29, 0, 0, 0).is_ok());
1278 assert!(DateTime::from_date_and_time(2100, 2, 29, 0, 0, 0).is_err());
1280 }
1281
1282 #[cfg(feature = "time")]
1283 use time::{format_description::well_known::Rfc3339, OffsetDateTime};
1284
1285 #[cfg(feature = "time")]
1286 #[test]
1287 fn datetime_try_from_offset_datetime() {
1288 use time::macros::datetime;
1289
1290 use super::DateTime;
1291
1292 let dt = DateTime::try_from(datetime!(2018-11-17 10:38:30 UTC)).unwrap();
1294 assert_eq!(dt.year(), 2018);
1295 assert_eq!(dt.month(), 11);
1296 assert_eq!(dt.day(), 17);
1297 assert_eq!(dt.hour(), 10);
1298 assert_eq!(dt.minute(), 38);
1299 assert_eq!(dt.second(), 30);
1300 }
1301
1302 #[cfg(feature = "time")]
1303 #[test]
1304 fn datetime_try_from_bounds() {
1305 use super::DateTime;
1306 use time::macros::datetime;
1307
1308 assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59 UTC)).is_err());
1310
1311 assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00 UTC)).is_ok());
1313
1314 assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59 UTC)).is_ok());
1316
1317 assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00 UTC)).is_err());
1319 }
1320
1321 #[cfg(feature = "time")]
1322 #[test]
1323 fn offset_datetime_try_from_datetime() {
1324 use time::macros::datetime;
1325
1326 use super::DateTime;
1327
1328 let dt =
1330 OffsetDateTime::try_from(DateTime::try_from_msdos(0x4D71, 0x54CF).unwrap()).unwrap();
1331 assert_eq!(dt, datetime!(2018-11-17 10:38:30 UTC));
1332 }
1333
1334 #[cfg(feature = "time")]
1335 #[test]
1336 fn offset_datetime_try_from_bounds() {
1337 use super::DateTime;
1338
1339 assert!(OffsetDateTime::try_from(unsafe {
1341 DateTime::from_msdos_unchecked(0x0000, 0x0000)
1342 })
1343 .is_err());
1344
1345 assert!(OffsetDateTime::try_from(unsafe {
1347 DateTime::from_msdos_unchecked(0xFFFF, 0xFFFF)
1348 })
1349 .is_err());
1350 }
1351
1352 #[test]
1353 #[allow(deprecated)]
1354 fn time_conversion() {
1355 use super::DateTime;
1356 let dt = DateTime::try_from_msdos(0x4D71, 0x54CF).unwrap();
1357 assert_eq!(dt.year(), 2018);
1358 assert_eq!(dt.month(), 11);
1359 assert_eq!(dt.day(), 17);
1360 assert_eq!(dt.hour(), 10);
1361 assert_eq!(dt.minute(), 38);
1362 assert_eq!(dt.second(), 30);
1363
1364 let dt = DateTime::try_from((0x4D71, 0x54CF)).unwrap();
1365 assert_eq!(dt.year(), 2018);
1366 assert_eq!(dt.month(), 11);
1367 assert_eq!(dt.day(), 17);
1368 assert_eq!(dt.hour(), 10);
1369 assert_eq!(dt.minute(), 38);
1370 assert_eq!(dt.second(), 30);
1371
1372 #[cfg(feature = "time")]
1373 assert_eq!(
1374 dt.to_time().unwrap().format(&Rfc3339).unwrap(),
1375 "2018-11-17T10:38:30Z"
1376 );
1377
1378 assert_eq!(<(u16, u16)>::from(dt), (0x4D71, 0x54CF));
1379 }
1380
1381 #[test]
1382 #[allow(deprecated)]
1383 fn time_out_of_bounds() {
1384 use super::DateTime;
1385 let dt = unsafe { DateTime::from_msdos_unchecked(0xFFFF, 0xFFFF) };
1386 assert_eq!(dt.year(), 2107);
1387 assert_eq!(dt.month(), 15);
1388 assert_eq!(dt.day(), 31);
1389 assert_eq!(dt.hour(), 31);
1390 assert_eq!(dt.minute(), 63);
1391 assert_eq!(dt.second(), 62);
1392
1393 #[cfg(feature = "time")]
1394 assert!(dt.to_time().is_err());
1395
1396 let dt = unsafe { DateTime::from_msdos_unchecked(0x0000, 0x0000) };
1397 assert_eq!(dt.year(), 1980);
1398 assert_eq!(dt.month(), 0);
1399 assert_eq!(dt.day(), 0);
1400 assert_eq!(dt.hour(), 0);
1401 assert_eq!(dt.minute(), 0);
1402 assert_eq!(dt.second(), 0);
1403
1404 #[cfg(feature = "time")]
1405 assert!(dt.to_time().is_err());
1406 }
1407
1408 #[cfg(feature = "time")]
1409 #[test]
1410 fn time_at_january() {
1411 use super::DateTime;
1412
1413 let clock = OffsetDateTime::from_unix_timestamp(1_577_836_800).unwrap();
1415
1416 assert!(DateTime::try_from(clock).is_ok());
1417 }
1418}