1pub mod builder;
5
6use std::ops::Deref;
7
8use futures_lite::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, SeekFrom};
9
10use crate::base::read::{get_combined_sizes, get_zip64_extra_field};
11use crate::entry::builder::ZipEntryBuilder;
12use crate::error::{Result, ZipError};
13use crate::spec::{
14 attribute::AttributeCompatibility,
15 consts::{LFH_LENGTH, LFH_SIGNATURE, NON_ZIP64_MAX_SIZE, SIGNATURE_LENGTH},
16 header::{ExtraField, LocalFileHeader},
17 parse::parse_extra_fields,
18 Compression,
19};
20use crate::{string::ZipString, ZipDateTime};
21
22#[derive(Clone, Debug)]
28pub struct ZipEntry {
29 pub(crate) filename: ZipString,
30 pub(crate) compression: Compression,
31 #[cfg(any(
32 feature = "deflate-write",
33 feature = "bzip2-write",
34 feature = "zstd-write",
35 feature = "lzma-write",
36 feature = "xz-write"
37 ))]
38 pub(crate) compression_level: async_compression::Level,
39 pub(crate) crc32: u32,
40 pub(crate) uncompressed_size: u64,
41 pub(crate) compressed_size: u64,
42 pub(crate) attribute_compatibility: AttributeCompatibility,
43 pub(crate) last_modification_date: ZipDateTime,
44 pub(crate) internal_file_attribute: u16,
45 pub(crate) external_file_attribute: u32,
46 pub(crate) extra_fields: Vec<ExtraField>,
47 pub(crate) comment: ZipString,
48 pub(crate) data_descriptor: bool,
49 pub(crate) file_offset: u64,
50}
51
52impl From<ZipEntryBuilder> for ZipEntry {
53 fn from(builder: ZipEntryBuilder) -> Self {
54 builder.0
55 }
56}
57
58impl ZipEntry {
59 pub(crate) fn new(filename: ZipString, compression: Compression) -> Self {
60 ZipEntry {
61 filename,
62 compression,
63 #[cfg(any(
64 feature = "deflate-write",
65 feature = "bzip2-write",
66 feature = "zstd-write",
67 feature = "lzma-write",
68 feature = "xz-write"
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 pub fn filename(&self) -> &ZipString {
92 &self.filename
93 }
94
95 pub fn compression(&self) -> Compression {
97 self.compression
98 }
99
100 pub fn crc32(&self) -> u32 {
102 self.crc32
103 }
104
105 pub fn uncompressed_size(&self) -> u64 {
107 self.uncompressed_size
108 }
109
110 pub fn compressed_size(&self) -> u64 {
112 self.compressed_size
113 }
114
115 pub fn attribute_compatibility(&self) -> AttributeCompatibility {
117 self.attribute_compatibility
118 }
119
120 pub fn last_modification_date(&self) -> &ZipDateTime {
122 &self.last_modification_date
123 }
124
125 pub fn internal_file_attribute(&self) -> u16 {
127 self.internal_file_attribute
128 }
129
130 pub fn external_file_attribute(&self) -> u32 {
132 self.external_file_attribute
133 }
134
135 pub fn extra_fields(&self) -> &[ExtraField] {
137 &self.extra_fields
138 }
139
140 pub fn comment(&self) -> &ZipString {
142 &self.comment
143 }
144
145 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 pub fn dir(&self) -> Result<bool> {
159 Ok(self.filename.as_str()?.ends_with('/'))
160 }
161
162 pub fn data_descriptor(&self) -> bool {
164 self.data_descriptor
165 }
166
167 pub fn file_offset(&self) -> u64 {
169 self.file_offset
170 }
171}
172
173#[derive(Clone)]
178pub struct StoredZipEntry {
179 pub(crate) entry: ZipEntry,
180 pub(crate) file_offset: u64,
182 pub(crate) header_size: u64,
183 pub(crate) data_end_boundary: u64,
184}
185
186impl StoredZipEntry {
187 pub fn header_offset(&self) -> u64 {
189 self.file_offset
190 }
191
192 pub fn header_size(&self) -> u64 {
197 self.header_size
198 }
199
200 pub(crate) async fn seek_to_data_offset<R: AsyncRead + AsyncSeek + Unpin>(&self, mut reader: &mut R) -> Result<()> {
202 reader.seek(SeekFrom::Start(self.file_offset)).await?;
204
205 let signature = {
207 let mut buffer = [0; 4];
208 reader.read_exact(&mut buffer).await?;
209 u32::from_le_bytes(buffer)
210 };
211
212 match signature {
213 LFH_SIGNATURE => (),
214 actual => return Err(ZipError::UnexpectedHeaderError(actual, LFH_SIGNATURE)),
215 };
216
217 let header = LocalFileHeader::from_reader(&mut reader).await?;
219 let data_start = self
220 .file_offset
221 .checked_add((SIGNATURE_LENGTH + LFH_LENGTH) as u64)
222 .and_then(|offset| offset.checked_add(header.file_name_length as u64))
223 .and_then(|offset| offset.checked_add(header.extra_field_length as u64))
224 .ok_or(ZipError::InvalidEntryDataRange)?;
225 let data_end = data_start.checked_add(self.entry.compressed_size()).ok_or(ZipError::InvalidEntryDataRange)?;
226 if data_end > self.data_end_boundary {
227 return Err(ZipError::EntryDataRangeOverlap {
228 start: data_start,
229 end: data_end,
230 boundary: self.data_end_boundary,
231 });
232 }
233
234 if header.flags.data_descriptor != self.entry.data_descriptor {
235 return Err(ZipError::LocalFileHeaderDataDescriptorMismatch);
236 }
237
238 let local_filename = crate::base::read::io::read_bytes(&mut reader, header.file_name_length.into()).await?;
239 if local_filename.as_slice() != self.entry.filename().as_bytes() {
240 return Err(ZipError::LocalFileHeaderNameMismatch);
241 }
242
243 if !header.flags.data_descriptor
244 && ((header.compressed_size != NON_ZIP64_MAX_SIZE
245 && header.compressed_size as u64 != self.entry.compressed_size)
246 || (header.uncompressed_size != NON_ZIP64_MAX_SIZE
247 && header.uncompressed_size as u64 != self.entry.uncompressed_size))
248 {
249 return Err(ZipError::LocalFileHeaderSizeMismatch);
250 }
251
252 let mut extra_field = vec![0; usize::from(header.extra_field_length)];
253 reader.read_exact(&mut extra_field).await?;
254 let extra_fields =
255 parse_extra_fields(extra_field, header.uncompressed_size, header.compressed_size, None, None)?;
256 let zip64_extra_field = get_zip64_extra_field(&extra_fields);
257
258 if !header.flags.data_descriptor
259 && zip64_extra_field.is_none()
260 && extra_fields.is_empty()
261 && (header.compressed_size == NON_ZIP64_MAX_SIZE || header.uncompressed_size == NON_ZIP64_MAX_SIZE)
262 {
263 return Err(ZipError::LocalFileHeaderSizeMismatch);
264 }
265
266 let (local_uncompressed_size, local_compressed_size) =
267 get_combined_sizes(header.uncompressed_size, header.compressed_size, &zip64_extra_field)?;
268
269 if !header.flags.data_descriptor
270 && (local_compressed_size != self.entry.compressed_size
271 || local_uncompressed_size != self.entry.uncompressed_size)
272 {
273 return Err(ZipError::LocalFileHeaderSizeMismatch);
274 }
275
276 Ok(())
277 }
278}
279
280impl Deref for StoredZipEntry {
281 type Target = ZipEntry;
282
283 fn deref(&self) -> &Self::Target {
284 &self.entry
285 }
286}