1use crate::format::magic::Magic;
4use crate::result::{ZipError, ZipResult, invalid};
5
6use core::any::type_name;
7use core::mem;
8use core::slice;
9use std::io::{self, Read, Write};
10
11pub(crate) unsafe trait Pod: Copy + 'static {
19 #[inline]
20 fn zeroed() -> Self {
21 unsafe { mem::zeroed() }
22 }
23
24 #[inline]
25 fn as_bytes(&self) -> &[u8] {
26 unsafe {
27 slice::from_raw_parts(
28 std::ptr::from_ref::<Self>(self).cast::<u8>(),
29 mem::size_of::<Self>(),
30 )
31 }
32 }
33
34 #[inline]
35 fn as_bytes_mut(&mut self) -> &mut [u8] {
36 unsafe {
37 slice::from_raw_parts_mut(
38 std::ptr::from_mut::<Self>(self).cast::<u8>(),
39 mem::size_of::<Self>(),
40 )
41 }
42 }
43}
44
45#[derive(Copy, Clone)]
46#[repr(C, packed)]
47struct BlockWithMagic<T: FixedSizeBlock> {
48 magic: Magic,
49 inner: T,
50}
51unsafe impl<T: FixedSizeBlock> Pod for BlockWithMagic<T> {}
52
53impl<T: FixedSizeBlock> BlockWithMagic<T> {
54 fn to_le(mut self) -> Self {
55 self.magic = self.magic.to_le();
56 self.inner = self.inner.to_le();
57 self
58 }
59}
60
61pub(crate) trait FixedSizeBlock: Pod {
62 const MAGIC: Magic;
63
64 const WRONG_MAGIC_ERROR: ZipError;
65
66 #[allow(clippy::wrong_self_convention)]
67 fn from_le(self) -> Self;
68
69 fn parse<R: Read + ?Sized>(reader: &mut R) -> ZipResult<Self> {
70 let mut block_with_magic = BlockWithMagic::zeroed();
71 if let Err(e) = reader.read_exact(block_with_magic.as_bytes_mut()) {
72 if e.kind() == io::ErrorKind::UnexpectedEof {
73 return Err(invalid!("Unexpected end of {}", type_name::<Self>()));
74 }
75 return Err(e.into());
76 }
77 let BlockWithMagic {
78 magic,
79 inner: block,
80 } = block_with_magic;
81 let magic = Magic::from_le(magic);
82 if magic != Self::MAGIC {
83 return Err(Self::WRONG_MAGIC_ERROR);
84 }
85 let block = Self::from_le(block);
86 Ok(block)
87 }
88
89 fn to_le(self) -> Self;
90
91 fn write<T: Write + ?Sized>(self, writer: &mut T) -> ZipResult<()> {
92 let block = BlockWithMagic {
93 magic: Self::MAGIC,
94 inner: self,
95 };
96 let block = block.to_le();
97 writer.write_all(block.as_bytes())?;
98 Ok(())
99 }
100}
101
102macro_rules! from_le {
104 ($obj:ident, $field:ident, $type:ty) => {
105 $obj.$field = <$type>::from_le($obj.$field);
106 };
107 ($obj:ident, [($field:ident, $type:ty) $(,)?]) => {
108 from_le![$obj, $field, $type];
109 };
110 ($obj:ident, [($field:ident, $type:ty), $($rest:tt),+ $(,)?]) => {
111 from_le![$obj, $field, $type];
112 from_le!($obj, [$($rest),+]);
113 };
114}
115
116macro_rules! to_le {
118 ($obj:ident, $field:ident, $type:ty) => {
119 $obj.$field = <$type>::to_le($obj.$field);
120 };
121 ($obj:ident, [($field:ident, $type:ty) $(,)?]) => {
122 to_le![$obj, $field, $type];
123 };
124 ($obj:ident, [($field:ident, $type:ty), $($rest:tt),+ $(,)?]) => {
125 to_le![$obj, $field, $type];
126 to_le!($obj, [$($rest),+]);
127 };
128}
129
130macro_rules! to_and_from_le {
134 ($($args:tt),+ $(,)?) => {
135 #[inline(always)]
136 fn from_le(mut self) -> Self {
137 from_le![self, [$($args),+]];
138 self
139 }
140 #[inline(always)]
141 fn to_le(mut self) -> Self {
142 to_le![self, [$($args),+]];
143 self
144 }
145 };
146}
147
148#[derive(Copy, Clone, Debug)]
149#[repr(packed, C)]
150#[allow(missing_docs)]
151pub struct ZipCentralEntryBlock {
152 pub version_made_by: u16,
153 pub version_to_extract: u16,
154 pub flags: u16,
155 pub compression_method: u16,
156 pub last_mod_time: u16,
157 pub last_mod_date: u16,
158 pub crc32: u32,
159 pub compressed_size: u32,
160 pub uncompressed_size: u32,
161 pub file_name_length: u16,
162 pub extra_field_length: u16,
163 pub file_comment_length: u16,
164 pub disk_number: u16,
165 pub internal_file_attributes: u16,
166 pub external_file_attributes: u32,
167 pub offset: u32,
168}
169
170unsafe impl Pod for ZipCentralEntryBlock {}
171
172impl ZipEntryBlock for ZipCentralEntryBlock {
173 fn get_uncompressed_size(&self) -> u32 {
174 self.uncompressed_size
175 }
176 fn get_compressed_size(&self) -> u32 {
177 self.compressed_size
178 }
179 fn get_header_start(&self) -> Option<u32> {
180 Some(self.offset)
181 }
182}
183
184impl FixedSizeBlock for ZipCentralEntryBlock {
185 const MAGIC: Magic = Magic::CENTRAL_DIRECTORY_HEADER_SIGNATURE;
186
187 const WRONG_MAGIC_ERROR: ZipError = invalid!("Invalid Central Directory header");
188
189 to_and_from_le![
190 (version_made_by, u16),
191 (version_to_extract, u16),
192 (flags, u16),
193 (compression_method, u16),
194 (last_mod_time, u16),
195 (last_mod_date, u16),
196 (crc32, u32),
197 (compressed_size, u32),
198 (uncompressed_size, u32),
199 (file_name_length, u16),
200 (extra_field_length, u16),
201 (file_comment_length, u16),
202 (disk_number, u16),
203 (internal_file_attributes, u16),
204 (external_file_attributes, u32),
205 (offset, u32),
206 ];
207}
208
209pub(crate) trait ZipEntryBlock {
210 fn get_uncompressed_size(&self) -> u32;
211 fn get_compressed_size(&self) -> u32;
212 fn get_header_start(&self) -> Option<u32>;
213}
214
215#[derive(Copy, Clone, Debug)]
216#[repr(packed, C)]
217#[allow(missing_docs)]
218pub struct ZipLocalEntryBlock {
219 pub version_made_by: u16,
220 pub flags: u16,
221 pub compression_method: u16,
222 pub last_mod_time: u16,
223 pub last_mod_date: u16,
224 pub crc32: u32,
225 pub compressed_size: u32,
226 pub uncompressed_size: u32,
227 pub file_name_length: u16,
228 pub extra_field_length: u16,
229}
230
231unsafe impl Pod for ZipLocalEntryBlock {}
232
233impl ZipEntryBlock for ZipLocalEntryBlock {
234 fn get_uncompressed_size(&self) -> u32 {
235 self.uncompressed_size
236 }
237 fn get_compressed_size(&self) -> u32 {
238 self.compressed_size
239 }
240 fn get_header_start(&self) -> Option<u32> {
241 None
242 }
243}
244
245impl FixedSizeBlock for ZipLocalEntryBlock {
246 const MAGIC: Magic = Magic::LOCAL_FILE_HEADER_SIGNATURE;
247
248 const WRONG_MAGIC_ERROR: ZipError = invalid!("Invalid local file header");
249
250 to_and_from_le![
251 (version_made_by, u16),
252 (flags, u16),
253 (compression_method, u16),
254 (last_mod_time, u16),
255 (last_mod_date, u16),
256 (crc32, u32),
257 (compressed_size, u32),
258 (uncompressed_size, u32),
259 (file_name_length, u16),
260 (extra_field_length, u16),
261 ];
262}
263
264#[derive(Copy, Clone, Debug)]
265#[repr(packed, C)]
266#[allow(missing_docs)]
267pub struct Zip32CDEBlock {
268 pub disk_number: u16,
269 pub disk_with_central_directory: u16,
270 pub number_of_files_on_this_disk: u16,
271 pub number_of_files: u16,
272 pub central_directory_size: u32,
273 pub central_directory_offset: u32,
274 pub zip_file_comment_length: u16,
275}
276
277unsafe impl Pod for Zip32CDEBlock {}
278
279impl FixedSizeBlock for Zip32CDEBlock {
280 const MAGIC: Magic = Magic::CENTRAL_DIRECTORY_END_SIGNATURE;
281
282 const WRONG_MAGIC_ERROR: ZipError = invalid!("Invalid Central Directory End header");
283
284 to_and_from_le![
285 (disk_number, u16),
286 (disk_with_central_directory, u16),
287 (number_of_files_on_this_disk, u16),
288 (number_of_files, u16),
289 (central_directory_size, u32),
290 (central_directory_offset, u32),
291 (zip_file_comment_length, u16)
292 ];
293}
294
295#[derive(Debug)]
296#[allow(missing_docs)]
297pub struct Zip32CentralDirectoryEnd {
298 pub disk_number: u16,
299 pub disk_with_central_directory: u16,
300 pub number_of_files_on_this_disk: u16,
301 pub number_of_files: u16,
302 pub central_directory_size: u32,
303 pub central_directory_offset: u32,
304 pub zip_file_comment: Box<[u8]>,
305}
306
307impl Zip32CentralDirectoryEnd {
308 fn into_block_and_comment(self) -> (Zip32CDEBlock, Box<[u8]>) {
309 let Self {
310 disk_number,
311 disk_with_central_directory,
312 number_of_files_on_this_disk,
313 number_of_files,
314 central_directory_size,
315 central_directory_offset,
316 zip_file_comment,
317 } = self;
318 let block = Zip32CDEBlock {
319 disk_number,
320 disk_with_central_directory,
321 number_of_files_on_this_disk,
322 number_of_files,
323 central_directory_size,
324 central_directory_offset,
325 zip_file_comment_length: zip_file_comment.len() as u16,
326 };
327
328 (block, zip_file_comment)
329 }
330
331 pub fn parse<T: Read + ?Sized>(reader: &mut T) -> ZipResult<Zip32CentralDirectoryEnd> {
333 let Zip32CDEBlock {
334 disk_number,
335 disk_with_central_directory,
336 number_of_files_on_this_disk,
337 number_of_files,
338 central_directory_size,
339 central_directory_offset,
340 zip_file_comment_length,
341 ..
342 } = Zip32CDEBlock::parse(reader)?;
343
344 let mut zip_file_comment = vec![0u8; zip_file_comment_length as usize].into_boxed_slice();
345 if let Err(e) = reader.read_exact(&mut zip_file_comment) {
346 if e.kind() == io::ErrorKind::UnexpectedEof {
347 return Err(invalid!("EOCD comment exceeds file boundary"));
348 }
349
350 return Err(e.into());
351 }
352
353 Ok(Zip32CentralDirectoryEnd {
354 disk_number,
355 disk_with_central_directory,
356 number_of_files_on_this_disk,
357 number_of_files,
358 central_directory_size,
359 central_directory_offset,
360 zip_file_comment,
361 })
362 }
363
364 pub fn write<T: Write>(self, writer: &mut T) -> ZipResult<()> {
366 let (block, comment) = self.into_block_and_comment();
367
368 if comment.len() > u16::MAX as usize {
369 return Err(invalid!("EOCD comment length exceeds u16::MAX"));
370 }
371
372 block.write(writer)?;
373 writer.write_all(&comment)?;
374 Ok(())
375 }
376
377 #[must_use]
379 pub fn may_be_zip64(&self) -> bool {
380 self.number_of_files == u16::MAX
381 || self.central_directory_size == u32::MAX
382 || self.central_directory_offset == u32::MAX
383 }
384}
385
386#[derive(Copy, Clone)]
387#[repr(packed, C)]
388#[allow(missing_docs)]
389pub struct Zip64CDELocatorBlock {
390 pub disk_with_central_directory: u32,
391 pub end_of_central_directory_offset: u64,
392 pub number_of_disks: u32,
393}
394
395unsafe impl Pod for Zip64CDELocatorBlock {}
396
397impl FixedSizeBlock for Zip64CDELocatorBlock {
398 const MAGIC: Magic = Magic::ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE;
399
400 const WRONG_MAGIC_ERROR: ZipError =
401 invalid!("Invalid ZIP64 End of Central Directory Locator header");
402
403 to_and_from_le![
404 (disk_with_central_directory, u32),
405 (end_of_central_directory_offset, u64),
406 (number_of_disks, u32),
407 ];
408}
409
410#[allow(missing_docs)]
411pub struct Zip64CentralDirectoryEndLocator {
412 pub disk_with_central_directory: u32,
413 pub end_of_central_directory_offset: u64,
414 pub number_of_disks: u32,
415}
416
417impl Zip64CentralDirectoryEndLocator {
418 pub fn parse<T: Read + ?Sized>(reader: &mut T) -> ZipResult<Zip64CentralDirectoryEndLocator> {
420 let Zip64CDELocatorBlock {
421 disk_with_central_directory,
422 end_of_central_directory_offset,
423 number_of_disks,
424 ..
425 } = Zip64CDELocatorBlock::parse(reader)?;
426
427 Ok(Zip64CentralDirectoryEndLocator {
428 disk_with_central_directory,
429 end_of_central_directory_offset,
430 number_of_disks,
431 })
432 }
433
434 #[must_use]
436 pub fn block(self) -> Zip64CDELocatorBlock {
437 let Self {
438 disk_with_central_directory,
439 end_of_central_directory_offset,
440 number_of_disks,
441 } = self;
442 Zip64CDELocatorBlock {
443 disk_with_central_directory,
444 end_of_central_directory_offset,
445 number_of_disks,
446 }
447 }
448
449 pub fn write<T: Write>(self, writer: &mut T) -> ZipResult<()> {
451 self.block().write(writer)
452 }
453}
454
455#[derive(Copy, Clone)]
456#[repr(packed, C)]
457#[allow(missing_docs)]
458pub struct Zip64CDEBlock {
459 pub record_size: u64,
460 pub version_made_by: u16,
461 pub version_needed_to_extract: u16,
462 pub disk_number: u32,
463 pub disk_with_central_directory: u32,
464 pub number_of_files_on_this_disk: u64,
465 pub number_of_files: u64,
466 pub central_directory_size: u64,
467 pub central_directory_offset: u64,
468}
469
470unsafe impl Pod for Zip64CDEBlock {}
471
472impl FixedSizeBlock for Zip64CDEBlock {
473 const MAGIC: Magic = Magic::ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE;
474
475 const WRONG_MAGIC_ERROR: ZipError = invalid!("Invalid ZIP64 Central Directory End header");
476
477 to_and_from_le![
478 (record_size, u64),
479 (version_made_by, u16),
480 (version_needed_to_extract, u16),
481 (disk_number, u32),
482 (disk_with_central_directory, u32),
483 (number_of_files_on_this_disk, u64),
484 (number_of_files, u64),
485 (central_directory_size, u64),
486 (central_directory_offset, u64),
487 ];
488}
489
490#[allow(missing_docs)]
491pub struct Zip64CentralDirectoryEnd {
492 pub record_size: u64,
493 pub version_made_by: u16,
494 pub version_needed_to_extract: u16,
495 pub disk_number: u32,
496 pub disk_with_central_directory: u32,
497 pub number_of_files_on_this_disk: u64,
498 pub number_of_files: u64,
499 pub central_directory_size: u64,
500 pub central_directory_offset: u64,
501 pub(crate) zip64_extensible_data_sector: Option<Box<[u8]>>,
502}
503
504impl Zip64CentralDirectoryEnd {
505 const MIN_SIZE: usize = 2 * size_of::<u16>() + 2 * size_of::<u32>() + 4 * size_of::<u64>();
508 pub(crate) const MIN_FULL_SIZE: usize =
509 2 * size_of::<u16>() + 2 * size_of::<u32>() + 5 * size_of::<u64>();
510 pub(crate) const RECORD_OVERHEAD: u64 = (size_of::<Magic>() + size_of::<u64>()) as u64;
512
513 pub(crate) fn parse<T: Read + ?Sized>(
514 reader: &mut T,
515 max_size: u64,
516 ) -> ZipResult<Zip64CentralDirectoryEnd> {
517 let Zip64CDEBlock {
518 record_size,
519 version_made_by,
520 version_needed_to_extract,
521 disk_number,
522 disk_with_central_directory,
523 number_of_files_on_this_disk,
524 number_of_files,
525 central_directory_size,
526 central_directory_offset,
527 ..
528 } = Zip64CDEBlock::parse(reader)?;
529
530 if record_size < (Self::MIN_SIZE - size_of::<Magic>()) as u64 {
531 return Err(invalid!("Low EOCD64 record size"));
532 } else if record_size.saturating_add(Self::RECORD_OVERHEAD) > max_size {
533 return Err(invalid!("EOCD64 extends beyond EOCD64 locator"));
534 }
535
536 let zip64_extensible_data_sector = if record_size > (Self::MIN_SIZE as u64) {
537 let mut extensible_data_sector =
538 vec![0u8; record_size as usize - Self::MIN_SIZE].into_boxed_slice();
539 if let Err(e) = reader.read_exact(&mut extensible_data_sector) {
540 if e.kind() == io::ErrorKind::UnexpectedEof {
541 return Err(invalid!(
542 "EOCD64 extensible data sector exceeds file boundary"
543 ));
544 }
545 return Err(e.into());
546 }
547 Some(extensible_data_sector)
548 } else {
549 None
550 };
551
552 Ok(Self {
553 record_size,
554 version_made_by,
555 version_needed_to_extract,
556 disk_number,
557 disk_with_central_directory,
558 number_of_files_on_this_disk,
559 number_of_files,
560 central_directory_size,
561 central_directory_offset,
562 zip64_extensible_data_sector,
563 })
564 }
565
566 pub(crate) fn into_block_and_extensible_data(self) -> (Zip64CDEBlock, Option<Box<[u8]>>) {
567 let Self {
568 record_size,
569 version_made_by,
570 version_needed_to_extract,
571 disk_number,
572 disk_with_central_directory,
573 number_of_files_on_this_disk,
574 number_of_files,
575 central_directory_size,
576 central_directory_offset,
577 zip64_extensible_data_sector,
578 } = self;
579
580 (
581 Zip64CDEBlock {
582 record_size,
583 version_made_by,
584 version_needed_to_extract,
585 disk_number,
586 disk_with_central_directory,
587 number_of_files_on_this_disk,
588 number_of_files,
589 central_directory_size,
590 central_directory_offset,
591 },
592 zip64_extensible_data_sector,
593 )
594 }
595
596 pub(crate) fn write<T: Write>(self, writer: &mut T) -> ZipResult<()> {
597 let (block, zip64_extensible_data) = self.into_block_and_extensible_data();
598 block.write(writer)?;
599 if let Some(extensible_data) = zip64_extensible_data {
600 writer.write_all(&extensible_data)?;
601 }
602 Ok(())
603 }
604}
605
606pub(crate) struct DataAndPosition<T> {
607 pub data: T,
608 #[allow(dead_code)]
609 pub position: u64,
610}
611
612impl<T> From<(T, u64)> for DataAndPosition<T> {
613 fn from(value: (T, u64)) -> Self {
614 Self {
615 data: value.0,
616 position: value.1,
617 }
618 }
619}
620
621pub(crate) struct CentralDirectoryEndInfo {
622 pub eocd: DataAndPosition<Zip32CentralDirectoryEnd>,
623 pub eocd64: Option<DataAndPosition<Zip64CentralDirectoryEnd>>,
624
625 pub archive_offset: u64,
626}
627
628#[cfg(test)]
629mod tests {
630 use std::io::Cursor;
631
632 use crate::{
633 format::blocks::{FixedSizeBlock, Pod},
634 format::magic::Magic,
635 result::{ZipError, invalid},
636 };
637
638 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
639 #[repr(packed, C)]
640 pub struct TestBlock {
641 pub file_name_length: u16,
642 }
643
644 unsafe impl Pod for TestBlock {}
645
646 impl FixedSizeBlock for TestBlock {
647 const MAGIC: Magic = Magic::literal(0x01111);
648
649 const WRONG_MAGIC_ERROR: ZipError = invalid!("unreachable");
650
651 to_and_from_le![(file_name_length, u16)];
652 }
653
654 #[test]
656 fn block_serde() {
657 let block = TestBlock {
658 file_name_length: 3,
659 };
660 let mut c = Cursor::new(Vec::new());
661 block.write(&mut c).unwrap();
662 c.set_position(0);
663 let block2 = TestBlock::parse(&mut c).unwrap();
664 assert_eq!(block, block2);
665 }
666
667 #[test]
668 fn test_size_zip64_central_directory_end() {
669 use super::Zip64CentralDirectoryEnd;
670 assert_eq!(Zip64CentralDirectoryEnd::MIN_SIZE, 44);
671 assert_eq!(Zip64CentralDirectoryEnd::MIN_FULL_SIZE, 52);
672 }
673}