1 //! Types that specify what is contained in a ZIP.
3 #[derive(Clone, Copy, Debug, PartialEq)]
11 pub fn from_u8(system: u8) -> System {
22 /// A DateTime field to be used for storing timestamps in a zip file
24 /// This structure does bounds checking to ensure the date is able to be stored in a zip file.
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).
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)]
45 impl ::std::default::Default for DateTime {
46 /// Constructs an 'default' datetime of 1980-01-01 00:00:00
47 fn default() -> DateTime {
60 /// Converts an msdos (u16, u16) pair to a DateTime object
61 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;
70 year: (years + 1980) as u16,
74 minute: minutes as u8,
75 second: seconds as u8,
79 /// Constructs a DateTime from a specific date and time
82 /// * year: [1980, 2107]
88 pub fn from_date_and_time(
95 ) -> Result<DateTime, ()> {
119 #[cfg(feature = "time")]
120 /// Converts a ::time::Tm object to a DateTime
122 /// Returns `Err` when this object is out of bounds
123 pub fn from_time(tm: ::time::Tm) -> Result<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,
150 /// Gets the time portion of this datetime in the msdos representation
151 pub fn timepart(&self) -> u16 {
152 ((self.second as u16) >> 1) | ((self.minute as u16) << 5) | ((self.hour as u16) << 11)
155 /// Gets the date portion of this datetime in the msdos representation
156 pub fn datepart(&self) -> u16 {
157 (self.day as u16) | ((self.month as u16) << 5) | ((self.year - 1980) << 9)
160 #[cfg(feature = "time")]
161 /// Converts the datetime to a Tm structure
163 /// The fields `tm_wday`, `tm_yday`, `tm_utcoff` and `tm_nsec` are set to their defaults.
164 pub fn to_time(&self) -> ::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,
177 /// Get the year. There is no epoch, i.e. 2018 will be returned as 2018.
178 pub fn year(&self) -> u16 {
182 /// Get the month, where 1 = january and 12 = december
183 pub fn month(&self) -> u8 {
188 pub fn day(&self) -> u8 {
193 pub fn hour(&self) -> u8 {
198 pub fn minute(&self) -> u8 {
203 pub fn second(&self) -> u8 {
208 pub const DEFAULT_VERSION: u8 = 46;
210 /// Structure representing a ZIP file.
211 #[derive(Debug, Clone)]
212 pub struct ZipFileData {
213 /// Compatibility of the file attribute information
215 /// Specification version
216 pub version_made_by: u8,
217 /// True if the file is encrypted.
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,
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,
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>,
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
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
243 /// External file attributes
244 pub external_attributes: u32,
248 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,
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 {
264 no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string());
266 ::std::path::Path::new(&filename)
268 .filter(|component| match *component {
269 ::std::path::Component::Normal(..) => true,
272 .fold(::std::path::PathBuf::new(), |mut path, ref cur| {
273 path.push(cur.as_os_str());
278 pub fn version_needed(&self) -> u16 {
279 match self.compression_method {
280 #[cfg(feature = "bzip2")]
281 crate::compression::CompressionMethod::Bzip2 => 46,
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);
301 let file_name = "/path/../../../../etc/./passwd\0/etc/shadow".to_string();
302 let data = ZipFileData {
306 compression_method: crate::compression::CompressionMethod::Stored,
307 last_modified_time: DateTime::default(),
310 uncompressed_size: 0,
311 file_name: file_name.clone(),
312 file_name_raw: file_name.into_bytes(),
313 file_comment: String::new(),
316 central_header_start: 0,
317 external_attributes: 0,
320 data.file_name_sanitized(),
321 ::std::path::PathBuf::from("path/etc/passwd")
326 fn datetime_default() {
328 let dt = DateTime::default();
329 assert_eq!(dt.timepart(), 0);
330 assert_eq!(dt.datepart(), 0b0000000_0001_00001);
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);
342 fn datetime_bounds() {
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());
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());
360 #[cfg(feature = "time")]
362 fn datetime_from_time_bounds() {
365 // 1979-12-31 23:59:59
366 assert!(DateTime::from_time(::time::Tm {
371 tm_mon: 11, // tm_mon has number range [0, 11]
372 tm_year: 79, // 1979 - 1900 = 79
377 // 1980-01-01 00:00:00
378 assert!(DateTime::from_time(::time::Tm {
383 tm_mon: 0, // tm_mon has number range [0, 11]
384 tm_year: 80, // 1980 - 1900 = 80
389 // 2107-12-31 23:59:59
390 assert!(DateTime::from_time(::time::Tm {
395 tm_mon: 11, // tm_mon has number range [0, 11]
396 tm_year: 207, // 2107 - 1900 = 207
401 // 2108-01-01 00:00:00
402 assert!(DateTime::from_time(::time::Tm {
407 tm_mon: 0, // tm_mon has number range [0, 11]
408 tm_year: 208, // 2108 - 1900 = 208
415 fn time_conversion() {
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);
425 #[cfg(feature = "time")]
427 format!("{}", dt.to_time().rfc3339()),
428 "2018-11-17T10:38:30Z"
433 fn time_out_of_bounds() {
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);
443 #[cfg(feature = "time")]
445 format!("{}", dt.to_time().rfc3339()),
446 "2107-15-31T31:63:62Z"
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);
457 #[cfg(feature = "time")]
459 format!("{}", dt.to_time().rfc3339()),
460 "1980-00-00T00:00:00Z"
464 #[cfg(feature = "time")]
466 fn time_at_january() {
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());