1use crate::compression::CompressionMethod;
4use crate::cp437::FromCp437;
5use crate::datetime::DateTime;
6use crate::extra_fields::{ExtraField, ExtraFields};
7use crate::format::blocks::{CentralDirectoryEndInfo, DataAndPosition, ZipCentralEntryBlock};
8use crate::format::flags::{ZipFileFlags, ZipFlags};
9use crate::format::system::System;
10use crate::result::{ZipError, ZipResult, invalid};
11use crate::types::ZipFileData;
12use indexmap::IndexMap;
13use std::io::{self, Read, Seek};
14use std::sync::OnceLock;
15
16mod config;
17pub use config::{ArchiveOffset, Config};
18
19pub(crate) mod extract;
20pub use extract::{ExtractOptions, RootDirFilter, SymlinkExtractAction, root_dir_common_filter};
21
22pub(crate) mod stream;
24pub use stream::{
25 read_zipfile_from_stream, read_zipfile_from_stream_with_compressed_size,
26 read_zipfile_from_stream_with_options,
27};
28
29pub(crate) mod magic_finder;
30pub(crate) mod readers;
31
32pub(crate) mod zipfile;
33pub use zipfile::{ZipFile, ZipFileEntry, ZipFileEntryWithDataDescriptor, ZipFileSeek};
34
35pub(crate) mod zip_archive;
36pub use zip_archive::{ZipArchive, ZipArchiveMetadata};
37
38#[cfg(feature = "aes-crypto")]
39pub use crate::aes::AesInfo;
40
41#[derive(Debug)]
42pub(crate) struct CentralDirectoryInfo {
43 pub(crate) archive_offset: u64,
44 pub(crate) directory_start: u64,
45 pub(crate) number_of_files: usize,
46 pub(crate) disk_number: u32,
47 pub(crate) disk_with_central_directory: u32,
48}
49
50impl<'a> TryFrom<&'a CentralDirectoryEndInfo> for CentralDirectoryInfo {
51 type Error = ZipError;
52
53 fn try_from(value: &'a CentralDirectoryEndInfo) -> Result<Self, Self::Error> {
54 let (relative_cd_offset, number_of_files, disk_number, disk_with_central_directory) =
55 match &value.eocd64 {
56 Some(DataAndPosition { data: eocd64, .. }) => {
57 if eocd64.number_of_files_on_this_disk > eocd64.number_of_files {
58 return Err(invalid!(
59 "ZIP64 footer indicates more files on this disk than in the whole archive"
60 ));
61 }
62 (
63 eocd64.central_directory_offset,
64 eocd64.number_of_files as usize,
65 eocd64.disk_number,
66 eocd64.disk_with_central_directory,
67 )
68 }
69 _ => (
70 u64::from(value.eocd.data.central_directory_offset),
71 value.eocd.data.number_of_files_on_this_disk as usize,
72 u32::from(value.eocd.data.disk_number),
73 u32::from(value.eocd.data.disk_with_central_directory),
74 ),
75 };
76
77 let directory_start = relative_cd_offset
78 .checked_add(value.archive_offset)
79 .ok_or(invalid!("Invalid central directory size or offset"))?;
80
81 Ok(Self {
82 archive_offset: value.archive_offset,
83 directory_start,
84 number_of_files,
85 disk_number,
86 disk_with_central_directory,
87 })
88 }
89}
90
91impl<R: Read + Seek> ZipArchive<R> {
92 pub(crate) fn merge_contents<W: std::io::Write + Seek>(
93 &mut self,
94 mut w: W,
95 ) -> ZipResult<IndexMap<Box<[u8]>, ZipFileData>> {
96 if self.shared.files.is_empty() {
97 return Ok(IndexMap::new());
98 }
99 let mut new_files = self.shared.files.clone();
100 let first_new_file_header_start = w.stream_position()?;
108
109 new_files.values_mut().try_for_each(|f| {
111 f.header_start = f
113 .header_start
114 .checked_add(first_new_file_header_start)
115 .ok_or(invalid!(
116 "new header start from merge would have been too large"
117 ))?;
118 f.central_header_start = 0;
121 if let Some(old_data_start) = f.data_start.take() {
124 let new_data_start = old_data_start
125 .checked_add(first_new_file_header_start)
126 .ok_or(invalid!(
127 "new data start from merge would have been too large"
128 ))?;
129 f.data_start.get_or_init(|| new_data_start);
130 }
131 Ok::<_, ZipError>(())
132 })?;
133
134 self.reader.rewind()?;
146 let length_to_read = self.shared.dir_start;
148 let mut limited_raw = (&mut self.reader as &mut dyn Read).take(length_to_read);
152 io::copy(&mut limited_raw, &mut w)?;
154
155 Ok(new_files)
157 }
158}
159
160#[inline]
161fn read_variable_length_byte_field<R: Read>(reader: &mut R, len: usize) -> ZipResult<Vec<u8>> {
162 let mut data = vec![0; len];
163 if let Err(e) = reader.read_exact(&mut data) {
164 if e.kind() == io::ErrorKind::UnexpectedEof {
165 return Err(invalid!(
166 "Variable-length field extends beyond file boundary"
167 ));
168 }
169 return Err(e.into());
170 }
171 Ok(data)
172}
173
174fn central_header_to_zip_file_inner<R: Read>(
176 reader: &mut R,
177 archive_offset: u64,
178 central_header_start: u64,
179 block: ZipCentralEntryBlock,
180) -> ZipResult<(ZipFileData, Vec<u8>)> {
181 let ZipCentralEntryBlock {
182 version_made_by,
184 flags,
186 compression_method,
187 last_mod_time,
188 last_mod_date,
189 crc32,
190 compressed_size,
191 uncompressed_size,
192 file_name_length,
193 extra_field_length,
194 file_comment_length,
195 external_file_attributes,
198 offset,
199 ..
200 } = block;
201
202 let is_utf8 = ZipFlags::matching(flags, ZipFlags::LanguageEncoding);
203
204 let mut file_name_raw = read_variable_length_byte_field(reader, file_name_length as usize)?;
205 let extra_fields_raw = read_variable_length_byte_field(reader, extra_field_length as usize)?;
206 let file_comment_raw = read_variable_length_byte_field(reader, file_comment_length as usize)?;
207 let file_comment: Box<str> = if is_utf8 {
208 String::from_utf8_lossy(&file_comment_raw).into()
209 } else {
210 file_comment_raw.from_cp437()?.into()
211 };
212
213 let (version_made_by, system) = System::extract_bytes(version_made_by);
214 let extra_fields = ExtraFields::parse(&extra_fields_raw, &block, false)?;
215 let mut result = ZipFileData {
217 system,
218 version_made_by,
219 compression_method: CompressionMethod::parse_from_u16(compression_method),
220 last_modified_time: DateTime::try_from_msdos(last_mod_date, last_mod_time).ok(),
221 crc32,
222 compressed_size: compressed_size.into(),
223 uncompressed_size: uncompressed_size.into(),
224 flags: ZipFileFlags(flags),
225 file_comment,
226 header_start: offset.into(),
227 central_header_start,
228 data_start: OnceLock::new(),
229 external_attributes: external_file_attributes,
230 large_file: false,
231 extra_fields,
232 };
233 result.apply_extra_fields(&mut file_name_raw)?;
234
235 result.header_start = result
237 .header_start
238 .checked_add(archive_offset)
239 .ok_or(invalid!("Archive header is too large"))?;
240
241 Ok((result, file_name_raw))
242}
243
244pub trait HasZipMetadata {
246 fn get_metadata(&self) -> &ZipFileData;
248}
249
250#[derive(Default)]
252#[non_exhaustive]
253pub struct ZipReadOptions<'a> {
254 pub(crate) password: Option<&'a [u8]>,
256 pub(crate) ignore_encryption_flag: bool,
258 pub(crate) ignore_crc: bool,
260 force_compressed_size: Option<u64>,
262 force_uncompressed_size: Option<u64>,
264 force_crc: Option<u32>,
266 pub(crate) size_unknown: bool,
269}
270
271impl<'a> ZipReadOptions<'a> {
272 #[must_use]
274 pub fn new() -> Self {
275 Self::default()
276 }
277
278 #[must_use]
280 pub fn password(mut self, password: Option<&'a [u8]>) -> Self {
281 self.password = password;
282 self
283 }
284
285 #[must_use]
287 pub fn ignore_encryption_flag(mut self, ignore: bool) -> Self {
288 self.ignore_encryption_flag = ignore;
289 self
290 }
291
292 #[must_use]
294 pub fn ignore_crc32(mut self, should_ignore: bool) -> Self {
295 self.ignore_crc = should_ignore;
296 self
297 }
298
299 #[must_use]
301 pub fn override_compressed_size(mut self, comp_size: u64) -> Self {
302 self.force_compressed_size = Some(comp_size);
303 self
304 }
305
306 #[must_use]
308 pub fn override_uncompressed_size(mut self, uncomp_size: u64) -> Self {
309 self.force_uncompressed_size = Some(uncomp_size);
310 self
311 }
312
313 #[must_use]
315 pub fn override_crc(mut self, crc: u32) -> Self {
316 self.force_crc = Some(crc);
317 self
318 }
319}