1#[cfg(feature = "aes-crypto")]
4use crate::extra_fields::AexEncryption;
5use crate::extra_fields::CustomExtraField;
6use crate::extra_fields::DataStreamAlignment;
7use crate::extra_fields::ExtendedTimestamp;
8use crate::extra_fields::Ntfs;
9use crate::extra_fields::UnicodeExtraField;
10use crate::extra_fields::UsedExtraField;
11use crate::extra_fields::Zip64ExtendedInformation;
12use crate::extra_fields::zip64_extended_information::Zip64Sizes;
13use crate::format::blocks::ZipEntryBlock;
14use crate::format::flags::ZipFlags;
15use crate::result::ZipResult;
16use crate::result::invalid;
17use crate::types::ZipFileData;
18use crate::unstable::LittleEndianReadExt;
19use std::io::ErrorKind;
20use std::io::{Cursor, Read, Write};
21
22#[derive(Debug, Clone, PartialEq)]
24#[non_exhaustive]
25pub enum ExtraField {
26 Ntfs(Ntfs),
28 ExtendedTimestamp(ExtendedTimestamp),
30 #[cfg(feature = "aes-crypto")]
32 AeXEncryption(AexEncryption),
33 Zip64ExtendedInformation(Zip64ExtendedInformation),
35 UnicodeComment(UnicodeExtraField),
37 UnicodePath(UnicodeExtraField),
39 DataStreamAlignment(DataStreamAlignment),
41 Custom(CustomExtraField),
43}
44
45#[derive(Debug, Clone, Default)]
47pub struct ExtraFields {
48 pub(crate) inner: Vec<ExtraField>,
49}
50
51impl ExtraFields {
52 pub(crate) fn parse<B: ZipEntryBlock>(
53 buff: &[u8],
54 block: &B,
55 is_local_header: bool,
56 ) -> ZipResult<Self> {
57 let mut reader = Cursor::new(buff);
58 let mut extra_fields = Vec::new();
59 while (reader.position() as usize) < buff.len() {
60 let parsed_extra_field = ExtraField::parse(&mut reader, block, is_local_header)?;
61 let Some(parsed_extra_field) = parsed_extra_field else {
62 break;
63 };
64 extra_fields.push(parsed_extra_field);
65 }
66 Ok(Self {
67 inner: extra_fields,
68 })
69 }
70
71 pub(crate) fn local_extra_fields_mut(&mut self) -> impl Iterator<Item = &mut ExtraField> {
72 self.inner.iter_mut()
73 }
74
75 pub(crate) fn local_extra_fields(&self) -> impl Iterator<Item = &ExtraField> {
76 self.inner.iter().filter(|ef| match ef {
77 ExtraField::Custom(cef) => !cef.central_only,
78 _ => true,
79 })
80 }
81
82 pub(crate) fn central_extra_fields(&self) -> impl Iterator<Item = &ExtraField> {
83 self.inner
85 .iter()
86 .filter(|ef| !matches!(ef, ExtraField::DataStreamAlignment(_)))
87 }
88}
89
90impl ExtraField {
91 pub(crate) fn parse<R: Read, B: ZipEntryBlock>(
92 reader: &mut R,
93 file: &B,
94 is_local_header: bool,
95 ) -> ZipResult<Option<Self>> {
96 let extra_field_header_id = match reader.read_u16_le() {
97 Ok(value) => value,
98 Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None),
99 Err(e) => return Err(e.into()),
100 };
101 let decoded_extra_field = UsedExtraField::try_from(extra_field_header_id);
102 let len = match decoded_extra_field {
103 Ok(known_field) => match reader.read_u16_le() {
104 Ok(len) => len,
105 Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
106 return Err(invalid!("Extra field {} header truncated", known_field));
107 }
108 Err(e) => return Err(e.into()),
109 },
110 Err(()) => {
111 match reader.read_u16_le() {
112 Ok(len) => len,
113 Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None), Err(_e) => {
115 let _ = std::io::copy(reader, &mut std::io::sink());
117 return Ok(None);
118 }
119 }
120 }
121 };
122 let parsed_extra_field = match decoded_extra_field {
123 Ok(UsedExtraField::Zip64ExtendedInfo) => {
125 ExtraField::Zip64ExtendedInformation(Zip64ExtendedInformation::parse(
126 reader,
127 len,
128 file.get_uncompressed_size(),
129 file.get_compressed_size(),
130 file.get_header_start(),
131 )?)
132 }
133 Ok(UsedExtraField::Ntfs) => {
134 ExtraField::Ntfs(Ntfs::try_from_reader(reader, len)?)
136 }
137 #[cfg(feature = "aes-crypto")]
138 Ok(UsedExtraField::AeXEncryption) => {
139 let (new_aes_enc, inner_compression) = AexEncryption::parse(reader, len)?;
141 ExtraField::AeXEncryption(AexEncryption::new(
142 new_aes_enc.1,
143 new_aes_enc.0,
144 inner_compression,
145 ))
146 }
147 Ok(UsedExtraField::ExtendedTimestamp) => ExtraField::ExtendedTimestamp(
148 ExtendedTimestamp::try_from_reader(reader, len, is_local_header)?,
149 ),
150 Ok(UsedExtraField::UnicodeComment) => {
151 let unicode = UnicodeExtraField::try_from_reader(reader, len)?;
154 ExtraField::UnicodeComment(unicode)
155 }
156 Ok(UsedExtraField::UnicodePath) => {
157 let unicode = UnicodeExtraField::try_from_reader(reader, len)?;
160 ExtraField::UnicodePath(unicode)
161 }
162 _ => {
163 let mut buf = vec![0u8; len as usize];
164 if let Err(e) = reader.read_exact(&mut buf) {
165 if e.kind() == ErrorKind::UnexpectedEof {
166 return Err(invalid!("Extra field content truncated"));
167 }
168 return Err(e.into());
169 }
170 ExtraField::Custom(CustomExtraField::new(
171 false,
172 extra_field_header_id,
173 buf.into_boxed_slice(),
174 ))
175 }
177 };
178 Ok(Some(parsed_extra_field))
179 }
180
181 pub(crate) fn size(&self, is_local_header: bool) -> usize {
182 match self {
183 ExtraField::Zip64ExtendedInformation(zip64_extra) => {
185 zip64_extra.full_size(is_local_header)
186 }
187 ExtraField::Ntfs(_ntfs) => {
188 0
190 }
191 #[cfg(feature = "aes-crypto")]
192 ExtraField::AeXEncryption(aes) => aes.full_size(),
193 ExtraField::ExtendedTimestamp(_extended_timestamp) => {
194 0
196 }
197 ExtraField::UnicodeComment(unicode_comment) => unicode_comment.full_size(),
198 ExtraField::UnicodePath(unicode_path) => unicode_path.full_size(),
199 ExtraField::Custom(custom) => custom.full_size(is_local_header),
200 ExtraField::DataStreamAlignment(data_stream_alignment) => {
201 data_stream_alignment.full_size(is_local_header)
202 }
203 }
204 }
205
206 pub(crate) fn write<W: Write>(&self, writer: &mut W, is_local_header: bool) -> ZipResult<()> {
207 match self {
208 ExtraField::Zip64ExtendedInformation(zip64_extra) => {
210 zip64_extra.write(writer, is_local_header)?;
211 }
212 #[cfg(feature = "aes-crypto")]
213 ExtraField::AeXEncryption(aex) => {
214 aex.write(writer)?;
215 }
216 ExtraField::Custom(custom) => {
217 custom.write(writer, is_local_header)?;
218 }
219 ExtraField::UnicodeComment(unicode_comment) => {
220 let magic = UsedExtraField::UnicodeComment.as_u16();
221 writer.write_all(&magic.to_le_bytes())?;
222 unicode_comment.write(writer)?;
223 }
224 ExtraField::UnicodePath(unicode_path) => {
225 let magic = UsedExtraField::UnicodePath.as_u16();
226 writer.write_all(&magic.to_le_bytes())?;
227 unicode_path.write(writer)?;
228 }
229 ExtraField::DataStreamAlignment(data_stream_alignment) => {
230 data_stream_alignment.write(writer, is_local_header)?
231 }
232 _ => {
233 }
235 }
236 Ok(())
237 }
238}
239
240impl ZipFileData {
241 pub(crate) fn apply_extra_fields(&mut self, file_name_raw: &mut Vec<u8>) -> ZipResult<()> {
242 for one_extra_field in &self.extra_fields.inner {
243 match one_extra_field {
244 ExtraField::Zip64ExtendedInformation(zip64_block) => {
246 self.large_file = true;
247 if let Some(Zip64Sizes {
248 uncompressed_size,
249 compressed_size,
250 }) = zip64_block.sizes
251 {
252 self.uncompressed_size = uncompressed_size;
253 self.compressed_size = compressed_size;
254 }
255 if let Some(head_start) = zip64_block.header_start {
256 self.header_start = head_start;
257 }
258 }
259 #[cfg(feature = "aes-crypto")]
260 ExtraField::AeXEncryption(AexEncryption {
261 compression_method, ..
262 }) => {
263 self.compression_method = *compression_method;
264 }
265 ExtraField::UnicodeComment(unicode) => {
266 if unicode.is_crc32_valid(self.file_comment.as_bytes())
272 && let Ok(comment) = String::from_utf8(unicode.content.to_vec())
273 {
274 self.file_comment = comment.into_boxed_str();
275 }
276 }
277 #[allow(clippy::collapsible_match)]
278 ExtraField::UnicodePath(unicode) => {
279 if unicode.is_crc32_valid(file_name_raw)
284 && std::str::from_utf8(&unicode.content).is_ok()
285 {
286 *file_name_raw = unicode.content.to_vec();
287 self.flags |= ZipFlags::LanguageEncoding.as_u16();
288 }
289 }
290 _ => {
291 }
293 }
294 }
295 Ok(())
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use crate::extra_fields::ExtraField;
302 use crate::extra_fields::ExtraFields;
303 use crate::format::blocks::ZipEntryBlock;
304
305 struct PlaceHolderBlock;
306
307 impl ZipEntryBlock for PlaceHolderBlock {
308 fn get_uncompressed_size(&self) -> u32 {
309 0
310 }
311 fn get_compressed_size(&self) -> u32 {
312 0
313 }
314 fn get_header_start(&self) -> Option<u32> {
315 None
316 }
317 }
318
319 #[test]
320 #[cfg(feature = "aes-crypto")]
321 fn aex_extra_field_with_feature() {
322 let buff = [1, 0x99, 7, 0, 1, 0, b'A', b'E', 3, 0, 0];
323
324 let extra_fields = ExtraFields::parse(&buff[..], &PlaceHolderBlock, true).unwrap();
325 assert!(matches!(
326 extra_fields.inner[0],
327 ExtraField::AeXEncryption(..)
328 ));
329 }
330
331 #[test]
332 #[cfg(not(feature = "aes-crypto"))]
333 fn aex_extra_field_without_feature() {
334 use crate::extra_fields::CustomExtraField;
335 use crate::extra_fields::UsedExtraField;
336
337 let buff = [1, 0x99, 7, 0, 1, 0, b'A', b'E', 3, 0, 0];
338
339 let extra_fields = ExtraFields::parse(&buff[..], &PlaceHolderBlock, true).unwrap();
340 let extra = CustomExtraField::new(
341 false,
342 UsedExtraField::AeXEncryption.as_u16(),
343 [1, 0, b'A', b'E', 3, 0, 0].to_vec().into_boxed_slice(),
344 );
345 assert_eq!(extra_fields.inner[0], ExtraField::Custom(extra));
346 }
347}