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