async_zip/base/read/
cd.rs1use futures_lite::io::{AsyncRead, AsyncReadExt};
2
3use crate::base::read::counting::Counting;
4use crate::base::read::io::CombinedCentralDirectoryRecord;
5use crate::base::read::{detect_comment, detect_filename, get_combined_sizes, get_zip64_extra_field, io};
6use crate::error::{Result, ZipError};
7use crate::spec::consts::{CDH_SIGNATURE, EOCDR_SIGNATURE, NON_ZIP64_MAX_SIZE, ZIP64_EOCDR_SIGNATURE};
8use crate::spec::header::{
9 CentralDirectoryRecord, EndOfCentralDirectoryHeader, Zip64EndOfCentralDirectoryLocator,
10 Zip64EndOfCentralDirectoryRecord,
11};
12use crate::spec::parse::parse_extra_fields;
13use crate::ZipString;
14
15pub enum Entry {
17 CentralDirectoryEntry(CentralDirectoryEntry),
18 EndOfCentralDirectoryRecord {
19 record: CombinedCentralDirectoryRecord,
21 comment: ZipString,
23 extensible: bool,
25 },
26}
27
28pub struct CentralDirectoryEntry {
30 pub(crate) compressed_size: u64,
32 pub(crate) uncompressed_size: u64,
34 pub(crate) lh_offset: u64,
36 pub(crate) header: CentralDirectoryRecord,
38 pub(crate) filename: ZipString,
40}
41
42impl CentralDirectoryEntry {
43 pub fn filename(&self) -> &ZipString {
50 &self.filename
51 }
52
53 pub fn dir(&self) -> Result<bool> {
55 Ok(self.filename.as_str()?.ends_with('/'))
56 }
57
58 pub fn unix_permissions(&self) -> Option<u32> {
60 Some((self.header.exter_attr) >> 16)
61 }
62
63 pub fn crc32(&self) -> u32 {
65 self.header.crc
66 }
67
68 pub fn file_offset(&self) -> u64 {
70 self.lh_offset
71 }
72
73 pub fn compressed_size(&self) -> u64 {
75 self.compressed_size
76 }
77
78 pub fn uncompressed_size(&self) -> u64 {
80 self.uncompressed_size
81 }
82}
83
84#[derive(Clone)]
85pub struct CentralDirectoryReader<R> {
86 reader: R,
87 initial: bool,
88 offset: u64,
89}
90
91impl<R> CentralDirectoryReader<Counting<R>>
92where
93 R: AsyncRead + Unpin,
94{
95 pub fn new(reader: R, offset: u64) -> Self {
97 Self { reader: Counting::new(reader), offset, initial: true }
98 }
99
100 pub async fn next(&mut self) -> Result<Entry> {
110 if self.initial {
114 self.initial = false;
115 } else {
116 let signature = {
117 let mut buffer = [0; 4];
118 self.reader.read_exact(&mut buffer).await?;
119 u32::from_le_bytes(buffer)
120 };
121 let offset = self.offset + self.reader.bytes_read();
122 match signature {
123 CDH_SIGNATURE => (),
124 EOCDR_SIGNATURE => {
125 let eocdr = EndOfCentralDirectoryHeader::from_reader(&mut self.reader).await?;
127
128 let observed_directory_size = offset.saturating_sub(self.offset);
129 if eocdr.size_cent_dir as u64 != observed_directory_size {
130 return Err(ZipError::InvalidCentralDirectorySize {
131 expected: eocdr.size_cent_dir as u64,
132 actual: observed_directory_size,
133 });
134 }
135
136 let comment =
138 io::read_string(&mut self.reader, eocdr.file_comm_length.into(), crate::StringEncoding::Utf8)
139 .await?;
140
141 if eocdr.central_directory_offset() != self.offset {
143 return Err(ZipError::InvalidEndOfCentralDirectoryOffset(
144 eocdr.central_directory_offset(),
145 offset,
146 ));
147 }
148
149 let record = CombinedCentralDirectoryRecord::try_from(&eocdr)?;
150 io::validate_comment(&comment)?;
151 io::validate_trailing_contents(&mut self.reader).await?;
152 return Ok(Entry::EndOfCentralDirectoryRecord { record, comment, extensible: false });
153 }
154 ZIP64_EOCDR_SIGNATURE => {
155 let zip64_eocdr = Zip64EndOfCentralDirectoryRecord::from_reader(&mut self.reader).await?;
157
158 let extensible = if zip64_eocdr.size_of_zip64_end_of_cd_record > 44 {
160 let extensible_data_size = zip64_eocdr.size_of_zip64_end_of_cd_record - 44;
161 io::skip_bytes(&mut self.reader, extensible_data_size).await?;
162 true
163 } else {
164 false
165 };
166
167 let Some(zip64_eocdl) =
169 Zip64EndOfCentralDirectoryLocator::try_from_reader(&mut self.reader).await?
170 else {
171 return Err(ZipError::MissingZip64EndOfCentralDirectoryLocator);
172 };
173
174 if zip64_eocdl.relative_offset != offset {
176 return Err(ZipError::InvalidZip64EndOfCentralDirectoryLocatorOffset(
177 zip64_eocdl.relative_offset,
178 offset,
179 ));
180 }
181
182 let signature = {
184 let mut buffer = [0; 4];
185 self.reader.read_exact(&mut buffer).await?;
186 u32::from_le_bytes(buffer)
187 };
188 if signature != EOCDR_SIGNATURE {
189 return Err(ZipError::UnexpectedHeaderError(signature, EOCDR_SIGNATURE));
190 }
191
192 let eocdr = EndOfCentralDirectoryHeader::from_reader(&mut self.reader).await?;
194
195 let comment =
197 io::read_string(&mut self.reader, eocdr.file_comm_length.into(), crate::StringEncoding::Utf8)
198 .await?;
199
200 let zip64_directory_size = zip64_eocdr.directory_size;
202 let combined = CombinedCentralDirectoryRecord::combine(eocdr, zip64_eocdr)?;
203
204 let observed_directory_size = offset.saturating_sub(self.offset);
205 if zip64_directory_size != observed_directory_size {
206 return Err(ZipError::InvalidCentralDirectorySize {
207 expected: zip64_directory_size,
208 actual: observed_directory_size,
209 });
210 }
211
212 if combined.central_directory_offset() != self.offset {
214 return Err(ZipError::InvalidEndOfCentralDirectoryOffset(
215 combined.central_directory_offset(),
216 offset,
217 ));
218 }
219
220 io::validate_comment(&comment)?;
221 io::validate_trailing_contents(&mut self.reader).await?;
222 return Ok(Entry::EndOfCentralDirectoryRecord { record: combined, comment, extensible });
223 }
224 actual => return Err(ZipError::UnexpectedHeaderError(actual, CDH_SIGNATURE)),
225 }
226 }
227
228 let header = CentralDirectoryRecord::from_reader(&mut self.reader).await?;
230
231 let filename_basic = io::read_bytes(&mut self.reader, header.file_name_length.into()).await?;
234 let extra_field = io::read_bytes(&mut self.reader, header.extra_field_length.into()).await?;
235 let extra_fields = parse_extra_fields(
236 extra_field,
237 header.uncompressed_size,
238 header.compressed_size,
239 Some(header.lh_offset),
240 Some(header.disk_start),
241 )?;
242 let zip64_extra_field = get_zip64_extra_field(&extra_fields);
243
244 let comment_basic = io::read_bytes(&mut self.reader, header.file_comment_length.into()).await?;
246 detect_comment(comment_basic, header.flags.filename_unicode, &extra_fields)?;
247
248 let (uncompressed_size, compressed_size) =
250 get_combined_sizes(header.uncompressed_size, header.compressed_size, &zip64_extra_field)?;
251 let lh_offset = if let Some(lh_offset) = zip64_extra_field
252 .and_then(|zip64| zip64.relative_header_offset)
253 .filter(|_| header.lh_offset == NON_ZIP64_MAX_SIZE)
254 {
255 lh_offset
256 } else {
257 header.lh_offset as u64
258 };
259
260 let filename = detect_filename(filename_basic, header.flags.filename_unicode, extra_fields.as_ref())?;
262
263 Ok(Entry::CentralDirectoryEntry(CentralDirectoryEntry {
264 header,
265 compressed_size,
266 uncompressed_size,
267 lh_offset,
268 filename,
269 }))
270 }
271}