• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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