1 //! Types that specify what is contained in a ZIP. 2 3 #[derive(Clone, Copy, Debug, PartialEq)] 4 pub enum System { 5 Dos = 0, 6 Unix = 3, 7 Unknown, 8 } 9 10 impl System { from_u8(system: u8) -> System11 pub fn from_u8(system: u8) -> System { 12 use self::System::*; 13 14 match system { 15 0 => Dos, 16 3 => Unix, 17 _ => Unknown, 18 } 19 } 20 } 21 22 /// A DateTime field to be used for storing timestamps in a zip file 23 /// 24 /// This structure does bounds checking to ensure the date is able to be stored in a zip file. 25 /// 26 /// When constructed manually from a date and time, it will also check if the input is sensible 27 /// (e.g. months are from [1, 12]), but when read from a zip some parts may be out of their normal 28 /// bounds (e.g. month 0, or hour 31). 29 /// 30 /// # Warning 31 /// 32 /// Some utilities use alternative timestamps to improve the accuracy of their 33 /// ZIPs, but we don't parse them yet. [We're working on this](https://github.com/zip-rs/zip/issues/156#issuecomment-652981904), 34 /// however this API shouldn't be considered complete. 35 #[derive(Debug, Clone, Copy)] 36 pub struct DateTime { 37 year: u16, 38 month: u8, 39 day: u8, 40 hour: u8, 41 minute: u8, 42 second: u8, 43 } 44 45 impl ::std::default::Default for DateTime { 46 /// Constructs an 'default' datetime of 1980-01-01 00:00:00 default() -> DateTime47 fn default() -> DateTime { 48 DateTime { 49 year: 1980, 50 month: 1, 51 day: 1, 52 hour: 0, 53 minute: 0, 54 second: 0, 55 } 56 } 57 } 58 59 impl DateTime { 60 /// Converts an msdos (u16, u16) pair to a DateTime object from_msdos(datepart: u16, timepart: u16) -> DateTime61 pub fn from_msdos(datepart: u16, timepart: u16) -> DateTime { 62 let seconds = (timepart & 0b0000000000011111) << 1; 63 let minutes = (timepart & 0b0000011111100000) >> 5; 64 let hours = (timepart & 0b1111100000000000) >> 11; 65 let days = (datepart & 0b0000000000011111) >> 0; 66 let months = (datepart & 0b0000000111100000) >> 5; 67 let years = (datepart & 0b1111111000000000) >> 9; 68 69 DateTime { 70 year: (years + 1980) as u16, 71 month: months as u8, 72 day: days as u8, 73 hour: hours as u8, 74 minute: minutes as u8, 75 second: seconds as u8, 76 } 77 } 78 79 /// Constructs a DateTime from a specific date and time 80 /// 81 /// The bounds are: 82 /// * year: [1980, 2107] 83 /// * month: [1, 12] 84 /// * day: [1, 31] 85 /// * hour: [0, 23] 86 /// * minute: [0, 59] 87 /// * second: [0, 60] from_date_and_time( year: u16, month: u8, day: u8, hour: u8, minute: u8, second: u8, ) -> Result<DateTime, ()>88 pub fn from_date_and_time( 89 year: u16, 90 month: u8, 91 day: u8, 92 hour: u8, 93 minute: u8, 94 second: u8, 95 ) -> Result<DateTime, ()> { 96 if year >= 1980 97 && year <= 2107 98 && month >= 1 99 && month <= 12 100 && day >= 1 101 && day <= 31 102 && hour <= 23 103 && minute <= 59 104 && second <= 60 105 { 106 Ok(DateTime { 107 year, 108 month, 109 day, 110 hour, 111 minute, 112 second, 113 }) 114 } else { 115 Err(()) 116 } 117 } 118 119 #[cfg(feature = "time")] 120 /// Converts a ::time::Tm object to a DateTime 121 /// 122 /// Returns `Err` when this object is out of bounds from_time(tm: ::time::Tm) -> Result<DateTime, ()>123 pub fn from_time(tm: ::time::Tm) -> Result<DateTime, ()> { 124 if tm.tm_year >= 80 125 && tm.tm_year <= 207 126 && tm.tm_mon >= 0 127 && tm.tm_mon <= 11 128 && tm.tm_mday >= 1 129 && tm.tm_mday <= 31 130 && tm.tm_hour >= 0 131 && tm.tm_hour <= 23 132 && tm.tm_min >= 0 133 && tm.tm_min <= 59 134 && tm.tm_sec >= 0 135 && tm.tm_sec <= 60 136 { 137 Ok(DateTime { 138 year: (tm.tm_year + 1900) as u16, 139 month: (tm.tm_mon + 1) as u8, 140 day: tm.tm_mday as u8, 141 hour: tm.tm_hour as u8, 142 minute: tm.tm_min as u8, 143 second: tm.tm_sec as u8, 144 }) 145 } else { 146 Err(()) 147 } 148 } 149 150 /// Gets the time portion of this datetime in the msdos representation timepart(&self) -> u16151 pub fn timepart(&self) -> u16 { 152 ((self.second as u16) >> 1) | ((self.minute as u16) << 5) | ((self.hour as u16) << 11) 153 } 154 155 /// Gets the date portion of this datetime in the msdos representation datepart(&self) -> u16156 pub fn datepart(&self) -> u16 { 157 (self.day as u16) | ((self.month as u16) << 5) | ((self.year - 1980) << 9) 158 } 159 160 #[cfg(feature = "time")] 161 /// Converts the datetime to a Tm structure 162 /// 163 /// The fields `tm_wday`, `tm_yday`, `tm_utcoff` and `tm_nsec` are set to their defaults. to_time(&self) -> ::time::Tm164 pub fn to_time(&self) -> ::time::Tm { 165 ::time::Tm { 166 tm_sec: self.second as i32, 167 tm_min: self.minute as i32, 168 tm_hour: self.hour as i32, 169 tm_mday: self.day as i32, 170 tm_mon: self.month as i32 - 1, 171 tm_year: self.year as i32 - 1900, 172 tm_isdst: -1, 173 ..::time::empty_tm() 174 } 175 } 176 177 /// Get the year. There is no epoch, i.e. 2018 will be returned as 2018. year(&self) -> u16178 pub fn year(&self) -> u16 { 179 self.year 180 } 181 182 /// Get the month, where 1 = january and 12 = december month(&self) -> u8183 pub fn month(&self) -> u8 { 184 self.month 185 } 186 187 /// Get the day day(&self) -> u8188 pub fn day(&self) -> u8 { 189 self.day 190 } 191 192 /// Get the hour hour(&self) -> u8193 pub fn hour(&self) -> u8 { 194 self.hour 195 } 196 197 /// Get the minute minute(&self) -> u8198 pub fn minute(&self) -> u8 { 199 self.minute 200 } 201 202 /// Get the second second(&self) -> u8203 pub fn second(&self) -> u8 { 204 self.second 205 } 206 } 207 208 pub const DEFAULT_VERSION: u8 = 46; 209 210 /// Structure representing a ZIP file. 211 #[derive(Debug, Clone)] 212 pub struct ZipFileData { 213 /// Compatibility of the file attribute information 214 pub system: System, 215 /// Specification version 216 pub version_made_by: u8, 217 /// True if the file is encrypted. 218 pub encrypted: bool, 219 /// Compression method used to store the file 220 pub compression_method: crate::compression::CompressionMethod, 221 /// Last modified time. This will only have a 2 second precision. 222 pub last_modified_time: DateTime, 223 /// CRC32 checksum 224 pub crc32: u32, 225 /// Size of the file in the ZIP 226 pub compressed_size: u64, 227 /// Size of the file when extracted 228 pub uncompressed_size: u64, 229 /// Name of the file 230 pub file_name: String, 231 /// Raw file name. To be used when file_name was incorrectly decoded. 232 pub file_name_raw: Vec<u8>, 233 /// File comment 234 pub file_comment: String, 235 /// Specifies where the local header of the file starts 236 pub header_start: u64, 237 /// Specifies where the central header of the file starts 238 /// 239 /// Note that when this is not known, it is set to 0 240 pub central_header_start: u64, 241 /// Specifies where the compressed data of the file starts 242 pub data_start: u64, 243 /// External file attributes 244 pub external_attributes: u32, 245 } 246 247 impl ZipFileData { file_name_sanitized(&self) -> ::std::path::PathBuf248 pub fn file_name_sanitized(&self) -> ::std::path::PathBuf { 249 let no_null_filename = match self.file_name.find('\0') { 250 Some(index) => &self.file_name[0..index], 251 None => &self.file_name, 252 } 253 .to_string(); 254 255 // zip files can contain both / and \ as separators regardless of the OS 256 // and as we want to return a sanitized PathBuf that only supports the 257 // OS separator let's convert incompatible separators to compatible ones 258 let separator = ::std::path::MAIN_SEPARATOR; 259 let opposite_separator = match separator { 260 '/' => '\\', 261 _ => '/', 262 }; 263 let filename = 264 no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string()); 265 266 ::std::path::Path::new(&filename) 267 .components() 268 .filter(|component| match *component { 269 ::std::path::Component::Normal(..) => true, 270 _ => false, 271 }) 272 .fold(::std::path::PathBuf::new(), |mut path, ref cur| { 273 path.push(cur.as_os_str()); 274 path 275 }) 276 } 277 version_needed(&self) -> u16278 pub fn version_needed(&self) -> u16 { 279 match self.compression_method { 280 #[cfg(feature = "bzip2")] 281 crate::compression::CompressionMethod::Bzip2 => 46, 282 _ => 20, 283 } 284 } 285 } 286 287 #[cfg(test)] 288 mod test { 289 #[test] system()290 fn system() { 291 use super::System; 292 assert_eq!(System::Dos as u16, 0u16); 293 assert_eq!(System::Unix as u16, 3u16); 294 assert_eq!(System::from_u8(0), System::Dos); 295 assert_eq!(System::from_u8(3), System::Unix); 296 } 297 298 #[test] sanitize()299 fn sanitize() { 300 use super::*; 301 let file_name = "/path/../../../../etc/./passwd\0/etc/shadow".to_string(); 302 let data = ZipFileData { 303 system: System::Dos, 304 version_made_by: 0, 305 encrypted: false, 306 compression_method: crate::compression::CompressionMethod::Stored, 307 last_modified_time: DateTime::default(), 308 crc32: 0, 309 compressed_size: 0, 310 uncompressed_size: 0, 311 file_name: file_name.clone(), 312 file_name_raw: file_name.into_bytes(), 313 file_comment: String::new(), 314 header_start: 0, 315 data_start: 0, 316 central_header_start: 0, 317 external_attributes: 0, 318 }; 319 assert_eq!( 320 data.file_name_sanitized(), 321 ::std::path::PathBuf::from("path/etc/passwd") 322 ); 323 } 324 325 #[test] datetime_default()326 fn datetime_default() { 327 use super::DateTime; 328 let dt = DateTime::default(); 329 assert_eq!(dt.timepart(), 0); 330 assert_eq!(dt.datepart(), 0b0000000_0001_00001); 331 } 332 333 #[test] datetime_max()334 fn datetime_max() { 335 use super::DateTime; 336 let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 60).unwrap(); 337 assert_eq!(dt.timepart(), 0b10111_111011_11110); 338 assert_eq!(dt.datepart(), 0b1111111_1100_11111); 339 } 340 341 #[test] datetime_bounds()342 fn datetime_bounds() { 343 use super::DateTime; 344 345 assert!(DateTime::from_date_and_time(2000, 1, 1, 23, 59, 60).is_ok()); 346 assert!(DateTime::from_date_and_time(2000, 1, 1, 24, 0, 0).is_err()); 347 assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 60, 0).is_err()); 348 assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 0, 61).is_err()); 349 350 assert!(DateTime::from_date_and_time(2107, 12, 31, 0, 0, 0).is_ok()); 351 assert!(DateTime::from_date_and_time(1980, 1, 1, 0, 0, 0).is_ok()); 352 assert!(DateTime::from_date_and_time(1979, 1, 1, 0, 0, 0).is_err()); 353 assert!(DateTime::from_date_and_time(1980, 0, 1, 0, 0, 0).is_err()); 354 assert!(DateTime::from_date_and_time(1980, 1, 0, 0, 0, 0).is_err()); 355 assert!(DateTime::from_date_and_time(2108, 12, 31, 0, 0, 0).is_err()); 356 assert!(DateTime::from_date_and_time(2107, 13, 31, 0, 0, 0).is_err()); 357 assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err()); 358 } 359 360 #[cfg(feature = "time")] 361 #[test] datetime_from_time_bounds()362 fn datetime_from_time_bounds() { 363 use super::DateTime; 364 365 // 1979-12-31 23:59:59 366 assert!(DateTime::from_time(::time::Tm { 367 tm_sec: 59, 368 tm_min: 59, 369 tm_hour: 23, 370 tm_mday: 31, 371 tm_mon: 11, // tm_mon has number range [0, 11] 372 tm_year: 79, // 1979 - 1900 = 79 373 ..::time::empty_tm() 374 }) 375 .is_err()); 376 377 // 1980-01-01 00:00:00 378 assert!(DateTime::from_time(::time::Tm { 379 tm_sec: 0, 380 tm_min: 0, 381 tm_hour: 0, 382 tm_mday: 1, 383 tm_mon: 0, // tm_mon has number range [0, 11] 384 tm_year: 80, // 1980 - 1900 = 80 385 ..::time::empty_tm() 386 }) 387 .is_ok()); 388 389 // 2107-12-31 23:59:59 390 assert!(DateTime::from_time(::time::Tm { 391 tm_sec: 59, 392 tm_min: 59, 393 tm_hour: 23, 394 tm_mday: 31, 395 tm_mon: 11, // tm_mon has number range [0, 11] 396 tm_year: 207, // 2107 - 1900 = 207 397 ..::time::empty_tm() 398 }) 399 .is_ok()); 400 401 // 2108-01-01 00:00:00 402 assert!(DateTime::from_time(::time::Tm { 403 tm_sec: 0, 404 tm_min: 0, 405 tm_hour: 0, 406 tm_mday: 1, 407 tm_mon: 0, // tm_mon has number range [0, 11] 408 tm_year: 208, // 2108 - 1900 = 208 409 ..::time::empty_tm() 410 }) 411 .is_err()); 412 } 413 414 #[test] time_conversion()415 fn time_conversion() { 416 use super::DateTime; 417 let dt = DateTime::from_msdos(0x4D71, 0x54CF); 418 assert_eq!(dt.year(), 2018); 419 assert_eq!(dt.month(), 11); 420 assert_eq!(dt.day(), 17); 421 assert_eq!(dt.hour(), 10); 422 assert_eq!(dt.minute(), 38); 423 assert_eq!(dt.second(), 30); 424 425 #[cfg(feature = "time")] 426 assert_eq!( 427 format!("{}", dt.to_time().rfc3339()), 428 "2018-11-17T10:38:30Z" 429 ); 430 } 431 432 #[test] time_out_of_bounds()433 fn time_out_of_bounds() { 434 use super::DateTime; 435 let dt = DateTime::from_msdos(0xFFFF, 0xFFFF); 436 assert_eq!(dt.year(), 2107); 437 assert_eq!(dt.month(), 15); 438 assert_eq!(dt.day(), 31); 439 assert_eq!(dt.hour(), 31); 440 assert_eq!(dt.minute(), 63); 441 assert_eq!(dt.second(), 62); 442 443 #[cfg(feature = "time")] 444 assert_eq!( 445 format!("{}", dt.to_time().rfc3339()), 446 "2107-15-31T31:63:62Z" 447 ); 448 449 let dt = DateTime::from_msdos(0x0000, 0x0000); 450 assert_eq!(dt.year(), 1980); 451 assert_eq!(dt.month(), 0); 452 assert_eq!(dt.day(), 0); 453 assert_eq!(dt.hour(), 0); 454 assert_eq!(dt.minute(), 0); 455 assert_eq!(dt.second(), 0); 456 457 #[cfg(feature = "time")] 458 assert_eq!( 459 format!("{}", dt.to_time().rfc3339()), 460 "1980-00-00T00:00:00Z" 461 ); 462 } 463 464 #[cfg(feature = "time")] 465 #[test] time_at_january()466 fn time_at_january() { 467 use super::DateTime; 468 469 // 2020-01-01 00:00:00 470 let clock = ::time::Timespec::new(1577836800, 0); 471 let tm = ::time::at_utc(clock); 472 assert!(DateTime::from_time(tm).is_ok()); 473 } 474 } 475