1 //! Types that specify what is contained in a ZIP.
2 use std::path;
3 
4 #[cfg(not(any(
5     all(target_arch = "arm", target_pointer_width = "32"),
6     target_arch = "mips",
7     target_arch = "powerpc"
8 )))]
9 use std::sync::atomic;
10 #[cfg(not(feature = "time"))]
11 use std::time::SystemTime;
12 #[cfg(doc)]
13 use {crate::read::ZipFile, crate::write::FileOptions};
14 
15 mod ffi {
16     pub const S_IFDIR: u32 = 0o0040000;
17     pub const S_IFREG: u32 = 0o0100000;
18 }
19 
20 #[cfg(any(
21     all(target_arch = "arm", target_pointer_width = "32"),
22     target_arch = "mips",
23     target_arch = "powerpc"
24 ))]
25 mod atomic {
26     use crossbeam_utils::sync::ShardedLock;
27     pub use std::sync::atomic::Ordering;
28 
29     #[derive(Debug, Default)]
30     pub struct AtomicU64 {
31         value: ShardedLock<u64>,
32     }
33 
34     impl AtomicU64 {
new(v: u64) -> Self35         pub fn new(v: u64) -> Self {
36             Self {
37                 value: ShardedLock::new(v),
38             }
39         }
get_mut(&mut self) -> &mut u6440         pub fn get_mut(&mut self) -> &mut u64 {
41             self.value.get_mut().unwrap()
42         }
load(&self, _: Ordering) -> u6443         pub fn load(&self, _: Ordering) -> u64 {
44             *self.value.read().unwrap()
45         }
store(&self, value: u64, _: Ordering)46         pub fn store(&self, value: u64, _: Ordering) {
47             *self.value.write().unwrap() = value;
48         }
49     }
50 }
51 
52 #[cfg(feature = "time")]
53 use crate::result::DateTimeRangeError;
54 #[cfg(feature = "time")]
55 use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
56 
57 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
58 pub enum System {
59     Dos = 0,
60     Unix = 3,
61     Unknown,
62 }
63 
64 impl System {
from_u8(system: u8) -> System65     pub fn from_u8(system: u8) -> System {
66         use self::System::*;
67 
68         match system {
69             0 => Dos,
70             3 => Unix,
71             _ => Unknown,
72         }
73     }
74 }
75 
76 /// Representation of a moment in time.
77 ///
78 /// Zip files use an old format from DOS to store timestamps,
79 /// with its own set of peculiarities.
80 /// For example, it has a resolution of 2 seconds!
81 ///
82 /// A [`DateTime`] can be stored directly in a zipfile with [`FileOptions::last_modified_time`],
83 /// or read from one with [`ZipFile::last_modified`]
84 ///
85 /// # Warning
86 ///
87 /// Because there is no timezone associated with the [`DateTime`], they should ideally only
88 /// be used for user-facing descriptions. This also means [`DateTime::to_time`] returns an
89 /// [`OffsetDateTime`] (which is the equivalent of chrono's `NaiveDateTime`).
90 ///
91 /// Modern zip files store more precise timestamps, which are ignored by [`crate::read::ZipArchive`],
92 /// so keep in mind that these timestamps are unreliable. [We're working on this](https://github.com/zip-rs/zip/issues/156#issuecomment-652981904).
93 #[derive(Debug, Clone, Copy)]
94 pub struct DateTime {
95     year: u16,
96     month: u8,
97     day: u8,
98     hour: u8,
99     minute: u8,
100     second: u8,
101 }
102 
103 impl ::std::default::Default for DateTime {
104     /// Constructs an 'default' datetime of 1980-01-01 00:00:00
default() -> DateTime105     fn default() -> DateTime {
106         DateTime {
107             year: 1980,
108             month: 1,
109             day: 1,
110             hour: 0,
111             minute: 0,
112             second: 0,
113         }
114     }
115 }
116 
117 impl DateTime {
118     /// Converts an msdos (u16, u16) pair to a DateTime object
from_msdos(datepart: u16, timepart: u16) -> DateTime119     pub fn from_msdos(datepart: u16, timepart: u16) -> DateTime {
120         let seconds = (timepart & 0b0000000000011111) << 1;
121         let minutes = (timepart & 0b0000011111100000) >> 5;
122         let hours = (timepart & 0b1111100000000000) >> 11;
123         let days = datepart & 0b0000000000011111;
124         let months = (datepart & 0b0000000111100000) >> 5;
125         let years = (datepart & 0b1111111000000000) >> 9;
126 
127         DateTime {
128             year: years + 1980,
129             month: months as u8,
130             day: days as u8,
131             hour: hours as u8,
132             minute: minutes as u8,
133             second: seconds as u8,
134         }
135     }
136 
137     /// Constructs a DateTime from a specific date and time
138     ///
139     /// The bounds are:
140     /// * year: [1980, 2107]
141     /// * month: [1, 12]
142     /// * day: [1, 31]
143     /// * hour: [0, 23]
144     /// * minute: [0, 59]
145     /// * second: [0, 60]
146     #[allow(clippy::result_unit_err)]
from_date_and_time( year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8, ) -> Result<DateTime, ()>147     pub fn from_date_and_time(
148         year: u16,
149         month: u8,
150         day: u8,
151         hour: u8,
152         minute: u8,
153         second: u8,
154     ) -> Result<DateTime, ()> {
155         if (1980..=2107).contains(&year)
156             && (1..=12).contains(&month)
157             && (1..=31).contains(&day)
158             && hour <= 23
159             && minute <= 59
160             && second <= 60
161         {
162             Ok(DateTime {
163                 year,
164                 month,
165                 day,
166                 hour,
167                 minute,
168                 second,
169             })
170         } else {
171             Err(())
172         }
173     }
174 
175     #[cfg(feature = "time")]
176     /// Converts a OffsetDateTime object to a DateTime
177     ///
178     /// Returns `Err` when this object is out of bounds
179     #[allow(clippy::result_unit_err)]
180     #[deprecated(note = "use `DateTime::try_from()`")]
from_time(dt: OffsetDateTime) -> Result<DateTime, ()>181     pub fn from_time(dt: OffsetDateTime) -> Result<DateTime, ()> {
182         dt.try_into().map_err(|_err| ())
183     }
184 
185     /// Gets the time portion of this datetime in the msdos representation
timepart(&self) -> u16186     pub fn timepart(&self) -> u16 {
187         ((self.second as u16) >> 1) | ((self.minute as u16) << 5) | ((self.hour as u16) << 11)
188     }
189 
190     /// Gets the date portion of this datetime in the msdos representation
datepart(&self) -> u16191     pub fn datepart(&self) -> u16 {
192         (self.day as u16) | ((self.month as u16) << 5) | ((self.year - 1980) << 9)
193     }
194 
195     #[cfg(feature = "time")]
196     /// Converts the DateTime to a OffsetDateTime structure
to_time(&self) -> Result<OffsetDateTime, ComponentRange>197     pub fn to_time(&self) -> Result<OffsetDateTime, ComponentRange> {
198         let date =
199             Date::from_calendar_date(self.year as i32, Month::try_from(self.month)?, self.day)?;
200         let time = Time::from_hms(self.hour, self.minute, self.second)?;
201         Ok(PrimitiveDateTime::new(date, time).assume_utc())
202     }
203 
204     /// Get the year. There is no epoch, i.e. 2018 will be returned as 2018.
year(&self) -> u16205     pub fn year(&self) -> u16 {
206         self.year
207     }
208 
209     /// Get the month, where 1 = january and 12 = december
210     ///
211     /// # Warning
212     ///
213     /// When read from a zip file, this may not be a reasonable value
month(&self) -> u8214     pub fn month(&self) -> u8 {
215         self.month
216     }
217 
218     /// Get the day
219     ///
220     /// # Warning
221     ///
222     /// When read from a zip file, this may not be a reasonable value
day(&self) -> u8223     pub fn day(&self) -> u8 {
224         self.day
225     }
226 
227     /// Get the hour
228     ///
229     /// # Warning
230     ///
231     /// When read from a zip file, this may not be a reasonable value
hour(&self) -> u8232     pub fn hour(&self) -> u8 {
233         self.hour
234     }
235 
236     /// Get the minute
237     ///
238     /// # Warning
239     ///
240     /// When read from a zip file, this may not be a reasonable value
minute(&self) -> u8241     pub fn minute(&self) -> u8 {
242         self.minute
243     }
244 
245     /// Get the second
246     ///
247     /// # Warning
248     ///
249     /// When read from a zip file, this may not be a reasonable value
second(&self) -> u8250     pub fn second(&self) -> u8 {
251         self.second
252     }
253 }
254 
255 #[cfg(feature = "time")]
256 impl TryFrom<OffsetDateTime> for DateTime {
257     type Error = DateTimeRangeError;
258 
try_from(dt: OffsetDateTime) -> Result<Self, Self::Error>259     fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
260         if dt.year() >= 1980 && dt.year() <= 2107 {
261             Ok(DateTime {
262                 year: (dt.year()) as u16,
263                 month: (dt.month()) as u8,
264                 day: dt.day(),
265                 hour: dt.hour(),
266                 minute: dt.minute(),
267                 second: dt.second(),
268             })
269         } else {
270             Err(DateTimeRangeError)
271         }
272     }
273 }
274 
275 pub const DEFAULT_VERSION: u8 = 46;
276 
277 /// A type like `AtomicU64` except it implements `Clone` and has predefined
278 /// ordering.
279 ///
280 /// It uses `Relaxed` ordering because it is not used for synchronisation.
281 #[derive(Debug)]
282 pub struct AtomicU64(atomic::AtomicU64);
283 
284 impl AtomicU64 {
new(v: u64) -> Self285     pub fn new(v: u64) -> Self {
286         Self(atomic::AtomicU64::new(v))
287     }
288 
load(&self) -> u64289     pub fn load(&self) -> u64 {
290         self.0.load(atomic::Ordering::Relaxed)
291     }
292 
store(&self, val: u64)293     pub fn store(&self, val: u64) {
294         self.0.store(val, atomic::Ordering::Relaxed)
295     }
296 
get_mut(&mut self) -> &mut u64297     pub fn get_mut(&mut self) -> &mut u64 {
298         self.0.get_mut()
299     }
300 }
301 
302 impl Clone for AtomicU64 {
clone(&self) -> Self303     fn clone(&self) -> Self {
304         Self(atomic::AtomicU64::new(self.load()))
305     }
306 }
307 
308 /// Structure representing a ZIP file.
309 #[derive(Debug, Clone)]
310 pub struct ZipFileData {
311     /// Compatibility of the file attribute information
312     pub system: System,
313     /// Specification version
314     pub version_made_by: u8,
315     /// True if the file is encrypted.
316     pub encrypted: bool,
317     /// True if the file uses a data-descriptor section
318     pub using_data_descriptor: bool,
319     /// Compression method used to store the file
320     pub compression_method: crate::compression::CompressionMethod,
321     /// Compression level to store the file
322     pub compression_level: Option<i32>,
323     /// Last modified time. This will only have a 2 second precision.
324     pub last_modified_time: DateTime,
325     /// CRC32 checksum
326     pub crc32: u32,
327     /// Size of the file in the ZIP
328     pub compressed_size: u64,
329     /// Size of the file when extracted
330     pub uncompressed_size: u64,
331     /// Name of the file
332     pub file_name: String,
333     /// Raw file name. To be used when file_name was incorrectly decoded.
334     pub file_name_raw: Vec<u8>,
335     /// Extra field usually used for storage expansion
336     pub extra_field: Vec<u8>,
337     /// File comment
338     pub file_comment: String,
339     /// Specifies where the local header of the file starts
340     pub header_start: u64,
341     /// Specifies where the central header of the file starts
342     ///
343     /// Note that when this is not known, it is set to 0
344     pub central_header_start: u64,
345     /// Specifies where the compressed data of the file starts
346     pub data_start: AtomicU64,
347     /// External file attributes
348     pub external_attributes: u32,
349     /// Reserve local ZIP64 extra field
350     pub large_file: bool,
351     /// AES mode if applicable
352     pub aes_mode: Option<(AesMode, AesVendorVersion)>,
353 }
354 
355 impl ZipFileData {
file_name_sanitized(&self) -> ::std::path::PathBuf356     pub fn file_name_sanitized(&self) -> ::std::path::PathBuf {
357         let no_null_filename = match self.file_name.find('\0') {
358             Some(index) => &self.file_name[0..index],
359             None => &self.file_name,
360         }
361         .to_string();
362 
363         // zip files can contain both / and \ as separators regardless of the OS
364         // and as we want to return a sanitized PathBuf that only supports the
365         // OS separator let's convert incompatible separators to compatible ones
366         let separator = ::std::path::MAIN_SEPARATOR;
367         let opposite_separator = match separator {
368             '/' => '\\',
369             _ => '/',
370         };
371         let filename =
372             no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string());
373 
374         ::std::path::Path::new(&filename)
375             .components()
376             .filter(|component| matches!(*component, ::std::path::Component::Normal(..)))
377             .fold(::std::path::PathBuf::new(), |mut path, ref cur| {
378                 path.push(cur.as_os_str());
379                 path
380             })
381     }
382 
enclosed_name(&self) -> Option<&path::Path>383     pub(crate) fn enclosed_name(&self) -> Option<&path::Path> {
384         if self.file_name.contains('\0') {
385             return None;
386         }
387         let path = path::Path::new(&self.file_name);
388         let mut depth = 0usize;
389         for component in path.components() {
390             match component {
391                 path::Component::Prefix(_) | path::Component::RootDir => return None,
392                 path::Component::ParentDir => depth = depth.checked_sub(1)?,
393                 path::Component::Normal(_) => depth += 1,
394                 path::Component::CurDir => (),
395             }
396         }
397         Some(path)
398     }
399 
400     /// Get unix mode for the file
unix_mode(&self) -> Option<u32>401     pub(crate) fn unix_mode(&self) -> Option<u32> {
402         if self.external_attributes == 0 {
403             return None;
404         }
405 
406         match self.system {
407             System::Unix => Some(self.external_attributes >> 16),
408             System::Dos => {
409                 // Interpret MS-DOS directory bit
410                 let mut mode = if 0x10 == (self.external_attributes & 0x10) {
411                     ffi::S_IFDIR | 0o0775
412                 } else {
413                     ffi::S_IFREG | 0o0664
414                 };
415                 if 0x01 == (self.external_attributes & 0x01) {
416                     // Read-only bit; strip write permissions
417                     mode &= 0o0555;
418                 }
419                 Some(mode)
420             }
421             _ => None,
422         }
423     }
424 
zip64_extension(&self) -> bool425     pub fn zip64_extension(&self) -> bool {
426         self.uncompressed_size > 0xFFFFFFFF
427             || self.compressed_size > 0xFFFFFFFF
428             || self.header_start > 0xFFFFFFFF
429     }
430 
version_needed(&self) -> u16431     pub fn version_needed(&self) -> u16 {
432         // higher versions matched first
433         match (self.zip64_extension(), self.compression_method) {
434             #[cfg(feature = "bzip2")]
435             (_, crate::compression::CompressionMethod::Bzip2) => 46,
436             (true, _) => 45,
437             _ => 20,
438         }
439     }
440 }
441 
442 /// The encryption specification used to encrypt a file with AES.
443 ///
444 /// According to the [specification](https://www.winzip.com/win/en/aes_info.html#winzip11) AE-2
445 /// does not make use of the CRC check.
446 #[derive(Copy, Clone, Debug)]
447 pub enum AesVendorVersion {
448     Ae1,
449     Ae2,
450 }
451 
452 /// AES variant used.
453 #[derive(Copy, Clone, Debug)]
454 pub enum AesMode {
455     Aes128,
456     Aes192,
457     Aes256,
458 }
459 
460 #[cfg(feature = "aes-crypto")]
461 impl AesMode {
salt_length(&self) -> usize462     pub fn salt_length(&self) -> usize {
463         self.key_length() / 2
464     }
465 
key_length(&self) -> usize466     pub fn key_length(&self) -> usize {
467         match self {
468             Self::Aes128 => 16,
469             Self::Aes192 => 24,
470             Self::Aes256 => 32,
471         }
472     }
473 }
474 
475 #[cfg(test)]
476 mod test {
477     #[test]
system()478     fn system() {
479         use super::System;
480         assert_eq!(System::Dos as u16, 0u16);
481         assert_eq!(System::Unix as u16, 3u16);
482         assert_eq!(System::from_u8(0), System::Dos);
483         assert_eq!(System::from_u8(3), System::Unix);
484     }
485 
486     #[test]
sanitize()487     fn sanitize() {
488         use super::*;
489         let file_name = "/path/../../../../etc/./passwd\0/etc/shadow".to_string();
490         let data = ZipFileData {
491             system: System::Dos,
492             version_made_by: 0,
493             encrypted: false,
494             using_data_descriptor: false,
495             compression_method: crate::compression::CompressionMethod::Stored,
496             compression_level: None,
497             last_modified_time: DateTime::default(),
498             crc32: 0,
499             compressed_size: 0,
500             uncompressed_size: 0,
501             file_name: file_name.clone(),
502             file_name_raw: file_name.into_bytes(),
503             extra_field: Vec::new(),
504             file_comment: String::new(),
505             header_start: 0,
506             data_start: AtomicU64::new(0),
507             central_header_start: 0,
508             external_attributes: 0,
509             large_file: false,
510             aes_mode: None,
511         };
512         assert_eq!(
513             data.file_name_sanitized(),
514             ::std::path::PathBuf::from("path/etc/passwd")
515         );
516     }
517 
518     #[test]
519     #[allow(clippy::unusual_byte_groupings)]
datetime_default()520     fn datetime_default() {
521         use super::DateTime;
522         let dt = DateTime::default();
523         assert_eq!(dt.timepart(), 0);
524         assert_eq!(dt.datepart(), 0b0000000_0001_00001);
525     }
526 
527     #[test]
528     #[allow(clippy::unusual_byte_groupings)]
datetime_max()529     fn datetime_max() {
530         use super::DateTime;
531         let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 60).unwrap();
532         assert_eq!(dt.timepart(), 0b10111_111011_11110);
533         assert_eq!(dt.datepart(), 0b1111111_1100_11111);
534     }
535 
536     #[test]
datetime_bounds()537     fn datetime_bounds() {
538         use super::DateTime;
539 
540         assert!(DateTime::from_date_and_time(2000, 1, 1, 23, 59, 60).is_ok());
541         assert!(DateTime::from_date_and_time(2000, 1, 1, 24, 0, 0).is_err());
542         assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 60, 0).is_err());
543         assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 0, 61).is_err());
544 
545         assert!(DateTime::from_date_and_time(2107, 12, 31, 0, 0, 0).is_ok());
546         assert!(DateTime::from_date_and_time(1980, 1, 1, 0, 0, 0).is_ok());
547         assert!(DateTime::from_date_and_time(1979, 1, 1, 0, 0, 0).is_err());
548         assert!(DateTime::from_date_and_time(1980, 0, 1, 0, 0, 0).is_err());
549         assert!(DateTime::from_date_and_time(1980, 1, 0, 0, 0, 0).is_err());
550         assert!(DateTime::from_date_and_time(2108, 12, 31, 0, 0, 0).is_err());
551         assert!(DateTime::from_date_and_time(2107, 13, 31, 0, 0, 0).is_err());
552         assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err());
553     }
554 
555     #[cfg(feature = "time")]
556     use time::{format_description::well_known::Rfc3339, OffsetDateTime};
557 
558     #[cfg(feature = "time")]
559     #[test]
datetime_try_from_bounds()560     fn datetime_try_from_bounds() {
561         use std::convert::TryFrom;
562 
563         use super::DateTime;
564         use time::macros::datetime;
565 
566         // 1979-12-31 23:59:59
567         assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59 UTC)).is_err());
568 
569         // 1980-01-01 00:00:00
570         assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00 UTC)).is_ok());
571 
572         // 2107-12-31 23:59:59
573         assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59 UTC)).is_ok());
574 
575         // 2108-01-01 00:00:00
576         assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00 UTC)).is_err());
577     }
578 
579     #[test]
time_conversion()580     fn time_conversion() {
581         use super::DateTime;
582         let dt = DateTime::from_msdos(0x4D71, 0x54CF);
583         assert_eq!(dt.year(), 2018);
584         assert_eq!(dt.month(), 11);
585         assert_eq!(dt.day(), 17);
586         assert_eq!(dt.hour(), 10);
587         assert_eq!(dt.minute(), 38);
588         assert_eq!(dt.second(), 30);
589 
590         #[cfg(feature = "time")]
591         assert_eq!(
592             dt.to_time().unwrap().format(&Rfc3339).unwrap(),
593             "2018-11-17T10:38:30Z"
594         );
595     }
596 
597     #[test]
time_out_of_bounds()598     fn time_out_of_bounds() {
599         use super::DateTime;
600         let dt = DateTime::from_msdos(0xFFFF, 0xFFFF);
601         assert_eq!(dt.year(), 2107);
602         assert_eq!(dt.month(), 15);
603         assert_eq!(dt.day(), 31);
604         assert_eq!(dt.hour(), 31);
605         assert_eq!(dt.minute(), 63);
606         assert_eq!(dt.second(), 62);
607 
608         #[cfg(feature = "time")]
609         assert!(dt.to_time().is_err());
610 
611         let dt = DateTime::from_msdos(0x0000, 0x0000);
612         assert_eq!(dt.year(), 1980);
613         assert_eq!(dt.month(), 0);
614         assert_eq!(dt.day(), 0);
615         assert_eq!(dt.hour(), 0);
616         assert_eq!(dt.minute(), 0);
617         assert_eq!(dt.second(), 0);
618 
619         #[cfg(feature = "time")]
620         assert!(dt.to_time().is_err());
621     }
622 
623     #[cfg(feature = "time")]
624     #[test]
time_at_january()625     fn time_at_january() {
626         use super::DateTime;
627         use std::convert::TryFrom;
628 
629         // 2020-01-01 00:00:00
630         let clock = OffsetDateTime::from_unix_timestamp(1_577_836_800).unwrap();
631 
632         assert!(DateTime::try_from(clock).is_ok());
633     }
634 }
635