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",
33 feature = "bzip2",
34 feature = "zstd",
35 feature = "lzma",
36 feature = "xz",
37 feature = "deflate64"
38 ))]
39 pub(crate) compression_level: async_compression::Level,
40 pub(crate) crc32: u32,
41 pub(crate) uncompressed_size: u64,
42 pub(crate) compressed_size: u64,
43 pub(crate) attribute_compatibility: AttributeCompatibility,
44 pub(crate) last_modification_date: ZipDateTime,
45 pub(crate) internal_file_attribute: u16,
46 pub(crate) external_file_attribute: u32,
47 pub(crate) extra_fields: Vec<ExtraField>,
48 pub(crate) comment: ZipString,
49 pub(crate) data_descriptor: bool,
50 pub(crate) file_offset: u64,
51}
52
53impl From<ZipEntryBuilder> for ZipEntry {
54 fn from(builder: ZipEntryBuilder) -> Self {
55 builder.0
56 }
57}
58
59impl ZipEntry {
60 pub(crate) fn new(filename: ZipString, compression: Compression) -> Self {
61 ZipEntry {
62 filename,
63 compression,
64 #[cfg(any(
65 feature = "deflate",
66 feature = "bzip2",
67 feature = "zstd",
68 feature = "lzma",
69 feature = "xz",
70 feature = "deflate64"
71 ))]
72 compression_level: async_compression::Level::Default,
73 crc32: 0,
74 uncompressed_size: 0,
75 compressed_size: 0,
76 attribute_compatibility: AttributeCompatibility::Unix,
77 last_modification_date: ZipDateTime::default_for_write(),
78 internal_file_attribute: 0,
79 external_file_attribute: 0,
80 extra_fields: Vec::new(),
81 comment: String::new().into(),
82 data_descriptor: false,
83 file_offset: 0,
84 }
85 }
86
87 pub fn filename(&self) -> &ZipString {
94 &self.filename
95 }
96
97 pub fn compression(&self) -> Compression {
99 self.compression
100 }
101
102 pub fn crc32(&self) -> u32 {
104 self.crc32
105 }
106
107 pub fn uncompressed_size(&self) -> u64 {
109 self.uncompressed_size
110 }
111
112 pub fn compressed_size(&self) -> u64 {
114 self.compressed_size
115 }
116
117 pub fn attribute_compatibility(&self) -> AttributeCompatibility {
119 self.attribute_compatibility
120 }
121
122 pub fn last_modification_date(&self) -> &ZipDateTime {
124 &self.last_modification_date
125 }
126
127 pub fn internal_file_attribute(&self) -> u16 {
129 self.internal_file_attribute
130 }
131
132 pub fn external_file_attribute(&self) -> u32 {
134 self.external_file_attribute
135 }
136
137 pub fn extra_fields(&self) -> &[ExtraField] {
139 &self.extra_fields
140 }
141
142 pub fn comment(&self) -> &ZipString {
144 &self.comment
145 }
146
147 pub fn unix_permissions(&self) -> Option<u16> {
152 if !matches!(self.attribute_compatibility, AttributeCompatibility::Unix) {
153 return None;
154 }
155
156 Some(((self.external_file_attribute) >> 16) as u16)
157 }
158
159 pub fn dir(&self) -> Result<bool> {
161 Ok(self.filename.as_str()?.ends_with('/'))
162 }
163
164 pub fn data_descriptor(&self) -> bool {
166 self.data_descriptor
167 }
168
169 pub fn file_offset(&self) -> u64 {
171 self.file_offset
172 }
173}
174
175#[derive(Clone)]
180pub struct StoredZipEntry {
181 pub(crate) entry: ZipEntry,
182 pub(crate) file_offset: u64,
184 pub(crate) header_size: u64,
185 pub(crate) data_end_boundary: u64,
186}
187
188impl StoredZipEntry {
189 pub fn header_offset(&self) -> u64 {
191 self.file_offset
192 }
193
194 pub fn header_size(&self) -> u64 {
199 self.header_size
200 }
201
202 pub(crate) async fn seek_to_data_offset<R: AsyncRead + AsyncSeek + Unpin>(&self, mut reader: &mut R) -> Result<()> {
204 reader.seek(SeekFrom::Start(self.file_offset)).await?;
206
207 let signature = {
209 let mut buffer = [0; 4];
210 reader.read_exact(&mut buffer).await?;
211 u32::from_le_bytes(buffer)
212 };
213
214 match signature {
215 LFH_SIGNATURE => (),
216 actual => return Err(ZipError::UnexpectedHeaderError(actual, LFH_SIGNATURE)),
217 };
218
219 let header = LocalFileHeader::from_reader(&mut reader).await?;
221 let data_start = self
222 .file_offset
223 .checked_add((SIGNATURE_LENGTH + LFH_LENGTH) as u64)
224 .and_then(|offset| offset.checked_add(header.file_name_length as u64))
225 .and_then(|offset| offset.checked_add(header.extra_field_length as u64))
226 .ok_or(ZipError::InvalidEntryDataRange)?;
227 let data_end = data_start.checked_add(self.entry.compressed_size()).ok_or(ZipError::InvalidEntryDataRange)?;
228 if data_end > self.data_end_boundary {
229 return Err(ZipError::EntryDataRangeOverlap {
230 start: data_start,
231 end: data_end,
232 boundary: self.data_end_boundary,
233 });
234 }
235
236 if header.flags.data_descriptor != self.entry.data_descriptor {
237 return Err(ZipError::LocalFileHeaderDataDescriptorMismatch);
238 }
239
240 let local_filename = crate::base::read::io::read_bytes(&mut reader, header.file_name_length.into()).await?;
241 if local_filename.as_slice() != self.entry.filename().as_bytes() {
242 return Err(ZipError::LocalFileHeaderNameMismatch);
243 }
244
245 if !header.flags.data_descriptor
246 && ((header.compressed_size != NON_ZIP64_MAX_SIZE
247 && header.compressed_size as u64 != self.entry.compressed_size)
248 || (header.uncompressed_size != NON_ZIP64_MAX_SIZE
249 && header.uncompressed_size as u64 != self.entry.uncompressed_size))
250 {
251 return Err(ZipError::LocalFileHeaderSizeMismatch);
252 }
253
254 let mut extra_field = vec![0; usize::from(header.extra_field_length)];
255 reader.read_exact(&mut extra_field).await?;
256 let extra_fields =
257 parse_extra_fields(extra_field, header.uncompressed_size, header.compressed_size, None, None)?;
258 let zip64_extra_field = get_zip64_extra_field(&extra_fields);
259
260 if !header.flags.data_descriptor
261 && zip64_extra_field.is_none()
262 && extra_fields.is_empty()
263 && (header.compressed_size == NON_ZIP64_MAX_SIZE || header.uncompressed_size == NON_ZIP64_MAX_SIZE)
264 {
265 return Err(ZipError::LocalFileHeaderSizeMismatch);
266 }
267
268 let (local_uncompressed_size, local_compressed_size) =
269 get_combined_sizes(header.uncompressed_size, header.compressed_size, &zip64_extra_field)?;
270
271 if !header.flags.data_descriptor
272 && (local_compressed_size != self.entry.compressed_size
273 || local_uncompressed_size != self.entry.uncompressed_size)
274 {
275 return Err(ZipError::LocalFileHeaderSizeMismatch);
276 }
277
278 Ok(())
279 }
280}
281
282impl Deref for StoredZipEntry {
283 type Target = ZipEntry;
284
285 fn deref(&self) -> &Self::Target {
286 &self.entry
287 }
288}