usecrate::read::magic_finder::{Backwards, Forward, MagicFinder, OptimisticMagicFinder}; usecrate::read::ArchiveOffset; usecrate::result::{ZipError, ZipResult}; use core::mem; use std::io; use std::io::prelude::*; use std::slice;
/// "Magic" header values used in the zip spec to locate metadata records. /// /// These values currently always take up a fixed four bytes, so we can parse and wrap them in this /// struct to enforce some small amount of type safety. #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] #[repr(transparent)] pub(crate) struct Magic(u32);
/// Similar to [`Magic`], but used for extra field tags as per section 4.5.3 of APPNOTE.TXT. #[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] #[repr(transparent)] pub(crate) struct ExtraFieldMagic(u16);
/* TODO: maybe try to use this for parsing extra fields as well as writing them? */ #[allow(dead_code)] impl ExtraFieldMagic { pubconstfn literal(x: u16) -> Self { Self(x)
}
/// The file size at which a ZIP64 record becomes necessary. /// /// If a file larger than this threshold attempts to be written, compressed or uncompressed, and /// [`FileOptions::large_file()`](crate::write::FileOptions) was not true, then [`ZipWriter`] will /// raise an [`io::Error`] with [`io::ErrorKind::Other`]. /// /// If the zip file itself is larger than this value, then a zip64 central directory record will be /// written to the end of the file. /// ///``` /// # fn main() -> Result<(), zip::result::ZipError> { /// # #[cfg(target_pointer_width = "64")] /// # { /// use std::io::{self, Cursor, prelude::*}; /// use std::error::Error; /// use zip::{ZipWriter, write::SimpleFileOptions}; /// /// let mut zip = ZipWriter::new(Cursor::new(Vec::new())); /// // Writing an extremely large file for this test is faster without compression. /// let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored); /// /// let big_len: usize = (zip::ZIP64_BYTES_THR as usize) + 1; /// let big_buf = vec![0u8; big_len]; /// zip.start_file("zero.dat", options)?; /// // This is too big! /// let res = zip.write_all(&big_buf[..]).err().unwrap(); /// assert_eq!(res.kind(), io::ErrorKind::Other); /// let description = format!("{}", &res); /// assert_eq!(description, "Large file option has not been set"); /// // Attempting to write anything further to the same zip will still succeed, but the previous /// // failing entry has been removed. /// zip.start_file("one.dat", options)?; /// let zip = zip.finish_into_readable()?; /// let names: Vec<_> = zip.file_names().collect(); /// assert_eq!(&names, &["one.dat"]); /// /// // Create a new zip output. /// let mut zip = ZipWriter::new(Cursor::new(Vec::new())); /// // This time, create a zip64 record for the file. /// let options = options.large_file(true); /// zip.start_file("zero.dat", options)?; /// // This succeeds because we specified that it could be a large file. /// assert!(zip.write_all(&big_buf[..]).is_ok()); /// # } /// # Ok(()) /// # } ///``` pubconst ZIP64_BYTES_THR: u64 = u32::MAX as u64; /// The number of entries within a single zip necessary to allocate a zip64 central /// directory record. /// /// If more than this number of entries is written to a [`ZipWriter`], then [`ZipWriter::finish()`] /// will write out extra zip64 data to the end of the zip file. pubconst ZIP64_ENTRY_THR: usize = u16::MAX as usize;
/// # Safety /// /// - No padding/uninit bytes /// - All bytes patterns must be valid /// - No cell, pointers /// /// See `bytemuck::Pod` for more details. pub(crate) unsafetrait Pod: Copy + 'static { #[inline] fn zeroed() -> Self { unsafe { mem::zeroed() }
}
/// Finds the EOCD and possibly the EOCD64 block and determines the archive offset. /// /// In the best case scenario (no prepended junk), this function will not backtrack /// in the reader. pub(crate) fn find_central_directory<R: Read + Seek>(
reader: &mut R,
archive_offset: ArchiveOffset,
end_exclusive: u64,
file_len: u64,
) -> ZipResult<CentralDirectoryEndInfo> { const EOCD_SIG_BYTES: [u8; mem::size_of::<Magic>()] =
Magic::CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes();
// Instantiate the mandatory finder letmut eocd_finder = MagicFinder::<Backwards<'static>>::new(&EOCD_SIG_BYTES, 0, end_exclusive); letmut subfinder: Option<OptimisticMagicFinder<Forward<'static>>> = None;
// Keep the last errors for cases of improper EOCD instances. letmut parsing_error = None;
whilelet Some(eocd_offset) = eocd_finder.next(reader)? { // Attempt to parse the EOCD block let eocd = match Zip32CentralDirectoryEnd::parse(reader) {
Ok(eocd) => eocd,
Err(e) => { if parsing_error.is_none() {
parsing_error = Some(e);
} continue;
}
};
// ! Relaxed (inequality) due to garbage-after-comment Python files // Consistency check: the EOCD comment must terminate before the end of file if eocd.zip_file_comment.len() as u64 + eocd_offset + 22 > file_len {
parsing_error = Some(ZipError::InvalidArchive("Invalid EOCD comment length")); continue;
}
let zip64_metadata = if eocd.may_be_zip64() { fn try_read_eocd64_locator(
reader: &mut (impl Read + Seek),
eocd_offset: u64,
) -> ZipResult<(u64, Zip64CentralDirectoryEndLocator)> { if eocd_offset < mem::size_of::<Zip64CDELocatorBlock>() as u64 { return Err(ZipError::InvalidArchive( "EOCD64 Locator does not fit in file",
));
}
let locator64_offset = eocd_offset - mem::size_of::<Zip64CDELocatorBlock>() as u64;
let Some((locator64_offset, locator64)) = zip64_metadata else { // Branch out for zip32 let relative_cd_offset = eocd.central_directory_offset as u64;
// If the archive is empty, there is nothing more to be checked, the archive is correct. if eocd.number_of_files == 0 { return Ok(CentralDirectoryEndInfo {
eocd: (eocd, eocd_offset).into(),
eocd64: None,
archive_offset: eocd_offset.saturating_sub(relative_cd_offset),
});
}
// Consistency check: the CD relative offset cannot be after the EOCD if relative_cd_offset >= eocd_offset {
parsing_error = Some(ZipError::InvalidArchive("Invalid CDFH offset in EOCD")); continue;
}
// Attempt to find the first CDFH let subfinder = subfinder
.get_or_insert_with(OptimisticMagicFinder::new_empty)
.repurpose(
&CDFH_SIG_BYTES, // The CDFH must be before the EOCD and after the relative offset, // because prepended junk can only move it forward.
(relative_cd_offset, eocd_offset), match archive_offset {
ArchiveOffset::Known(n) => {
Some((relative_cd_offset.saturating_add(n).min(eocd_offset), true))
}
_ => Some((relative_cd_offset, false)),
},
);
// Consistency check: find the first CDFH iflet Some(cd_offset) = subfinder.next(reader)? { // The first CDFH will define the archive offset let archive_offset = cd_offset - relative_cd_offset;
// Consistency check: the EOCD64 offset must be before EOCD64 Locator offset */ if locator64.end_of_central_directory_offset >= locator64_offset {
parsing_error = Some(ZipError::InvalidArchive("Invalid EOCD64 Locator CD offset")); continue;
}
if locator64.number_of_disks > 1 {
parsing_error = Some(ZipError::InvalidArchive( "Multi-disk ZIP files are not supported",
)); continue;
}
// This was hidden inside a function to collect errors in a single place. // Once try blocks are stabilized, this can go away. fn try_read_eocd64<R: Read + Seek>(
reader: &mut R,
locator64: &Zip64CentralDirectoryEndLocator,
expected_length: u64,
) -> ZipResult<Zip64CentralDirectoryEnd> { let z64 = Zip64CentralDirectoryEnd::parse(reader, expected_length)?;
// Consistency check: EOCD64 locator should agree with the EOCD64 if z64.disk_with_central_directory != locator64.disk_with_central_directory { return Err(ZipError::InvalidArchive( "Invalid EOCD64: inconsistency with Locator data",
));
}
// Consistency check: the EOCD64 must have the expected length if z64.record_size + 12 != expected_length { return Err(ZipError::InvalidArchive( "Invalid EOCD64: inconsistent length",
));
}
Ok(z64)
}
// Attempt to find the EOCD64 with an initial guess let subfinder = subfinder
.get_or_insert_with(OptimisticMagicFinder::new_empty)
.repurpose(
&EOCD64_SIG_BYTES,
(locator64.end_of_central_directory_offset, locator64_offset), match archive_offset {
ArchiveOffset::Known(n) => Some((
locator64
.end_of_central_directory_offset
.saturating_add(n)
.min(locator64_offset), true,
)),
_ => Some((locator64.end_of_central_directory_offset, false)),
},
);
/// Demonstrate that a block object can be safely written to memory and deserialized back out. #[test] fn block_serde() { let block = TestBlock {
magic: TestBlock::MAGIC,
file_name_length: 3,
}; letmut c = Cursor::new(Vec::new());
block.write(&mut c).unwrap();
c.set_position(0); let block2 = TestBlock::parse(&mut c).unwrap();
assert_eq!(block, block2);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.