Skip to main content

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