async_zip/entry/
mod.rs

1// Copyright (c) 2022 Harry [Majored] [hello@majored.pw]
2// MIT License (https://github.com/Majored/rs-async-zip/blob/main/LICENSE)
3
4use std::ops::Deref;
5
6use futures_lite::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, SeekFrom};
7
8use crate::error::{Result, ZipError};
9use crate::spec::{
10    attribute::AttributeCompatibility,
11    consts::LFH_SIGNATURE,
12    header::{ExtraField, LocalFileHeader},
13    Compression,
14};
15use crate::{string::ZipString, ZipDateTime};
16
17/// An immutable store of data about a ZIP entry.
18#[derive(Clone, Debug)]
19pub struct ZipEntry {
20    pub(crate) filename: ZipString,
21    pub(crate) compression: Compression,
22    #[cfg(any(
23        feature = "deflate",
24        feature = "bzip2",
25        feature = "zstd",
26        feature = "lzma",
27        feature = "xz",
28        feature = "deflate64"
29    ))]
30    pub(crate) compression_level: async_compression::Level,
31    pub(crate) crc32: u32,
32    pub(crate) uncompressed_size: u64,
33    pub(crate) compressed_size: u64,
34    pub(crate) attribute_compatibility: AttributeCompatibility,
35    pub(crate) last_modification_date: ZipDateTime,
36    pub(crate) internal_file_attribute: u16,
37    pub(crate) external_file_attribute: u32,
38    pub(crate) extra_fields: Vec<ExtraField>,
39    pub(crate) comment: ZipString,
40    pub(crate) data_descriptor: bool,
41    pub(crate) file_offset: u64,
42}
43
44impl ZipEntry {
45    /// Returns the entry's filename.
46    ///
47    /// ## Note
48    /// This will return the raw filename stored during ZIP creation. If calling this method on entries retrieved from
49    /// untrusted ZIP files, the filename should be sanitised before being used as a path to prevent [directory
50    /// traversal attacks](https://en.wikipedia.org/wiki/Directory_traversal_attack).
51    pub fn filename(&self) -> &ZipString {
52        &self.filename
53    }
54
55    /// Returns the entry's compression method.
56    pub fn compression(&self) -> Compression {
57        self.compression
58    }
59
60    /// Returns the entry's CRC32 value.
61    pub fn crc32(&self) -> u32 {
62        self.crc32
63    }
64
65    /// Returns the entry's uncompressed size.
66    pub fn uncompressed_size(&self) -> u64 {
67        self.uncompressed_size
68    }
69
70    /// Returns the entry's compressed size.
71    pub fn compressed_size(&self) -> u64 {
72        self.compressed_size
73    }
74
75    /// Returns the entry's attribute's host compatibility.
76    pub fn attribute_compatibility(&self) -> AttributeCompatibility {
77        self.attribute_compatibility
78    }
79
80    /// Returns the entry's last modification time & date.
81    pub fn last_modification_date(&self) -> &ZipDateTime {
82        &self.last_modification_date
83    }
84
85    /// Returns the entry's internal file attribute.
86    pub fn internal_file_attribute(&self) -> u16 {
87        self.internal_file_attribute
88    }
89
90    /// Returns the entry's external file attribute
91    pub fn external_file_attribute(&self) -> u32 {
92        self.external_file_attribute
93    }
94
95    /// Returns the entry's extra field data.
96    pub fn extra_fields(&self) -> &[ExtraField] {
97        &self.extra_fields
98    }
99
100    /// Returns the entry's file comment.
101    pub fn comment(&self) -> &ZipString {
102        &self.comment
103    }
104
105    /// Returns the entry's integer-based UNIX permissions.
106    ///
107    /// # Note
108    /// This will return None if the attribute host compatibility is not listed as Unix.
109    pub fn unix_permissions(&self) -> Option<u16> {
110        if !matches!(self.attribute_compatibility, AttributeCompatibility::Unix) {
111            return None;
112        }
113
114        Some(((self.external_file_attribute) >> 16) as u16)
115    }
116
117    /// Returns whether or not the entry represents a directory.
118    pub fn dir(&self) -> Result<bool> {
119        Ok(self.filename.as_str()?.ends_with('/'))
120    }
121
122    /// Returns whether or not the entry has a data descriptor.
123    pub fn data_descriptor(&self) -> bool {
124        self.data_descriptor
125    }
126
127    /// Returns the file offset in bytes of the local file header for this entry.
128    pub fn file_offset(&self) -> u64 {
129        self.file_offset
130    }
131}
132
133/// An immutable store of data about how a ZIP entry is stored within a specific archive.
134///
135/// Besides storing archive independent information like the size and timestamp it can also be used to query
136/// information about how the entry is stored in an archive.
137#[derive(Clone)]
138pub struct StoredZipEntry {
139    pub(crate) entry: ZipEntry,
140    // pub(crate) general_purpose_flag: GeneralPurposeFlag,
141    pub(crate) file_offset: u64,
142    pub(crate) header_size: u64,
143}
144
145impl StoredZipEntry {
146    /// Returns the offset in bytes to where the header of the entry starts.
147    pub fn header_offset(&self) -> u64 {
148        self.file_offset
149    }
150
151    /// Returns the combined size in bytes of the header, the filename, and any extra fields.
152    ///
153    /// Note: This uses the extra field length stored in the central directory, which may differ from that stored in
154    /// the local file header. See specification: <https://github.com/Majored/rs-async-zip/blob/main/SPECIFICATION.md#732>
155    pub fn header_size(&self) -> u64 {
156        self.header_size
157    }
158
159    /// Seek to the offset in bytes where the data of the entry starts.
160    pub(crate) async fn seek_to_data_offset<R: AsyncRead + AsyncSeek + Unpin>(&self, mut reader: &mut R) -> Result<()> {
161        // Seek to the header
162        reader.seek(SeekFrom::Start(self.file_offset)).await?;
163
164        // Check the signature
165        let signature = {
166            let mut buffer = [0; 4];
167            reader.read_exact(&mut buffer).await?;
168            u32::from_le_bytes(buffer)
169        };
170
171        match signature {
172            LFH_SIGNATURE => (),
173            actual => return Err(ZipError::UnexpectedHeaderError(actual, LFH_SIGNATURE)),
174        };
175
176        // Skip the local file header and trailing data
177        let header = LocalFileHeader::from_reader(&mut reader).await?;
178        let trailing_size = (header.file_name_length as i64) + (header.extra_field_length as i64);
179        reader.seek(SeekFrom::Current(trailing_size)).await?;
180
181        Ok(())
182    }
183}
184
185impl Deref for StoredZipEntry {
186    type Target = ZipEntry;
187
188    fn deref(&self) -> &Self::Target {
189        &self.entry
190    }
191}