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