diff options
Diffstat (limited to 'third_party/rust/zip/src/types.rs')
-rw-r--r-- | third_party/rust/zip/src/types.rs | 608 |
1 files changed, 608 insertions, 0 deletions
diff --git a/third_party/rust/zip/src/types.rs b/third_party/rust/zip/src/types.rs new file mode 100644 index 0000000000..ad3a5700b2 --- /dev/null +++ b/third_party/rust/zip/src/types.rs @@ -0,0 +1,608 @@ +//! Types that specify what is contained in a ZIP. +#[cfg(feature = "time")] +use std::convert::{TryFrom, TryInto}; +#[cfg(not(any( + all(target_arch = "arm", target_pointer_width = "32"), + target_arch = "mips", + target_arch = "powerpc" +)))] +use std::sync::atomic; +#[cfg(not(feature = "time"))] +use std::time::SystemTime; +#[cfg(doc)] +use {crate::read::ZipFile, crate::write::FileOptions}; + +#[cfg(any( + all(target_arch = "arm", target_pointer_width = "32"), + target_arch = "mips", + target_arch = "powerpc" +))] +mod atomic { + use crossbeam_utils::sync::ShardedLock; + pub use std::sync::atomic::Ordering; + + #[derive(Debug, Default)] + pub struct AtomicU64 { + value: ShardedLock<u64>, + } + + impl AtomicU64 { + pub fn new(v: u64) -> Self { + Self { + value: ShardedLock::new(v), + } + } + pub fn get_mut(&mut self) -> &mut u64 { + self.value.get_mut().unwrap() + } + pub fn load(&self, _: Ordering) -> u64 { + *self.value.read().unwrap() + } + pub fn store(&self, value: u64, _: Ordering) { + *self.value.write().unwrap() = value; + } + } +} + +#[cfg(feature = "time")] +use crate::result::DateTimeRangeError; +#[cfg(feature = "time")] +use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum System { + Dos = 0, + Unix = 3, + Unknown, +} + +impl System { + pub fn from_u8(system: u8) -> System { + use self::System::*; + + match system { + 0 => Dos, + 3 => Unix, + _ => Unknown, + } + } +} + +/// Representation of a moment in time. +/// +/// Zip files use an old format from DOS to store timestamps, +/// with its own set of peculiarities. +/// For example, it has a resolution of 2 seconds! +/// +/// A [`DateTime`] can be stored directly in a zipfile with [`FileOptions::last_modified_time`], +/// or read from one with [`ZipFile::last_modified`] +/// +/// # Warning +/// +/// Because there is no timezone associated with the [`DateTime`], they should ideally only +/// be used for user-facing descriptions. This also means [`DateTime::to_time`] returns an +/// [`OffsetDateTime`] (which is the equivalent of chrono's `NaiveDateTime`). +/// +/// Modern zip files store more precise timestamps, which are ignored by [`crate::read::ZipArchive`], +/// so keep in mind that these timestamps are unreliable. [We're working on this](https://github.com/zip-rs/zip/issues/156#issuecomment-652981904). +#[derive(Debug, Clone, Copy)] +pub struct DateTime { + year: u16, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, +} + +impl ::std::default::Default for DateTime { + /// Constructs an 'default' datetime of 1980-01-01 00:00:00 + fn default() -> DateTime { + DateTime { + year: 1980, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + } + } +} + +impl DateTime { + /// Converts an msdos (u16, u16) pair to a DateTime object + pub fn from_msdos(datepart: u16, timepart: u16) -> DateTime { + let seconds = (timepart & 0b0000000000011111) << 1; + let minutes = (timepart & 0b0000011111100000) >> 5; + let hours = (timepart & 0b1111100000000000) >> 11; + let days = datepart & 0b0000000000011111; + let months = (datepart & 0b0000000111100000) >> 5; + let years = (datepart & 0b1111111000000000) >> 9; + + DateTime { + year: years + 1980, + month: months as u8, + day: days as u8, + hour: hours as u8, + minute: minutes as u8, + second: seconds as u8, + } + } + + /// Constructs a DateTime from a specific date and time + /// + /// The bounds are: + /// * year: [1980, 2107] + /// * month: [1, 12] + /// * day: [1, 31] + /// * hour: [0, 23] + /// * minute: [0, 59] + /// * second: [0, 60] + #[allow(clippy::result_unit_err)] + pub fn from_date_and_time( + year: u16, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + ) -> Result<DateTime, ()> { + if (1980..=2107).contains(&year) + && (1..=12).contains(&month) + && (1..=31).contains(&day) + && hour <= 23 + && minute <= 59 + && second <= 60 + { + Ok(DateTime { + year, + month, + day, + hour, + minute, + second, + }) + } else { + Err(()) + } + } + + #[cfg(feature = "time")] + /// Converts a OffsetDateTime object to a DateTime + /// + /// Returns `Err` when this object is out of bounds + #[allow(clippy::result_unit_err)] + #[deprecated(note = "use `DateTime::try_from()`")] + pub fn from_time(dt: OffsetDateTime) -> Result<DateTime, ()> { + dt.try_into().map_err(|_err| ()) + } + + /// Gets the time portion of this datetime in the msdos representation + pub fn timepart(&self) -> u16 { + ((self.second as u16) >> 1) | ((self.minute as u16) << 5) | ((self.hour as u16) << 11) + } + + /// Gets the date portion of this datetime in the msdos representation + pub fn datepart(&self) -> u16 { + (self.day as u16) | ((self.month as u16) << 5) | ((self.year - 1980) << 9) + } + + #[cfg(feature = "time")] + /// Converts the DateTime to a OffsetDateTime structure + pub fn to_time(&self) -> Result<OffsetDateTime, ComponentRange> { + let date = + Date::from_calendar_date(self.year as i32, Month::try_from(self.month)?, self.day)?; + let time = Time::from_hms(self.hour, self.minute, self.second)?; + Ok(PrimitiveDateTime::new(date, time).assume_utc()) + } + + /// Get the year. There is no epoch, i.e. 2018 will be returned as 2018. + pub fn year(&self) -> u16 { + self.year + } + + /// Get the month, where 1 = january and 12 = december + /// + /// # Warning + /// + /// When read from a zip file, this may not be a reasonable value + pub fn month(&self) -> u8 { + self.month + } + + /// Get the day + /// + /// # Warning + /// + /// When read from a zip file, this may not be a reasonable value + pub fn day(&self) -> u8 { + self.day + } + + /// Get the hour + /// + /// # Warning + /// + /// When read from a zip file, this may not be a reasonable value + pub fn hour(&self) -> u8 { + self.hour + } + + /// Get the minute + /// + /// # Warning + /// + /// When read from a zip file, this may not be a reasonable value + pub fn minute(&self) -> u8 { + self.minute + } + + /// Get the second + /// + /// # Warning + /// + /// When read from a zip file, this may not be a reasonable value + pub fn second(&self) -> u8 { + self.second + } +} + +#[cfg(feature = "time")] +impl TryFrom<OffsetDateTime> for DateTime { + type Error = DateTimeRangeError; + + fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> { + if dt.year() >= 1980 && dt.year() <= 2107 { + Ok(DateTime { + year: (dt.year()) as u16, + month: (dt.month()) as u8, + day: dt.day(), + hour: dt.hour(), + minute: dt.minute(), + second: dt.second(), + }) + } else { + Err(DateTimeRangeError) + } + } +} + +pub const DEFAULT_VERSION: u8 = 46; + +/// A type like `AtomicU64` except it implements `Clone` and has predefined +/// ordering. +/// +/// It uses `Relaxed` ordering because it is not used for synchronisation. +#[derive(Debug)] +pub struct AtomicU64(atomic::AtomicU64); + +impl AtomicU64 { + pub fn new(v: u64) -> Self { + Self(atomic::AtomicU64::new(v)) + } + + pub fn load(&self) -> u64 { + self.0.load(atomic::Ordering::Relaxed) + } + + pub fn store(&self, val: u64) { + self.0.store(val, atomic::Ordering::Relaxed) + } + + pub fn get_mut(&mut self) -> &mut u64 { + self.0.get_mut() + } +} + +impl Clone for AtomicU64 { + fn clone(&self) -> Self { + Self(atomic::AtomicU64::new(self.load())) + } +} + +/// Structure representing a ZIP file. +#[derive(Debug, Clone)] +pub struct ZipFileData { + /// Compatibility of the file attribute information + pub system: System, + /// Specification version + pub version_made_by: u8, + /// True if the file is encrypted. + pub encrypted: bool, + /// True if the file uses a data-descriptor section + pub using_data_descriptor: bool, + /// Compression method used to store the file + pub compression_method: crate::compression::CompressionMethod, + /// Compression level to store the file + pub compression_level: Option<i32>, + /// Last modified time. This will only have a 2 second precision. + pub last_modified_time: DateTime, + /// CRC32 checksum + pub crc32: u32, + /// Size of the file in the ZIP + pub compressed_size: u64, + /// Size of the file when extracted + pub uncompressed_size: u64, + /// Name of the file + pub file_name: String, + /// Raw file name. To be used when file_name was incorrectly decoded. + pub file_name_raw: Vec<u8>, + /// Extra field usually used for storage expansion + pub extra_field: Vec<u8>, + /// File comment + pub file_comment: String, + /// Specifies where the local header of the file starts + pub header_start: u64, + /// Specifies where the central header of the file starts + /// + /// Note that when this is not known, it is set to 0 + pub central_header_start: u64, + /// Specifies where the compressed data of the file starts + pub data_start: AtomicU64, + /// External file attributes + pub external_attributes: u32, + /// Reserve local ZIP64 extra field + pub large_file: bool, + /// AES mode if applicable + pub aes_mode: Option<(AesMode, AesVendorVersion)>, +} + +impl ZipFileData { + pub fn file_name_sanitized(&self) -> ::std::path::PathBuf { + let no_null_filename = match self.file_name.find('\0') { + Some(index) => &self.file_name[0..index], + None => &self.file_name, + } + .to_string(); + + // zip files can contain both / and \ as separators regardless of the OS + // and as we want to return a sanitized PathBuf that only supports the + // OS separator let's convert incompatible separators to compatible ones + let separator = ::std::path::MAIN_SEPARATOR; + let opposite_separator = match separator { + '/' => '\\', + _ => '/', + }; + let filename = + no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string()); + + ::std::path::Path::new(&filename) + .components() + .filter(|component| matches!(*component, ::std::path::Component::Normal(..))) + .fold(::std::path::PathBuf::new(), |mut path, ref cur| { + path.push(cur.as_os_str()); + path + }) + } + + pub fn zip64_extension(&self) -> bool { + self.uncompressed_size > 0xFFFFFFFF + || self.compressed_size > 0xFFFFFFFF + || self.header_start > 0xFFFFFFFF + } + + pub fn version_needed(&self) -> u16 { + // higher versions matched first + match (self.zip64_extension(), self.compression_method) { + #[cfg(feature = "bzip2")] + (_, crate::compression::CompressionMethod::Bzip2) => 46, + (true, _) => 45, + _ => 20, + } + } +} + +/// The encryption specification used to encrypt a file with AES. +/// +/// According to the [specification](https://www.winzip.com/win/en/aes_info.html#winzip11) AE-2 +/// does not make use of the CRC check. +#[derive(Copy, Clone, Debug)] +pub enum AesVendorVersion { + Ae1, + Ae2, +} + +/// AES variant used. +#[derive(Copy, Clone, Debug)] +pub enum AesMode { + Aes128, + Aes192, + Aes256, +} + +#[cfg(feature = "aes-crypto")] +impl AesMode { + pub fn salt_length(&self) -> usize { + self.key_length() / 2 + } + + pub fn key_length(&self) -> usize { + match self { + Self::Aes128 => 16, + Self::Aes192 => 24, + Self::Aes256 => 32, + } + } +} + +#[cfg(test)] +mod test { + #[test] + fn system() { + use super::System; + assert_eq!(System::Dos as u16, 0u16); + assert_eq!(System::Unix as u16, 3u16); + assert_eq!(System::from_u8(0), System::Dos); + assert_eq!(System::from_u8(3), System::Unix); + } + + #[test] + fn sanitize() { + use super::*; + let file_name = "/path/../../../../etc/./passwd\0/etc/shadow".to_string(); + let data = ZipFileData { + system: System::Dos, + version_made_by: 0, + encrypted: false, + using_data_descriptor: false, + compression_method: crate::compression::CompressionMethod::Stored, + compression_level: None, + last_modified_time: DateTime::default(), + crc32: 0, + compressed_size: 0, + uncompressed_size: 0, + file_name: file_name.clone(), + file_name_raw: file_name.into_bytes(), + extra_field: Vec::new(), + file_comment: String::new(), + header_start: 0, + data_start: AtomicU64::new(0), + central_header_start: 0, + external_attributes: 0, + large_file: false, + aes_mode: None, + }; + assert_eq!( + data.file_name_sanitized(), + ::std::path::PathBuf::from("path/etc/passwd") + ); + } + + #[test] + #[allow(clippy::unusual_byte_groupings)] + fn datetime_default() { + use super::DateTime; + let dt = DateTime::default(); + assert_eq!(dt.timepart(), 0); + assert_eq!(dt.datepart(), 0b0000000_0001_00001); + } + + #[test] + #[allow(clippy::unusual_byte_groupings)] + fn datetime_max() { + use super::DateTime; + let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 60).unwrap(); + assert_eq!(dt.timepart(), 0b10111_111011_11110); + assert_eq!(dt.datepart(), 0b1111111_1100_11111); + } + + #[test] + fn datetime_bounds() { + use super::DateTime; + + assert!(DateTime::from_date_and_time(2000, 1, 1, 23, 59, 60).is_ok()); + assert!(DateTime::from_date_and_time(2000, 1, 1, 24, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 60, 0).is_err()); + assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 0, 61).is_err()); + + assert!(DateTime::from_date_and_time(2107, 12, 31, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(1980, 1, 1, 0, 0, 0).is_ok()); + assert!(DateTime::from_date_and_time(1979, 1, 1, 0, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(1980, 0, 1, 0, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(1980, 1, 0, 0, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(2108, 12, 31, 0, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(2107, 13, 31, 0, 0, 0).is_err()); + assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err()); + } + + #[cfg(feature = "time")] + use time::{format_description::well_known::Rfc3339, OffsetDateTime}; + + #[cfg(feature = "time")] + #[test] + fn datetime_from_time_bounds() { + use std::convert::TryFrom; + + use super::DateTime; + use time::macros::datetime; + + // 1979-12-31 23:59:59 + assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59 UTC)).is_err()); + + // 1980-01-01 00:00:00 + assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00 UTC)).is_ok()); + + // 2107-12-31 23:59:59 + assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59 UTC)).is_ok()); + + // 2108-01-01 00:00:00 + assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00 UTC)).is_err()); + } + + #[cfg(feature = "time")] + #[test] + fn datetime_try_from_bounds() { + use std::convert::TryFrom; + + use super::DateTime; + use time::macros::datetime; + + // 1979-12-31 23:59:59 + assert!(DateTime::try_from(datetime!(1979-12-31 23:59:59 UTC)).is_err()); + + // 1980-01-01 00:00:00 + assert!(DateTime::try_from(datetime!(1980-01-01 00:00:00 UTC)).is_ok()); + + // 2107-12-31 23:59:59 + assert!(DateTime::try_from(datetime!(2107-12-31 23:59:59 UTC)).is_ok()); + + // 2108-01-01 00:00:00 + assert!(DateTime::try_from(datetime!(2108-01-01 00:00:00 UTC)).is_err()); + } + + #[test] + fn time_conversion() { + use super::DateTime; + let dt = DateTime::from_msdos(0x4D71, 0x54CF); + assert_eq!(dt.year(), 2018); + assert_eq!(dt.month(), 11); + assert_eq!(dt.day(), 17); + assert_eq!(dt.hour(), 10); + assert_eq!(dt.minute(), 38); + assert_eq!(dt.second(), 30); + + #[cfg(feature = "time")] + assert_eq!( + dt.to_time().unwrap().format(&Rfc3339).unwrap(), + "2018-11-17T10:38:30Z" + ); + } + + #[test] + fn time_out_of_bounds() { + use super::DateTime; + let dt = DateTime::from_msdos(0xFFFF, 0xFFFF); + assert_eq!(dt.year(), 2107); + assert_eq!(dt.month(), 15); + assert_eq!(dt.day(), 31); + assert_eq!(dt.hour(), 31); + assert_eq!(dt.minute(), 63); + assert_eq!(dt.second(), 62); + + #[cfg(feature = "time")] + assert!(dt.to_time().is_err()); + + let dt = DateTime::from_msdos(0x0000, 0x0000); + assert_eq!(dt.year(), 1980); + assert_eq!(dt.month(), 0); + assert_eq!(dt.day(), 0); + assert_eq!(dt.hour(), 0); + assert_eq!(dt.minute(), 0); + assert_eq!(dt.second(), 0); + + #[cfg(feature = "time")] + assert!(dt.to_time().is_err()); + } + + #[cfg(feature = "time")] + #[test] + fn time_at_january() { + use super::DateTime; + use std::convert::TryFrom; + + // 2020-01-01 00:00:00 + let clock = OffsetDateTime::from_unix_timestamp(1_577_836_800).unwrap(); + + assert!(DateTime::try_from(clock).is_ok()); + } +} |