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