1use cfg_if::cfg_if;
3use num_enum::{FromPrimitive, IntoPrimitive};
4use std::convert::TryInto;
5use std::ops::{Range, RangeInclusive};
6use std::path;
7
8#[cfg(doc)]
9use {crate::read::ZipFile, crate::write::FileOptions};
10
11mod ffi {
12 pub const S_IFDIR: u32 = 0o0040000;
13 pub const S_IFREG: u32 = 0o0100000;
14}
15
16cfg_if! {
17 if #[cfg(any(
18 all(target_arch = "arm", target_pointer_width = "32"),
19 target_arch = "mips",
20 target_arch = "powerpc"
21 ))] {
22 mod atomic {
23 use crossbeam_utils::sync::ShardedLock;
24 pub use std::sync::atomic::Ordering;
25
26 #[derive(Debug, Default)]
27 pub struct AtomicU64 {
28 value: ShardedLock<u64>,
29 }
30
31 impl AtomicU64 {
32 pub fn new(v: u64) -> Self {
33 Self {
34 value: ShardedLock::new(v),
35 }
36 }
37 pub fn get_mut(&mut self) -> &mut u64 {
38 self.value.get_mut().unwrap()
39 }
40 pub fn load(&self, _: Ordering) -> u64 {
41 *self.value.read().unwrap()
42 }
43 pub fn store(&self, value: u64, _: Ordering) {
44 *self.value.write().unwrap() = value;
45 }
46 }
47 }
48 } else {
49 use std::sync::atomic;
50 }
51
52}
53
54cfg_if! {
55 if #[cfg(feature = "time")] {
56 use crate::result::DateTimeRangeError;
57 use time::{
58 Date,
59 Month,
60 OffsetDateTime,
61 PrimitiveDateTime,
62 Time,
63 UtcOffset,
64 error::ComponentRange,
65 };
66 } else {
67 use std::time::SystemTime;
68 }
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
72#[repr(u8)]
73pub enum System {
74 Dos = 0,
75 Unix = 3,
76 #[num_enum(default)]
77 Unknown,
78}
79
80#[derive(Debug, Clone, Copy)]
98pub struct DateTime {
99 year: u16,
100 month: u8,
101 day: u8,
102 hour: u8,
103 minute: u8,
104 second: u8,
105}
106
107impl ::std::default::Default for DateTime {
108 fn default() -> DateTime {
109 Self::zero()
110 }
111}
112
113impl DateTime {
114 pub const fn zero() -> Self {
116 Self {
117 year: 1980,
118 month: 1,
119 day: 1,
120 hour: 0,
121 minute: 0,
122 second: 0,
123 }
124 }
125
126 pub const YEAR_RANGE: RangeInclusive<u16> = 1980..=2107;
128 pub const MONTH_RANGE: RangeInclusive<u8> = 1..=12;
130 pub const DAY_RANGE: RangeInclusive<u8> = 1..=31;
132 pub const HOUR_RANGE: Range<u8> = 0..24;
134 pub const MINUTE_RANGE: Range<u8> = 0..60;
136 pub const SECOND_RANGE: RangeInclusive<u8> = 0..=60;
138
139 fn check_year(year: u16) -> Result<(), DateTimeRangeError> {
140 if Self::YEAR_RANGE.contains(&year) {
141 Ok(())
142 } else {
143 Err(DateTimeRangeError::InvalidYear(year, Self::YEAR_RANGE))
144 }
145 }
146
147 fn check_month(month: u8) -> Result<(), DateTimeRangeError> {
148 if Self::MONTH_RANGE.contains(&month) {
149 Ok(())
150 } else {
151 Err(DateTimeRangeError::InvalidMonth(month, Self::MONTH_RANGE))
152 }
153 }
154
155 fn check_day(day: u8) -> Result<(), DateTimeRangeError> {
156 if Self::DAY_RANGE.contains(&day) {
157 Ok(())
158 } else {
159 Err(DateTimeRangeError::InvalidDay(day, Self::DAY_RANGE))
160 }
161 }
162
163 fn check_hour(hour: u8) -> Result<(), DateTimeRangeError> {
164 if Self::HOUR_RANGE.contains(&hour) {
165 Ok(())
166 } else {
167 Err(DateTimeRangeError::InvalidHour(hour, Self::HOUR_RANGE))
168 }
169 }
170
171 fn check_minute(minute: u8) -> Result<(), DateTimeRangeError> {
172 if Self::MINUTE_RANGE.contains(&minute) {
173 Ok(())
174 } else {
175 Err(DateTimeRangeError::InvalidMinute(
176 minute,
177 Self::MINUTE_RANGE,
178 ))
179 }
180 }
181
182 fn check_second(second: u8) -> Result<(), DateTimeRangeError> {
183 if Self::SECOND_RANGE.contains(&second) {
184 Ok(())
185 } else {
186 Err(DateTimeRangeError::InvalidSecond(
187 second,
188 Self::SECOND_RANGE,
189 ))
190 }
191 }
192
193 pub fn from_msdos(datepart: u16, timepart: u16) -> DateTime {
195 let seconds = (timepart & 0b0000000000011111) << 1;
196 let minutes = (timepart & 0b0000011111100000) >> 5;
197 let hours = (timepart & 0b1111100000000000) >> 11;
198 let days = datepart & 0b0000000000011111;
199 let months = (datepart & 0b0000000111100000) >> 5;
200 let years = (datepart & 0b1111111000000000) >> 9;
201
202 DateTime {
203 year: years + 1980,
204 month: months as u8,
205 day: days as u8,
206 hour: hours as u8,
207 minute: minutes as u8,
208 second: seconds as u8,
209 }
210 }
211
212 pub fn from_date_and_time(
222 year: u16,
223 month: u8,
224 day: u8,
225 hour: u8,
226 minute: u8,
227 second: u8,
228 ) -> Result<DateTime, DateTimeRangeError> {
229 Self::check_year(year)?;
230 Self::check_month(month)?;
231 Self::check_day(day)?;
232 Self::check_hour(hour)?;
233 Self::check_minute(minute)?;
234 Self::check_second(second)?;
235 Ok(Self {
236 year,
237 month,
238 day,
239 hour,
240 minute,
241 second,
242 })
243 }
244
245 pub fn timepart(&self) -> u16 {
247 ((self.second as u16) >> 1) | ((self.minute as u16) << 5) | ((self.hour as u16) << 11)
248 }
249
250 pub fn datepart(&self) -> u16 {
252 (self.day as u16) | ((self.month as u16) << 5) | ((self.year - 1980) << 9)
253 }
254
255 #[cfg(feature = "time")]
257 #[cfg_attr(docsrs, doc(cfg(feature = "time")))]
258 pub fn to_time(&self, offset: UtcOffset) -> Result<OffsetDateTime, ComponentRange> {
259 let date =
260 Date::from_calendar_date(self.year as i32, Month::try_from(self.month)?, self.day)?;
261 let time = Time::from_hms(self.hour, self.minute, self.second)?;
262 Ok(PrimitiveDateTime::new(date, time).assume_offset(offset))
263 }
264
265 pub fn year(&self) -> u16 {
267 self.year
268 }
269
270 pub fn month(&self) -> u8 {
276 self.month
277 }
278
279 pub fn day(&self) -> u8 {
285 self.day
286 }
287
288 pub fn hour(&self) -> u8 {
294 self.hour
295 }
296
297 pub fn minute(&self) -> u8 {
303 self.minute
304 }
305
306 pub fn second(&self) -> u8 {
312 self.second
313 }
314}
315
316#[cfg(feature = "time")]
317#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
318impl TryFrom<OffsetDateTime> for DateTime {
319 type Error = DateTimeRangeError;
320
321 fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
322 let year: u16 = dt
323 .year()
324 .try_into()
325 .map_err(|e| DateTimeRangeError::NumericConversion("year", e))?;
326 let month: u8 = dt.month().into();
327 let day: u8 = dt.day().into();
328 let hour: u8 = dt.hour().into();
329 let minute: u8 = dt.minute().into();
330 let second: u8 = dt.second().into();
331
332 Self::from_date_and_time(year, month, day, hour, minute, second)
333 }
334}
335
336pub const DEFAULT_VERSION: u8 = 46;
337
338#[derive(Debug)]
343pub struct AtomicU64(atomic::AtomicU64);
344
345impl AtomicU64 {
346 pub fn new(v: u64) -> Self {
347 Self(atomic::AtomicU64::new(v))
348 }
349
350 pub fn load(&self) -> u64 {
351 self.0.load(atomic::Ordering::Relaxed)
352 }
353
354 pub fn store(&self, val: u64) {
355 self.0.store(val, atomic::Ordering::Relaxed)
356 }
357
358 pub fn get_mut(&mut self) -> &mut u64 {
359 self.0.get_mut()
360 }
361}
362
363impl Clone for AtomicU64 {
364 fn clone(&self) -> Self {
365 Self(atomic::AtomicU64::new(self.load()))
366 }
367}
368
369#[derive(Debug, Clone)]
371pub struct ZipFileData {
372 pub system: System,
374 pub version_made_by: u8,
376 pub encrypted: bool,
378 pub using_data_descriptor: bool,
380 pub compression_method: crate::compression::CompressionMethod,
382 pub compression_level: Option<i32>,
384 pub last_modified_time: DateTime,
386 pub crc32: u32,
388 pub compressed_size: u64,
390 pub uncompressed_size: u64,
392 pub file_name: String,
394 pub file_name_raw: Vec<u8>,
396 pub extra_field: Vec<u8>,
398 pub file_comment: String,
400 pub header_start: u64,
402 pub central_header_start: u64,
406 pub data_start: AtomicU64,
408 pub external_attributes: u32,
410 pub large_file: bool,
412 pub aes_mode: Option<(AesMode, AesVendorVersion)>,
414}
415
416impl ZipFileData {
417 pub fn file_name_sanitized(&self) -> ::std::path::PathBuf {
418 let no_null_filename = match self.file_name.find('\0') {
419 Some(index) => &self.file_name[0..index],
420 None => &self.file_name,
421 }
422 .to_string();
423
424 let separator = ::std::path::MAIN_SEPARATOR;
428 let opposite_separator = match separator {
429 '/' => '\\',
430 _ => '/',
431 };
432 let filename =
433 no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string());
434
435 ::std::path::Path::new(&filename)
436 .components()
437 .filter(|component| matches!(*component, ::std::path::Component::Normal(..)))
438 .fold(::std::path::PathBuf::new(), |mut path, ref cur| {
439 path.push(cur.as_os_str());
440 path
441 })
442 }
443
444 pub(crate) fn enclosed_name(&self) -> Option<&path::Path> {
445 if self.file_name.contains('\0') {
446 return None;
447 }
448 let path = path::Path::new(&self.file_name);
449 let mut depth = 0usize;
450 for component in path.components() {
451 match component {
452 path::Component::Prefix(_) | path::Component::RootDir => return None,
453 path::Component::ParentDir => depth = depth.checked_sub(1)?,
454 path::Component::Normal(_) => depth += 1,
455 path::Component::CurDir => (),
456 }
457 }
458 Some(path)
459 }
460
461 pub(crate) fn unix_mode(&self) -> Option<u32> {
463 if self.external_attributes == 0 {
464 return None;
465 }
466
467 match self.system {
468 System::Unix => Some(self.external_attributes >> 16),
469 System::Dos => {
470 let mut mode = if 0x10 == (self.external_attributes & 0x10) {
472 ffi::S_IFDIR | 0o0775
473 } else {
474 ffi::S_IFREG | 0o0664
475 };
476 if 0x01 == (self.external_attributes & 0x01) {
477 mode &= 0o0555;
479 }
480 Some(mode)
481 }
482 _ => None,
483 }
484 }
485
486 pub fn zip64_extension(&self) -> bool {
487 self.uncompressed_size > 0xFFFFFFFF
488 || self.compressed_size > 0xFFFFFFFF
489 || self.header_start > 0xFFFFFFFF
490 }
491
492 pub fn version_needed(&self) -> u16 {
493 match (self.zip64_extension(), self.compression_method) {
495 #[cfg(feature = "bzip2")]
496 (_, crate::compression::CompressionMethod::Bzip2) => 46,
497 (true, _) => 45,
498 _ => 20,
499 }
500 }
501}
502
503#[derive(Copy, Clone, Debug)]
508pub enum AesVendorVersion {
509 Ae1,
510 Ae2,
511}
512
513#[derive(Copy, Clone, Debug)]
515pub enum AesMode {
516 Aes128,
517 Aes192,
518 Aes256,
519}
520
521#[cfg(feature = "aes-crypto")]
522#[cfg_attr(docsrs, doc(cfg(feature = "aes-crypto")))]
523impl AesMode {
524 pub fn salt_length(&self) -> usize {
525 self.key_length() / 2
526 }
527
528 pub fn key_length(&self) -> usize {
529 match self {
530 Self::Aes128 => 16,
531 Self::Aes192 => 24,
532 Self::Aes256 => 32,
533 }
534 }
535}
536
537#[cfg(test)]
538mod test {
539 #[test]
540 fn system() {
541 use super::System;
542 assert_eq!(u8::from(System::Dos), 0u8);
543 assert_eq!(System::Dos as u8, 0u8);
544 assert_eq!(System::Unix as u8, 3u8);
545 assert_eq!(u8::from(System::Unix), 3u8);
546 assert_eq!(System::from(0), System::Dos);
547 assert_eq!(System::from(3), System::Unix);
548 assert_eq!(u8::from(System::Unknown), 4u8);
549 assert_eq!(System::Unknown as u8, 4u8);
550 }
551
552 #[test]
553 fn sanitize() {
554 use super::*;
555 let file_name = "/path/../../../../etc/./passwd\0/etc/shadow".to_string();
556 let data = ZipFileData {
557 system: System::Dos,
558 version_made_by: 0,
559 encrypted: false,
560 using_data_descriptor: false,
561 compression_method: crate::compression::CompressionMethod::Stored,
562 compression_level: None,
563 last_modified_time: DateTime::default(),
564 crc32: 0,
565 compressed_size: 0,
566 uncompressed_size: 0,
567 file_name: file_name.clone(),
568 file_name_raw: file_name.into_bytes(),
569 extra_field: Vec::new(),
570 file_comment: String::new(),
571 header_start: 0,
572 data_start: AtomicU64::new(0),
573 central_header_start: 0,
574 external_attributes: 0,
575 large_file: false,
576 aes_mode: None,
577 };
578 assert_eq!(
579 data.file_name_sanitized(),
580 ::std::path::PathBuf::from("path/etc/passwd")
581 );
582 }
583
584 #[test]
585 #[allow(clippy::unusual_byte_groupings)]
586 fn datetime_default() {
587 use super::DateTime;
588 let dt = DateTime::default();
589 assert_eq!(dt.timepart(), 0);
590 assert_eq!(dt.datepart(), 0b0000000_0001_00001);
591 }
592
593 #[test]
594 #[allow(clippy::unusual_byte_groupings)]
595 fn datetime_max() {
596 use super::DateTime;
597 let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 60).unwrap();
598 assert_eq!(dt.timepart(), 0b10111_111011_11110);
599 assert_eq!(dt.datepart(), 0b1111111_1100_11111);
600 }
601
602 #[test]
603 fn datetime_bounds() {
604 use super::DateTime;
605
606 assert!(DateTime::from_date_and_time(2000, 1, 1, 23, 59, 60).is_ok());
607 assert!(DateTime::from_date_and_time(2000, 1, 1, 24, 0, 0).is_err());
608 assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 60, 0).is_err());
609 assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 0, 61).is_err());
610
611 assert!(DateTime::from_date_and_time(2107, 12, 31, 0, 0, 0).is_ok());
612 assert!(DateTime::from_date_and_time(1980, 1, 1, 0, 0, 0).is_ok());
613 assert!(DateTime::from_date_and_time(1979, 1, 1, 0, 0, 0).is_err());
614 assert!(DateTime::from_date_and_time(1980, 0, 1, 0, 0, 0).is_err());
615 assert!(DateTime::from_date_and_time(1980, 1, 0, 0, 0, 0).is_err());
616 assert!(DateTime::from_date_and_time(2108, 12, 31, 0, 0, 0).is_err());
617 assert!(DateTime::from_date_and_time(2107, 13, 31, 0, 0, 0).is_err());
618 assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err());
619 }
620
621 #[cfg(feature = "time")]
622 use time::{format_description::well_known::Rfc3339, OffsetDateTime, UtcOffset};
623
624 #[cfg(feature = "time")]
625 #[test]
626 fn datetime_try_from_bounds() {
627 use std::convert::TryFrom;
628
629 use super::DateTime;
630 use time::macros::datetime;
631
632 assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59 UTC)).is_err());
634
635 assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00 UTC)).is_ok());
637
638 assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59 UTC)).is_ok());
640
641 assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00 UTC)).is_err());
643 }
644
645 #[test]
646 fn time_conversion() {
647 use super::DateTime;
648 let dt = DateTime::from_msdos(0x4D71, 0x54CF);
649 assert_eq!(dt.year(), 2018);
650 assert_eq!(dt.month(), 11);
651 assert_eq!(dt.day(), 17);
652 assert_eq!(dt.hour(), 10);
653 assert_eq!(dt.minute(), 38);
654 assert_eq!(dt.second(), 30);
655
656 #[cfg(feature = "time")]
657 assert_eq!(
658 dt.to_time(UtcOffset::UTC)
659 .unwrap()
660 .format(&Rfc3339)
661 .unwrap(),
662 "2018-11-17T10:38:30Z"
663 );
664 }
665
666 #[test]
667 fn time_out_of_bounds() {
668 use super::DateTime;
669 let dt = DateTime::from_msdos(0xFFFF, 0xFFFF);
670 assert_eq!(dt.year(), 2107);
671 assert_eq!(dt.month(), 15);
672 assert_eq!(dt.day(), 31);
673 assert_eq!(dt.hour(), 31);
674 assert_eq!(dt.minute(), 63);
675 assert_eq!(dt.second(), 62);
676
677 #[cfg(feature = "time")]
678 assert!(dt.to_time(UtcOffset::UTC).is_err());
679
680 let dt = DateTime::from_msdos(0x0000, 0x0000);
681 assert_eq!(dt.year(), 1980);
682 assert_eq!(dt.month(), 0);
683 assert_eq!(dt.day(), 0);
684 assert_eq!(dt.hour(), 0);
685 assert_eq!(dt.minute(), 0);
686 assert_eq!(dt.second(), 0);
687
688 #[cfg(feature = "time")]
689 assert!(dt.to_time(UtcOffset::UTC).is_err());
690 }
691
692 #[cfg(feature = "time")]
693 #[test]
694 fn time_at_january() {
695 use super::DateTime;
696 use std::convert::TryFrom;
697
698 let clock = OffsetDateTime::from_unix_timestamp(1_577_836_800).unwrap();
700
701 assert!(DateTime::try_from(clock).is_ok());
702 }
703}