1#[cfg(doc)]
3use {crate::read::ZipFile, crate::write::FileOptions};
4
5use std::path;
6
7#[cfg(not(any(
8 all(target_arch = "arm", target_pointer_width = "32"),
9 target_arch = "mips",
10 target_arch = "powerpc"
11)))]
12use std::sync::atomic;
13
14mod ffi {
15 pub const S_IFDIR: u32 = 0o0040000;
16 pub const S_IFREG: u32 = 0o0100000;
17}
18
19#[cfg(any(
20 all(target_arch = "arm", target_pointer_width = "32"),
21 target_arch = "mips",
22 target_arch = "powerpc"
23))]
24mod atomic {
25 use crossbeam_utils::sync::ShardedLock;
26 pub use std::sync::atomic::Ordering;
27
28 #[derive(Debug, Default)]
29 pub struct AtomicU64 {
30 value: ShardedLock<u64>,
31 }
32
33 impl AtomicU64 {
34 pub fn new(v: u64) -> Self {
35 Self {
36 value: ShardedLock::new(v),
37 }
38 }
39 pub fn get_mut(&mut self) -> &mut u64 {
40 self.value.get_mut().unwrap()
41 }
42 pub fn load(&self, _: Ordering) -> u64 {
43 *self.value.read().unwrap()
44 }
45 pub fn store(&self, value: u64, _: Ordering) {
46 *self.value.write().unwrap() = value;
47 }
48 }
49}
50
51#[cfg(feature = "time")]
52use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
53
54#[derive(Clone, Copy, Debug, PartialEq)]
55pub enum System {
56 Dos = 0,
57 Unix = 3,
58 Unknown,
59}
60
61impl System {
62 pub fn from_u8(system: u8) -> System {
63 use self::System::*;
64
65 match system {
66 0 => Dos,
67 3 => Unix,
68 _ => Unknown,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Copy)]
91pub struct DateTime {
92 year: u16,
93 month: u8,
94 day: u8,
95 hour: u8,
96 minute: u8,
97 second: u8,
98}
99
100impl ::std::default::Default for DateTime {
101 fn default() -> DateTime {
103 DateTime {
104 year: 1980,
105 month: 1,
106 day: 1,
107 hour: 0,
108 minute: 0,
109 second: 0,
110 }
111 }
112}
113
114impl DateTime {
115 pub fn from_msdos(datepart: u16, timepart: u16) -> DateTime {
117 let seconds = (timepart & 0b0000000000011111) << 1;
118 let minutes = (timepart & 0b0000011111100000) >> 5;
119 let hours = (timepart & 0b1111100000000000) >> 11;
120 let days = datepart & 0b0000000000011111;
121 let months = (datepart & 0b0000000111100000) >> 5;
122 let years = (datepart & 0b1111111000000000) >> 9;
123
124 DateTime {
125 year: (years + 1980) as u16,
126 month: months as u8,
127 day: days as u8,
128 hour: hours as u8,
129 minute: minutes as u8,
130 second: seconds as u8,
131 }
132 }
133
134 #[allow(clippy::result_unit_err)]
144 pub fn from_date_and_time(
145 year: u16,
146 month: u8,
147 day: u8,
148 hour: u8,
149 minute: u8,
150 second: u8,
151 ) -> Result<DateTime, ()> {
152 if (1980..=2107).contains(&year)
153 && month >= 1
154 && month <= 12
155 && day >= 1
156 && day <= 31
157 && hour <= 23
158 && minute <= 59
159 && second <= 60
160 {
161 Ok(DateTime {
162 year,
163 month,
164 day,
165 hour,
166 minute,
167 second,
168 })
169 } else {
170 Err(())
171 }
172 }
173
174 #[cfg(feature = "time")]
175 #[allow(clippy::result_unit_err)]
179 pub fn from_time(dt: OffsetDateTime) -> Result<DateTime, ()> {
180 if dt.year() >= 1980 && dt.year() <= 2107 {
181 Ok(DateTime {
182 year: (dt.year()) as u16,
183 month: (dt.month()) as u8,
184 day: dt.day() as u8,
185 hour: dt.hour() as u8,
186 minute: dt.minute() as u8,
187 second: dt.second() as u8,
188 })
189 } else {
190 Err(())
191 }
192 }
193
194 pub fn timepart(&self) -> u16 {
196 ((self.second as u16) >> 1) | ((self.minute as u16) << 5) | ((self.hour as u16) << 11)
197 }
198
199 pub fn datepart(&self) -> u16 {
201 (self.day as u16) | ((self.month as u16) << 5) | ((self.year - 1980) << 9)
202 }
203
204 #[cfg(feature = "time")]
205 pub fn to_time(&self) -> Result<OffsetDateTime, ComponentRange> {
207 use std::convert::TryFrom;
208
209 let date =
210 Date::from_calendar_date(self.year as i32, Month::try_from(self.month)?, self.day)?;
211 let time = Time::from_hms(self.hour, self.minute, self.second)?;
212 Ok(PrimitiveDateTime::new(date, time).assume_utc())
213 }
214
215 pub fn year(&self) -> u16 {
217 self.year
218 }
219
220 pub fn month(&self) -> u8 {
226 self.month
227 }
228
229 pub fn day(&self) -> u8 {
235 self.day
236 }
237
238 pub fn hour(&self) -> u8 {
244 self.hour
245 }
246
247 pub fn minute(&self) -> u8 {
253 self.minute
254 }
255
256 pub fn second(&self) -> u8 {
262 self.second
263 }
264}
265
266pub const DEFAULT_VERSION: u8 = 46;
267
268#[derive(Debug)]
273pub struct AtomicU64(atomic::AtomicU64);
274
275impl AtomicU64 {
276 pub fn new(v: u64) -> Self {
277 Self(atomic::AtomicU64::new(v))
278 }
279
280 pub fn load(&self) -> u64 {
281 self.0.load(atomic::Ordering::Relaxed)
282 }
283
284 pub fn store(&self, val: u64) {
285 self.0.store(val, atomic::Ordering::Relaxed)
286 }
287
288 pub fn get_mut(&mut self) -> &mut u64 {
289 self.0.get_mut()
290 }
291}
292
293impl Clone for AtomicU64 {
294 fn clone(&self) -> Self {
295 Self(atomic::AtomicU64::new(self.load()))
296 }
297}
298
299#[derive(Debug, Clone)]
301pub struct ZipFileData {
302 pub system: System,
304 pub version_made_by: u8,
306 pub encrypted: bool,
308 pub using_data_descriptor: bool,
310 pub compression_method: crate::compression::CompressionMethod,
312 pub compression_level: Option<i32>,
314 pub last_modified_time: DateTime,
316 pub crc32: u32,
318 pub compressed_size: u64,
320 pub uncompressed_size: u64,
322 pub file_name: String,
324 pub file_name_raw: Vec<u8>,
326 pub extra_field: Vec<u8>,
328 pub file_comment: String,
330 pub header_start: u64,
332 pub central_header_start: u64,
336 pub data_start: AtomicU64,
338 pub external_attributes: u32,
340 pub large_file: bool,
342 pub aes_mode: Option<(AesMode, AesVendorVersion)>,
344}
345
346impl ZipFileData {
347 pub fn file_name_sanitized(&self) -> ::std::path::PathBuf {
348 let no_null_filename = match self.file_name.find('\0') {
349 Some(index) => &self.file_name[0..index],
350 None => &self.file_name,
351 }
352 .to_string();
353
354 let separator = ::std::path::MAIN_SEPARATOR;
358 let opposite_separator = match separator {
359 '/' => '\\',
360 _ => '/',
361 };
362 let filename =
363 no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string());
364
365 ::std::path::Path::new(&filename)
366 .components()
367 .filter(|component| matches!(*component, ::std::path::Component::Normal(..)))
368 .fold(::std::path::PathBuf::new(), |mut path, ref cur| {
369 path.push(cur.as_os_str());
370 path
371 })
372 }
373
374 pub fn enclosed_name(&self) -> Option<&path::Path> {
375 if self.file_name.contains('\0') {
376 return None;
377 }
378 let path = path::Path::new(&self.file_name);
379 let mut depth = 0usize;
380 for component in path.components() {
381 match component {
382 path::Component::Prefix(_) | path::Component::RootDir => return None,
383 path::Component::ParentDir => depth = depth.checked_sub(1)?,
384 path::Component::Normal(_) => depth += 1,
385 path::Component::CurDir => (),
386 }
387 }
388 Some(path)
389 }
390
391 pub fn unix_mode(&self) -> Option<u32> {
393 if self.external_attributes == 0 {
394 return None;
395 }
396
397 match self.system {
398 System::Unix => Some(self.external_attributes >> 16),
399 System::Dos => {
400 let mut mode = if 0x10 == (self.external_attributes & 0x10) {
402 ffi::S_IFDIR | 0o0775
403 } else {
404 ffi::S_IFREG | 0o0664
405 };
406 if 0x01 == (self.external_attributes & 0x01) {
407 mode &= 0o0555;
409 }
410 Some(mode)
411 }
412 _ => None,
413 }
414 }
415
416 pub fn zip64_extension(&self) -> bool {
417 self.uncompressed_size > 0xFFFFFFFF
418 || self.compressed_size > 0xFFFFFFFF
419 || self.header_start > 0xFFFFFFFF
420 }
421
422 pub fn version_needed(&self) -> u16 {
423 match (self.zip64_extension(), self.compression_method) {
425 #[cfg(feature = "bzip2")]
426 (_, crate::compression::CompressionMethod::Bzip2) => 46,
427 (true, _) => 45,
428 _ => 20,
429 }
430 }
431}
432
433#[derive(Copy, Clone, Debug)]
438pub enum AesVendorVersion {
439 Ae1,
440 Ae2,
441}
442
443#[derive(Copy, Clone, Debug)]
445pub enum AesMode {
446 Aes128,
447 Aes192,
448 Aes256,
449}
450
451#[cfg(feature = "aes-crypto")]
452impl AesMode {
453 pub fn salt_length(&self) -> usize {
454 self.key_length() / 2
455 }
456
457 pub fn key_length(&self) -> usize {
458 match self {
459 Self::Aes128 => 16,
460 Self::Aes192 => 24,
461 Self::Aes256 => 32,
462 }
463 }
464}
465
466#[cfg(test)]
467mod test {
468 #[test]
469 fn system() {
470 use super::System;
471 assert_eq!(System::Dos as u16, 0u16);
472 assert_eq!(System::Unix as u16, 3u16);
473 assert_eq!(System::from_u8(0), System::Dos);
474 assert_eq!(System::from_u8(3), System::Unix);
475 }
476
477 #[test]
478 fn sanitize() {
479 use super::*;
480 let file_name = "/path/../../../../etc/./passwd\0/etc/shadow".to_string();
481 let data = ZipFileData {
482 system: System::Dos,
483 version_made_by: 0,
484 encrypted: false,
485 using_data_descriptor: false,
486 compression_method: crate::compression::CompressionMethod::Stored,
487 compression_level: None,
488 last_modified_time: DateTime::default(),
489 crc32: 0,
490 compressed_size: 0,
491 uncompressed_size: 0,
492 file_name: file_name.clone(),
493 file_name_raw: file_name.into_bytes(),
494 extra_field: Vec::new(),
495 file_comment: String::new(),
496 header_start: 0,
497 data_start: AtomicU64::new(0),
498 central_header_start: 0,
499 external_attributes: 0,
500 large_file: false,
501 aes_mode: None,
502 };
503 assert_eq!(
504 data.file_name_sanitized(),
505 ::std::path::PathBuf::from("path/etc/passwd")
506 );
507 }
508
509 #[test]
510 #[allow(clippy::unusual_byte_groupings)]
511 fn datetime_default() {
512 use super::DateTime;
513 let dt = DateTime::default();
514 assert_eq!(dt.timepart(), 0);
515 assert_eq!(dt.datepart(), 0b0000000_0001_00001);
516 }
517
518 #[test]
519 #[allow(clippy::unusual_byte_groupings)]
520 fn datetime_max() {
521 use super::DateTime;
522 let dt = DateTime::from_date_and_time(2107, 12, 31, 23, 59, 60).unwrap();
523 assert_eq!(dt.timepart(), 0b10111_111011_11110);
524 assert_eq!(dt.datepart(), 0b1111111_1100_11111);
525 }
526
527 #[test]
528 fn datetime_bounds() {
529 use super::DateTime;
530
531 assert!(DateTime::from_date_and_time(2000, 1, 1, 23, 59, 60).is_ok());
532 assert!(DateTime::from_date_and_time(2000, 1, 1, 24, 0, 0).is_err());
533 assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 60, 0).is_err());
534 assert!(DateTime::from_date_and_time(2000, 1, 1, 0, 0, 61).is_err());
535
536 assert!(DateTime::from_date_and_time(2107, 12, 31, 0, 0, 0).is_ok());
537 assert!(DateTime::from_date_and_time(1980, 1, 1, 0, 0, 0).is_ok());
538 assert!(DateTime::from_date_and_time(1979, 1, 1, 0, 0, 0).is_err());
539 assert!(DateTime::from_date_and_time(1980, 0, 1, 0, 0, 0).is_err());
540 assert!(DateTime::from_date_and_time(1980, 1, 0, 0, 0, 0).is_err());
541 assert!(DateTime::from_date_and_time(2108, 12, 31, 0, 0, 0).is_err());
542 assert!(DateTime::from_date_and_time(2107, 13, 31, 0, 0, 0).is_err());
543 assert!(DateTime::from_date_and_time(2107, 12, 32, 0, 0, 0).is_err());
544 }
545
546 #[cfg(feature = "time")]
547 use time::{format_description::well_known::Rfc3339, OffsetDateTime};
548
549 #[cfg(feature = "time")]
550 #[test]
551 fn datetime_from_time_bounds() {
552 use super::DateTime;
553 use time::macros::datetime;
554
555 assert!(DateTime::from_time(datetime!(1979-12-31 23:59:59 UTC)).is_err());
557
558 assert!(DateTime::from_time(datetime!(1980-01-01 00:00:00 UTC)).is_ok());
560
561 assert!(DateTime::from_time(datetime!(2107-12-31 23:59:59 UTC)).is_ok());
563
564 assert!(DateTime::from_time(datetime!(2108-01-01 00:00:00 UTC)).is_err());
566 }
567
568 #[test]
569 fn time_conversion() {
570 use super::DateTime;
571 let dt = DateTime::from_msdos(0x4D71, 0x54CF);
572 assert_eq!(dt.year(), 2018);
573 assert_eq!(dt.month(), 11);
574 assert_eq!(dt.day(), 17);
575 assert_eq!(dt.hour(), 10);
576 assert_eq!(dt.minute(), 38);
577 assert_eq!(dt.second(), 30);
578
579 #[cfg(feature = "time")]
580 assert_eq!(
581 dt.to_time().unwrap().format(&Rfc3339).unwrap(),
582 "2018-11-17T10:38:30Z"
583 );
584 }
585
586 #[test]
587 fn time_out_of_bounds() {
588 use super::DateTime;
589 let dt = DateTime::from_msdos(0xFFFF, 0xFFFF);
590 assert_eq!(dt.year(), 2107);
591 assert_eq!(dt.month(), 15);
592 assert_eq!(dt.day(), 31);
593 assert_eq!(dt.hour(), 31);
594 assert_eq!(dt.minute(), 63);
595 assert_eq!(dt.second(), 62);
596
597 #[cfg(feature = "time")]
598 assert!(dt.to_time().is_err());
599
600 let dt = DateTime::from_msdos(0x0000, 0x0000);
601 assert_eq!(dt.year(), 1980);
602 assert_eq!(dt.month(), 0);
603 assert_eq!(dt.day(), 0);
604 assert_eq!(dt.hour(), 0);
605 assert_eq!(dt.minute(), 0);
606 assert_eq!(dt.second(), 0);
607
608 #[cfg(feature = "time")]
609 assert!(dt.to_time().is_err());
610 }
611
612 #[cfg(feature = "time")]
613 #[test]
614 fn time_at_january() {
615 use super::DateTime;
616
617 let clock = OffsetDateTime::from_unix_timestamp(1_577_836_800).unwrap();
619
620 assert!(DateTime::from_time(clock).is_ok());
621 }
622}