zip/extra_fields/
extended_timestamp.rs

1use crate::result::invalid;
2use crate::result::{ZipError, ZipResult};
3use crate::unstable::LittleEndianReadExt;
4use std::io::Read;
5
6/// extended timestamp, as described in <https://libzip.org/specifications/extrafld.txt>
7
8#[derive(Debug, Clone)]
9pub struct ExtendedTimestamp {
10    mod_time: Option<u32>,
11    ac_time: Option<u32>,
12    cr_time: Option<u32>,
13}
14
15impl ExtendedTimestamp {
16    /// creates an extended timestamp struct by reading the required bytes from the reader.
17    ///
18    /// This method assumes that the length has already been read, therefore
19    /// it must be passed as an argument
20    pub fn try_from_reader<R>(reader: &mut R, len: u16) -> ZipResult<Self>
21    where
22        R: Read,
23    {
24        if len == 0 {
25            return Err(invalid!("Extended timestamp field is empty"));
26        }
27        let mut flags = [0u8];
28        let mut bytes_to_read = len as usize;
29        reader.read_exact(&mut flags)?;
30        bytes_to_read -= flags.len();
31        let flags = flags[0];
32
33        // the `flags` field refers to the local headers and might not correspond
34        // to the len field. If the length field is 1+4, we assume that only
35        // the modification time has been set
36
37        // > Those times that are present will appear in the order indicated, but
38        // > any combination of times may be omitted.  (Creation time may be
39        // > present without access time, for example.)  TSize should equal
40        // > (1 + 4*(number of set bits in Flags)), as the block is currently
41        // > defined.
42        if len != 5 && len as u32 != 1 + 4 * flags.count_ones() {
43            //panic!("found len {len} and flags {flags:08b}");
44            return Err(ZipError::UnsupportedArchive(
45                "flags and len don't match in extended timestamp field",
46            ));
47        }
48
49        // allow unsupported/undocumented flags
50
51        let mod_time = if (flags & 0b00000001u8 == 0b00000001u8) || len == 5 {
52            bytes_to_read -= size_of::<u32>();
53            Some(reader.read_u32_le()?)
54        } else {
55            None
56        };
57
58        let ac_time = if flags & 0b00000010u8 == 0b00000010u8 && len > 5 {
59            bytes_to_read -= size_of::<u32>();
60            Some(reader.read_u32_le()?)
61        } else {
62            None
63        };
64
65        let cr_time = if flags & 0b00000100u8 == 0b00000100u8 && len > 5 {
66            bytes_to_read -= size_of::<u32>();
67            Some(reader.read_u32_le()?)
68        } else {
69            None
70        };
71
72        if bytes_to_read > 0 {
73            // ignore undocumented bytes
74            reader.read_exact(&mut vec![0; bytes_to_read])?
75        }
76
77        Ok(Self {
78            mod_time,
79            ac_time,
80            cr_time,
81        })
82    }
83
84    /// returns the last modification timestamp, if defined, as UNIX epoch seconds
85    pub fn mod_time(&self) -> Option<u32> {
86        self.mod_time
87    }
88
89    /// returns the last access timestamp, if defined, as UNIX epoch seconds
90    pub fn ac_time(&self) -> Option<u32> {
91        self.ac_time
92    }
93
94    /// returns the creation timestamp, if defined, as UNIX epoch seconds
95    pub fn cr_time(&self) -> Option<u32> {
96        self.cr_time
97    }
98}
99
100#[test]
101/// Ensure we don't panic or read garbage data if the field body is empty
102pub fn test_bad_extended_timestamp() -> ZipResult<()> {
103    use crate::ZipArchive;
104    use std::io::Cursor;
105
106    assert!(ZipArchive::new(Cursor::new(include_bytes!(
107        "../../tests/data/extended_timestamp_bad.zip"
108    )))
109    .is_err());
110    Ok(())
111}