summaryrefslogtreecommitdiffstats
path: root/vendor/zip/src/types.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/zip/src/types.rs')
-rw-r--r--vendor/zip/src/types.rs573
1 files changed, 573 insertions, 0 deletions
diff --git a/vendor/zip/src/types.rs b/vendor/zip/src/types.rs
new file mode 100644
index 000000000..b65fad401
--- /dev/null
+++ b/vendor/zip/src/types.rs
@@ -0,0 +1,573 @@
+//! Types that specify what is contained in a ZIP.
+#[cfg(doc)]
+use {crate::read::ZipFile, crate::write::FileOptions};
+
+#[cfg(not(any(
+ all(target_arch = "arm", target_pointer_width = "32"),
+ target_arch = "mips",
+ target_arch = "powerpc"
+)))]
+use std::sync::atomic;
+
+#[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 time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+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) as u16,
+ 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)
+ && month >= 1
+ && month <= 12
+ && day >= 1
+ && day <= 31
+ && 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)]
+ pub fn from_time(dt: OffsetDateTime) -> Result<DateTime, ()> {
+ if dt.year() >= 1980 && dt.year() <= 2107 {
+ Ok(DateTime {
+ year: (dt.year()) as u16,
+ month: (dt.month()) as u8,
+ day: dt.day() as u8,
+ hour: dt.hour() as u8,
+ minute: dt.minute() as u8,
+ second: dt.second() as u8,
+ })
+ } else {
+ 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> {
+ use std::convert::TryFrom;
+
+ 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
+ }
+}
+
+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 super::DateTime;
+ use time::macros::datetime;
+
+ // 1979-12-31 23:59:59
+ assert!(DateTime::from_time(datetime!(1979-12-31 23:59:59 UTC)).is_err());
+
+ // 1980-01-01 00:00:00
+ assert!(DateTime::from_time(datetime!(1980-01-01 00:00:00 UTC)).is_ok());
+
+ // 2107-12-31 23:59:59
+ assert!(DateTime::from_time(datetime!(2107-12-31 23:59:59 UTC)).is_ok());
+
+ // 2108-01-01 00:00:00
+ assert!(DateTime::from_time(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;
+
+ // 2020-01-01 00:00:00
+ let clock = OffsetDateTime::from_unix_timestamp(1_577_836_800).unwrap();
+
+ assert!(DateTime::from_time(clock).is_ok());
+ }
+}