async_mtzip/zip/
extra_field.rs

1//! ZIP file extra field
2
3use std::{fs::Metadata, io::Write};
4
5use cfg_if::cfg_if;
6use tokio::io::AsyncWrite;
7
8/// This is a structure containing [`ExtraField`]s associated with a file or directory in a zip
9/// file, mostly used for filesystem properties, and this is the only functionality implemented
10/// here.
11///
12/// The [`new_from_fs`](Self::new_from_fs) method will use the metadata the filesystem provides to
13/// construct the collection.
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct ExtraFields {
16    pub(crate) values: Vec<ExtraField>,
17}
18
19impl ExtraFields {
20    /// Create a new set of [`ExtraField`]s. [`Self::new_from_fs`] should be preferred.
21    ///
22    /// # Safety
23    ///
24    /// All fields must have valid values depending on the field type.
25    pub unsafe fn new<I>(fields: I) -> Self
26        where
27            I: IntoIterator<Item=ExtraField>,
28    {
29        Self {
30            values: fields.into_iter().collect(),
31        }
32    }
33
34    /// This method will use the filesystem metadata to get the properties that can be stored in
35    /// ZIP [`ExtraFields`].
36    ///
37    /// The behavior is dependent on the target platform. Will return an empty set if the target os
38    /// is not Windows or Linux and not of UNIX family.
39    pub fn new_from_fs(metadata: &Metadata) -> Self {
40        cfg_if! {
41            if #[cfg(target_os = "windows")] {
42                Self::new_windows(metadata)
43            } else if #[cfg(target_os = "linux")] {
44                Self::new_linux(metadata)
45            } else if #[cfg(all(unix, not(target_os = "linux")))] {
46                Self::new_unix(metadata)
47            } else {
48                Self::default()
49            }
50        }
51    }
52
53    #[cfg(target_os = "linux")]
54    fn new_linux(metadata: &Metadata) -> Self {
55        use std::os::linux::fs::MetadataExt;
56
57        let mod_time = Some(metadata.st_mtime() as i32);
58        let ac_time = Some(metadata.st_atime() as i32);
59        let cr_time = Some(metadata.st_ctime() as i32);
60
61        let uid = metadata.st_uid();
62        let gid = metadata.st_gid();
63
64        Self {
65            values: vec![
66                ExtraField::UnixExtendedTimestamp {
67                    mod_time,
68                    ac_time,
69                    cr_time,
70                },
71                ExtraField::UnixAttrs { uid, gid },
72            ],
73        }
74    }
75
76    #[cfg(all(unix, not(target_os = "linux")))]
77    #[allow(dead_code)]
78    fn new_unix(metadata: &Metadata) -> Self {
79        use std::os::unix::fs::MetadataExt;
80
81        let mod_time = Some(metadata.mtime() as i32);
82        let ac_time = Some(metadata.atime() as i32);
83        let cr_time = Some(metadata.ctime() as i32);
84
85        let uid = metadata.uid();
86        let gid = metadata.gid();
87
88        Self {
89            values: vec![
90                ExtraField::UnixExtendedTimestamp {
91                    mod_time,
92                    ac_time,
93                    cr_time,
94                },
95                ExtraField::UnixAttrs { uid, gid },
96            ],
97        }
98    }
99
100    #[cfg(target_os = "windows")]
101    fn new_windows(metadata: &Metadata) -> Self {
102        use std::os::windows::fs::MetadataExt;
103
104        let mtime = metadata.last_write_time();
105        let atime = metadata.last_access_time();
106        let ctime = metadata.creation_time();
107
108        Self {
109            values: vec![ExtraField::Ntfs {
110                mtime,
111                atime,
112                ctime,
113            }],
114        }
115    }
116
117    pub(crate) fn data_length<const CENTRAL_HEADER: bool>(&self) -> u16 {
118        self.values
119            .iter()
120            .map(|f| 4 + f.field_size::<CENTRAL_HEADER>())
121            .sum()
122    }
123
124    pub(crate) fn write<W: Write, const CENTRAL_HEADER: bool>(
125        &self,
126        writer: &mut W,
127    ) -> std::io::Result<()> {
128        for field in &self.values {
129            field.write::<_, CENTRAL_HEADER>(writer)?;
130        }
131        Ok(())
132    }
133
134    pub(crate) async fn write_with_tokio<W: AsyncWrite + Unpin, const CENTRAL_HEADER: bool>(
135        &self,
136        writer: &mut W,
137    ) -> std::io::Result<()> {
138        for field in &self.values {
139            field.write_with_tokio::<_, CENTRAL_HEADER>(writer).await?;
140        }
141        Ok(())
142    }
143}
144
145/// Extra data that can be associated with a file or directory.
146///
147/// This library only implements the filesystem properties in NTFS and UNIX format.
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum ExtraField {
150    /// NTFS file properties.
151    Ntfs {
152        /// Last modification timestamp
153        mtime: u64,
154        /// Last access timestamp
155        atime: u64,
156        /// File/directory creation timestamp
157        ctime: u64,
158    },
159    /// Info-Zip extended unix timestamp. Each part is optional by definition, but will be
160    /// populated by [`ExtraFields::new_from_fs`].
161    UnixExtendedTimestamp {
162        /// Last modification timestamp
163        mod_time: Option<i32>,
164        /// Last access timestamp
165        ac_time: Option<i32>,
166        /// Creation timestamp
167        cr_time: Option<i32>,
168    },
169    /// UNIX file/directory attributes defined by Info-Zip.
170    UnixAttrs {
171        /// UID of the owner
172        uid: u32,
173        /// GID of the group
174        gid: u32,
175    },
176}
177
178const MOD_TIME_PRESENT: u8 = 1;
179const AC_TIME_PRESENT: u8 = 1 << 1;
180const CR_TIME_PRESENT: u8 = 1 << 2;
181
182impl ExtraField {
183    #[inline]
184    fn header_id(&self) -> u16 {
185        match self {
186            Self::Ntfs {
187                mtime: _,
188                atime: _,
189                ctime: _,
190            } => 0x000a,
191            Self::UnixExtendedTimestamp {
192                mod_time: _,
193                ac_time: _,
194                cr_time: _,
195            } => 0x5455,
196            Self::UnixAttrs { uid: _, gid: _ } => 0x7875,
197        }
198    }
199
200    #[inline]
201    const fn optional_field_size<T: Sized>(field: &Option<T>) -> u16 {
202        match field {
203            Some(_) => std::mem::size_of::<T>() as u16,
204            None => 0,
205        }
206    }
207
208    #[inline]
209    const fn field_size<const CENTRAL_HEADER: bool>(&self) -> u16 {
210        match self {
211            Self::Ntfs {
212                mtime: _,
213                atime: _,
214                ctime: _,
215            } => 32,
216            Self::UnixExtendedTimestamp {
217                mod_time,
218                ac_time,
219                cr_time,
220            } => {
221                1 + Self::optional_field_size(mod_time) + {
222                    if !CENTRAL_HEADER {
223                        Self::optional_field_size(ac_time) + Self::optional_field_size(cr_time)
224                    } else {
225                        0
226                    }
227                }
228            }
229            Self::UnixAttrs { uid: _, gid: _ } => 11,
230        }
231    }
232
233    #[inline]
234    const fn if_present(val: Option<i32>, if_present: u8) -> u8 {
235        match val {
236            Some(_) => if_present,
237            None => 0,
238        }
239    }
240
241    const NTFS_FIELD_LEN: usize = 32;
242    const UNIX_ATTRS_LEN: usize = 11;
243
244    pub(crate) fn write<W: Write, const CENTRAL_HEADER: bool>(
245        self,
246        writer: &mut W,
247    ) -> std::io::Result<()> {
248        // Header ID
249        writer.write_all(&self.header_id().to_le_bytes())?;
250        // Field data size
251        writer.write_all(&self.field_size::<CENTRAL_HEADER>().to_le_bytes())?;
252
253        match self {
254            Self::Ntfs {
255                mtime,
256                atime,
257                ctime,
258            } => {
259                // Writing to a temporary in-memory array
260                let mut field = [0; Self::NTFS_FIELD_LEN];
261                {
262                    let mut field_buf: &mut [u8] = &mut field;
263
264                    // Reserved field
265                    field_buf.write_all(&0_u32.to_le_bytes())?;
266
267                    // Tag1 number
268                    field_buf.write_all(&1_u16.to_le_bytes())?;
269                    // Tag1 size
270                    field_buf.write_all(&24_u16.to_le_bytes())?;
271
272                    // Mtime
273                    field_buf.write_all(&mtime.to_le_bytes())?;
274                    // Atime
275                    field_buf.write_all(&atime.to_le_bytes())?;
276                    // Ctime
277                    field_buf.write_all(&ctime.to_le_bytes())?;
278                }
279
280                writer.write_all(&field)?;
281            }
282            Self::UnixExtendedTimestamp {
283                mod_time,
284                ac_time,
285                cr_time,
286            } => {
287                let flags = Self::if_present(mod_time, MOD_TIME_PRESENT)
288                    | Self::if_present(ac_time, AC_TIME_PRESENT)
289                    | Self::if_present(cr_time, CR_TIME_PRESENT);
290                writer.write_all(&[flags])?;
291                if let Some(mod_time) = mod_time {
292                    writer.write_all(&mod_time.to_le_bytes())?;
293                }
294                if !CENTRAL_HEADER {
295                    if let Some(ac_time) = ac_time {
296                        writer.write_all(&ac_time.to_le_bytes())?;
297                    }
298                    if let Some(cr_time) = cr_time {
299                        writer.write_all(&cr_time.to_le_bytes())?;
300                    }
301                }
302            }
303            Self::UnixAttrs { uid, gid } => {
304                // Writing to a temporary in-memory array
305                let mut field = [0; Self::UNIX_ATTRS_LEN];
306                {
307                    let mut field_buf: &mut [u8] = &mut field;
308
309                    // Version of the field
310                    field_buf.write_all(&[1])?;
311                    // UID size
312                    field_buf.write_all(&[4])?;
313                    // UID
314                    field_buf.write_all(&uid.to_le_bytes())?;
315                    // GID size
316                    field_buf.write_all(&[4])?;
317                    // GID
318                    field_buf.write_all(&gid.to_le_bytes())?;
319                }
320
321                writer.write_all(&field)?;
322            }
323        }
324
325        Ok(())
326    }
327
328    pub(crate) async fn write_with_tokio<W: AsyncWrite + Unpin, const CENTRAL_HEADER: bool>(
329        self,
330        writer: &mut W,
331    ) -> std::io::Result<()> {
332        use tokio::io::AsyncWriteExt;
333        // Header ID
334        writer.write_all(&self.header_id().to_le_bytes()).await?;
335        // Field data size
336        writer.write_all(&self.field_size::<CENTRAL_HEADER>().to_le_bytes()).await?;
337
338        match self {
339            Self::Ntfs {
340                mtime,
341                atime,
342                ctime,
343            } => {
344                // Writing to a temporary in-memory array
345                let mut field = [0; Self::NTFS_FIELD_LEN];
346                {
347                    let mut field_buf: &mut [u8] = &mut field;
348
349                    // Reserved field
350                    field_buf.write_all(&0_u32.to_le_bytes())?;
351
352                    // Tag1 number
353                    field_buf.write_all(&1_u16.to_le_bytes())?;
354                    // Tag1 size
355                    field_buf.write_all(&24_u16.to_le_bytes())?;
356
357                    // Mtime
358                    field_buf.write_all(&mtime.to_le_bytes())?;
359                    // Atime
360                    field_buf.write_all(&atime.to_le_bytes())?;
361                    // Ctime
362                    field_buf.write_all(&ctime.to_le_bytes())?;
363                }
364
365                writer.write_all(&field).await?;
366            }
367            Self::UnixExtendedTimestamp {
368                mod_time,
369                ac_time,
370                cr_time,
371            } => {
372                let flags = Self::if_present(mod_time, MOD_TIME_PRESENT)
373                    | Self::if_present(ac_time, AC_TIME_PRESENT)
374                    | Self::if_present(cr_time, CR_TIME_PRESENT);
375                writer.write_all(&[flags]).await?;
376                if let Some(mod_time) = mod_time {
377                    writer.write_all(&mod_time.to_le_bytes()).await?;
378                }
379                if !CENTRAL_HEADER {
380                    if let Some(ac_time) = ac_time {
381                        writer.write_all(&ac_time.to_le_bytes()).await?;
382                    }
383                    if let Some(cr_time) = cr_time {
384                        writer.write_all(&cr_time.to_le_bytes()).await?;
385                    }
386                }
387            }
388            Self::UnixAttrs { uid, gid } => {
389                // Writing to a temporary in-memory array
390                let mut field = [0; Self::UNIX_ATTRS_LEN];
391                {
392                    let mut field_buf: &mut [u8] = &mut field;
393
394                    // Version of the field
395                    field_buf.write_all(&[1])?;
396                    // UID size
397                    field_buf.write_all(&[4])?;
398                    // UID
399                    field_buf.write_all(&uid.to_le_bytes())?;
400                    // GID size
401                    field_buf.write_all(&[4])?;
402                    // GID
403                    field_buf.write_all(&gid.to_le_bytes())?;
404                }
405
406                writer.write_all(&field).await?;
407            }
408        }
409
410        Ok(())
411    }
412}