#[cfg(feature = "std")]
use crate::Crc32;
use crate::errors::{Error, ErrorKind};
use crate::extra_fields::{ExtraFieldId, ExtraFields};
use crate::headers::EntryFlags;
use crate::mode::{
CreatorSystem, EntryMode, VersionMadeBy, msdos_mode_to_file_mode, unix_mode_to_file_mode,
};
use crate::path::{RawPath, ZipFilePath};
#[cfg(feature = "std")]
use crate::reader_at::{ReaderAt, ReaderAtExt};
use crate::time::{DosDateTime, ZipDateTimeKind, extract_best_timestamp};
use crate::utils::{le_u16, le_u32, le_u64};
use crate::{EndOfCentralDirectory, EndOfCentralDirectoryRecordFixed, ZipLocator};
#[cfg(feature = "std")]
use std::io::Write;
#[cfg(feature = "std")]
mod reader;
#[cfg(feature = "std")]
pub use reader::{ZipEntries, ZipEntry, ZipReader, ZipSliceVerifier, ZipVerifier};
pub(crate) const END_OF_CENTRAL_DIR_SIGNATURE64: u32 = 0x06064b50;
pub(crate) const END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE: u32 = 0x07064b50;
pub(crate) const CENTRAL_HEADER_SIGNATURE: u32 = 0x02014b50;
pub const RECOMMENDED_BUFFER_SIZE: usize = 1 << 16;
pub const MAX_CENTRAL_DIRECTORY_RECORD_SIZE: usize =
ZipFileHeaderFixed::SIZE + 3 * u16::MAX as usize;
#[derive(Debug, Clone)]
pub struct ZipSliceArchive<T> {
data: T,
eocd: EndOfCentralDirectory,
}
impl<T: AsRef<[u8]>> ZipSliceArchive<T> {
pub(crate) fn new(data: T, eocd: EndOfCentralDirectory) -> Self {
ZipSliceArchive { data, eocd }
}
pub fn entries(&self) -> ZipSliceEntries<'_> {
let data = self.data.as_ref();
let directory_start = self.eocd.directory_offset();
let entry_data = &data[(directory_start as usize)..self.eocd.head_eocd_offset() as usize];
ZipSliceEntries {
entry_data,
base_offset: self.eocd.base_offset(),
current_offset: directory_start,
}
}
pub fn get_ref(&self) -> &T {
&self.data
}
pub fn into_inner(self) -> T {
self.data
}
pub fn entries_hint(&self) -> u64 {
self.eocd.entries()
}
pub fn eocd_offset(&self) -> u64 {
self.eocd.tail_eocd_offset()
}
pub fn directory_offset(&self) -> u64 {
self.eocd.directory_offset()
}
pub fn end_offset(&self) -> u64 {
self.eocd.tail_eocd_offset()
+ EndOfCentralDirectoryRecordFixed::SIZE as u64
+ self.comment().as_bytes().len() as u64
}
pub fn comment(&self) -> ZipStr<'_> {
let data = self.data.as_ref();
let comment_start =
self.eocd.tail_eocd_offset() as usize + EndOfCentralDirectoryRecordFixed::SIZE;
let comment_len = self.eocd.comment_len();
ZipStr::new(&data[comment_start..comment_start + comment_len])
}
#[cfg(feature = "std")]
pub fn into_cursor_archive(self) -> ZipArchive<std::io::Cursor<T>> {
ZipArchive {
reader: std::io::Cursor::new(self.data),
eocd: self.eocd,
}
}
pub fn get_entry(&self, entry: ZipArchiveEntryWayfinder) -> Result<ZipSliceEntry<'_>, Error> {
let data = self.data.as_ref();
let header = &data[(entry.local_header_offset as usize).min(data.len())..];
let file_header = ZipLocalFileHeaderFixed::parse(header)?;
let variable_length = file_header.variable_length();
let header_size = (ZipLocalFileHeaderFixed::SIZE + variable_length) as u32;
let (total_size, o1) =
(u64::from(header_size)).overflowing_add(entry.compressed_size_hint());
if o1 || (header.len() as u64) < total_size {
return Err(Error::from(ErrorKind::Eof));
}
let (entire_entry, descriptor) = header.split_at(total_size as usize);
Ok(ZipSliceEntry {
data: entire_entry,
verifier: ZipVerification {
crc: entry.crc,
uncompressed_size: entry.uncompressed_size_hint(),
},
local_header_offset: entry.local_header_offset,
data_start_offset: header_size,
has_data_descriptor: entry.has_data_descriptor,
data_descriptor_uses_zip64_sizes: entry.data_descriptor_uses_zip64_sizes,
descriptor,
})
}
}
#[derive(Debug, Clone)]
pub struct ZipSliceEntry<'a> {
data: &'a [u8],
verifier: ZipVerification,
local_header_offset: u64,
data_start_offset: u32,
has_data_descriptor: bool,
data_descriptor_uses_zip64_sizes: bool,
descriptor: &'a [u8],
}
impl<'a> ZipSliceEntry<'a> {
pub fn data(&self) -> &'a [u8] {
&self.data[self.data_start_offset as usize..]
}
pub fn claim_verifier(&self) -> ZipVerification {
self.verifier
}
pub fn data_descriptor(&self) -> Result<Option<ZipDataDescriptor>, Error> {
if !self.has_data_descriptor {
return Ok(None);
}
let descriptor =
DataDescriptor::parse(self.descriptor, self.data_descriptor_uses_zip64_sizes)?;
Ok(Some(ZipDataDescriptor {
crc: descriptor.crc,
compressed_size: descriptor.compressed_size,
uncompressed_size: descriptor.uncompressed_size,
}))
}
#[cfg(feature = "std")]
pub fn verifying_reader<D>(&self, reader: D) -> ZipSliceVerifier<D>
where
D: std::io::Read,
{
ZipSliceVerifier(ZipVerifier {
reader,
verifier: self.verifier,
crc: Crc32::new(),
size: 0,
})
}
pub fn compressed_data_range(&self) -> (u64, u64) {
let compressed_data_start = self.local_header_offset + self.data_start_offset as u64;
let compressed_data_end =
compressed_data_start + (self.data.len() - self.data_start_offset as usize) as u64;
(compressed_data_start, compressed_data_end)
}
pub fn local_header(&self) -> ZipLocalFileHeader<'a> {
let header =
ZipLocalFileHeaderFixed::parse(self.data).expect("header has already been parsed");
let file_name_len = header.file_name_len as usize;
let extra_field_len = header.extra_field_len as usize;
let filename_start = ZipLocalFileHeaderFixed::SIZE;
let filename_end = filename_start + file_name_len;
let extra_field_end = filename_end + extra_field_len;
let (compressed_size, uncompressed_size) =
local_header_size_hints(&header, &self.data[filename_end..extra_field_end]);
ZipLocalFileHeader {
fixed: header,
compressed_size,
uncompressed_size,
file_path: ZipFilePath::from_bytes(&self.data[filename_start..filename_end]),
extra_field: &self.data[filename_end..extra_field_end],
}
}
}
#[derive(Debug, Clone)]
pub struct ZipSliceEntries<'data> {
entry_data: &'data [u8],
base_offset: u64,
current_offset: u64,
}
impl<'data> ZipSliceEntries<'data> {
#[inline]
pub fn next_entry(&mut self) -> Result<Option<ZipFileHeaderRecord<'data>>, Error> {
if self.entry_data.is_empty() {
return Ok(None);
}
let file_header = ZipFileHeaderFixed::parse(self.entry_data)?;
let Some((file_name, extra_field, file_comment, entry_data)) =
file_header.parse_variable_length(&self.entry_data[ZipFileHeaderFixed::SIZE..])
else {
return Err(Error::from(ErrorKind::Eof));
};
let mut entry = ZipFileHeaderRecord::from_parts(
file_header,
file_name,
extra_field,
file_comment,
self.current_offset,
);
entry.local_header_offset += self.base_offset;
self.current_offset += (self.entry_data.len() - entry_data.len()) as u64;
self.entry_data = entry_data;
Ok(Some(entry))
}
}
impl<'data> Iterator for ZipSliceEntries<'data> {
type Item = Result<ZipFileHeaderRecord<'data>, Error>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.next_entry().transpose()
}
}
#[cfg_attr(
feature = "std",
doc = "With the `std` feature, it can also be created from a file or any `Read + Seek` source."
)]
#[cfg_attr(
feature = "std",
doc = r#"
# Examples
Creating from a file:
```rust
# fn example_from_file(file: std::fs::File) -> Result<(), rawzip::Error> {
# use rawzip::{ZipArchive, RECOMMENDED_BUFFER_SIZE};
let mut buffer = vec![0u8; RECOMMENDED_BUFFER_SIZE];
let archive = ZipArchive::from_file(file, &mut buffer)?;
# Ok(())
# }
```
"#
)]
#[derive(Debug, Clone)]
#[cfg_attr(not(feature = "std"), allow(dead_code))]
pub struct ZipArchive<R> {
reader: R,
eocd: EndOfCentralDirectory,
}
impl ZipArchive<()> {
pub fn with_max_search_space(max_search_space: u64) -> ZipLocator {
ZipLocator::new().max_search_space(max_search_space)
}
pub fn from_slice<T: AsRef<[u8]>>(data: T) -> Result<ZipSliceArchive<T>, Error> {
ZipLocator::new().locate_in_slice(data).map_err(|(_, e)| e)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ZipVerification {
pub crc: u32,
pub uncompressed_size: u64,
}
impl ZipVerification {
pub fn valid(&self, rhs: ZipVerification) -> Result<(), Error> {
if self.uncompressed_size != rhs.uncompressed_size {
return Err(Error::from(ErrorKind::InvalidSize {
expected: self.uncompressed_size,
actual: rhs.uncompressed_size,
}));
}
if self.crc != rhs.crc {
return Err(Error::from(ErrorKind::InvalidChecksum {
expected: self.crc,
actual: rhs.crc,
}));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ZipLocalFileHeader<'a> {
fixed: ZipLocalFileHeaderFixed,
compressed_size: u64,
uncompressed_size: u64,
file_path: ZipFilePath<RawPath<'a>>,
extra_field: &'a [u8],
}
impl<'a> ZipLocalFileHeader<'a> {
#[inline]
pub fn flags(&self) -> EntryFlags {
self.fixed.flags
}
#[inline]
pub fn file_path(&self) -> ZipFilePath<RawPath<'a>> {
self.file_path
}
#[inline]
pub fn last_modified_dos(&self) -> DosDateTime {
DosDateTime::new(self.fixed.last_mod_time, self.fixed.last_mod_date)
}
#[inline]
pub fn compression_method(&self) -> CompressionMethod {
self.fixed.compression_method
}
#[inline]
pub fn last_modified(&self) -> ZipDateTimeKind {
extract_best_timestamp(
self.extra_fields(),
self.fixed.last_mod_time,
self.fixed.last_mod_date,
)
}
#[inline]
pub fn crc32(&self) -> u32 {
self.fixed.crc32
}
#[inline]
pub fn compressed_size_hint(&self) -> u64 {
self.compressed_size
}
#[inline]
pub fn uncompressed_size_hint(&self) -> u64 {
self.uncompressed_size
}
#[inline]
pub fn extra_fields(&self) -> ExtraFields<'a> {
ExtraFields::new(self.extra_field)
}
}
fn local_header_size_hints(header: &ZipLocalFileHeaderFixed, extra_field: &[u8]) -> (u64, u64) {
let mut compressed_size = u64::from(header.compressed_size);
let mut uncompressed_size = u64::from(header.uncompressed_size);
if header.compressed_size == u32::MAX || header.uncompressed_size == u32::MAX {
for (field_id, field_data) in ExtraFields::new(extra_field) {
if field_id != ExtraFieldId::ZIP64 {
continue;
}
let mut field = field_data;
if header.uncompressed_size == u32::MAX {
if let Some(v) = field.get(..8).map(le_u64) {
uncompressed_size = v;
field = &field[8..];
}
}
if header.compressed_size == u32::MAX {
if let Some(v) = field.get(..8).map(le_u64) {
compressed_size = v;
}
}
break;
}
}
(compressed_size, uncompressed_size)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ZipDataDescriptor {
crc: u32,
compressed_size: u64,
uncompressed_size: u64,
}
impl ZipDataDescriptor {
#[inline]
pub fn crc32(&self) -> u32 {
self.crc
}
#[inline]
pub fn compressed_size(&self) -> u64 {
self.compressed_size
}
#[inline]
pub fn uncompressed_size(&self) -> u64 {
self.uncompressed_size
}
}
#[derive(Debug, Clone)]
pub(crate) struct DataDescriptor {
crc: u32,
compressed_size: u64,
uncompressed_size: u64,
}
impl DataDescriptor {
#[cfg(feature = "std")]
const MAX_SIZE: usize = 24;
pub const SIGNATURE: u32 = 0x08074b50;
fn parse(data: &[u8], uses_zip64_sizes: bool) -> Result<DataDescriptor, Error> {
let eof = || Error::from(ErrorKind::Eof);
let body = match data.split_first_chunk::<4>() {
Some((sig, rest)) if u32::from_le_bytes(*sig) == Self::SIGNATURE => rest,
_ => data,
};
let (crc, sizes) = body.split_first_chunk::<4>().ok_or_else(eof)?;
let crc = u32::from_le_bytes(*crc);
let (compressed_size, uncompressed_size) = if uses_zip64_sizes {
let (compressed, rest) = sizes.split_first_chunk::<8>().ok_or_else(eof)?;
let (uncompressed, _) = rest.split_first_chunk::<8>().ok_or_else(eof)?;
(
u64::from_le_bytes(*compressed),
u64::from_le_bytes(*uncompressed),
)
} else {
let (compressed, rest) = sizes.split_first_chunk::<4>().ok_or_else(eof)?;
let (uncompressed, _) = rest.split_first_chunk::<4>().ok_or_else(eof)?;
(
u64::from(u32::from_le_bytes(*compressed)),
u64::from(u32::from_le_bytes(*uncompressed)),
)
};
Ok(DataDescriptor {
crc,
compressed_size,
uncompressed_size,
})
}
#[cfg(feature = "std")]
fn read_at<R>(reader: R, offset: u64, uses_zip64_sizes: bool) -> Result<DataDescriptor, Error>
where
R: ReaderAt,
{
let mut buffer = [0u8; Self::MAX_SIZE];
let read = reader.try_read_at_least_at(&mut buffer, Self::MAX_SIZE, offset)?;
Self::parse(&buffer[..read], uses_zip64_sizes)
}
}
#[derive(Debug, Clone)]
pub(crate) struct Zip64EndOfCentralDirectory {
pub offset: u64,
pub central_dir_offset: u64,
pub central_dir_size: u64,
pub num_entries: u64,
}
impl Zip64EndOfCentralDirectory {
#[inline]
pub fn from_parts(offset: u64, record: Zip64EndOfCentralDirectoryRecord) -> Self {
Self {
offset,
central_dir_offset: record.central_dir_offset,
central_dir_size: record.central_dir_size,
num_entries: record.num_entries,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Zip64EndOfCentralDirectoryRecord {
pub signature: u32,
#[allow(dead_code)]
pub size: u64,
#[allow(dead_code)]
pub version_made_by: VersionMadeBy,
#[allow(dead_code)]
pub version_needed: u16,
#[allow(dead_code)]
pub disk_number: u32,
#[allow(dead_code)]
pub cd_disk: u32,
pub num_entries: u64,
#[allow(dead_code)]
pub total_entries: u64,
pub central_dir_size: u64,
pub central_dir_offset: u64,
}
impl Zip64EndOfCentralDirectoryRecord {
pub(crate) const SIZE: usize = 56;
#[inline]
pub fn parse(data: &[u8]) -> Result<Zip64EndOfCentralDirectoryRecord, Error> {
if data.len() < Self::SIZE {
return Err(Error::from(ErrorKind::Eof));
}
let result = Zip64EndOfCentralDirectoryRecord {
signature: le_u32(&data[0..4]),
size: le_u64(&data[4..12]),
version_made_by: VersionMadeBy::from_raw(le_u16(&data[12..14])),
version_needed: le_u16(&data[14..16]),
disk_number: le_u32(&data[16..20]),
cd_disk: le_u32(&data[20..24]),
num_entries: le_u64(&data[24..32]),
total_entries: le_u64(&data[32..40]),
central_dir_size: le_u64(&data[40..48]),
central_dir_offset: le_u64(&data[48..56]),
};
if result.signature != END_OF_CENTRAL_DIR_SIGNATURE64 {
return Err(Error::from(ErrorKind::InvalidSignature {
expected: END_OF_CENTRAL_DIR_SIGNATURE64,
actual: result.signature,
}));
}
Ok(result)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct CompressionMethod(u16);
impl CompressionMethod {
pub const STORE: Self = Self(0);
pub const SHRUNK: Self = Self(1);
pub const REDUCE1: Self = Self(2);
pub const REDUCE2: Self = Self(3);
pub const REDUCE3: Self = Self(4);
pub const REDUCE4: Self = Self(5);
pub const IMPLODED: Self = Self(6);
pub const TOKENIZING: Self = Self(7);
pub const DEFLATE: Self = Self(8);
pub const DEFLATE64: Self = Self(9);
pub const DCL_IMPLODE: Self = Self(10);
pub const BZIP2: Self = Self(12);
pub const LZMA: Self = Self(14);
pub const CMPSC: Self = Self(16);
pub const TERSE: Self = Self(18);
pub const LZ77: Self = Self(19);
pub const ZSTD_DEPRECATED: Self = Self(20);
pub const ZSTD: Self = Self(93);
pub const MP3: Self = Self(94);
pub const XZ: Self = Self(95);
pub const JPEG: Self = Self(96);
pub const WAVPACK: Self = Self(97);
pub const PPMD: Self = Self(98);
pub const AES: Self = Self(99);
#[inline]
pub const fn new(id: u16) -> Self {
Self(id)
}
#[inline]
pub const fn as_u16(self) -> u16 {
self.0
}
#[inline]
pub const fn name(self) -> Option<&'static str> {
match self.0 {
0 => Some("STORE"),
1 => Some("SHRUNK"),
2 => Some("REDUCE1"),
3 => Some("REDUCE2"),
4 => Some("REDUCE3"),
5 => Some("REDUCE4"),
6 => Some("IMPLODED"),
7 => Some("TOKENIZING"),
8 => Some("DEFLATE"),
9 => Some("DEFLATE64"),
10 => Some("DCL_IMPLODE"),
12 => Some("BZIP2"),
14 => Some("LZMA"),
16 => Some("CMPSC"),
18 => Some("TERSE"),
19 => Some("LZ77"),
20 => Some("ZSTD_DEPRECATED"),
93 => Some("ZSTD"),
94 => Some("MP3"),
95 => Some("XZ"),
96 => Some("JPEG"),
97 => Some("WAVPACK"),
98 => Some("PPMD"),
99 => Some("AES"),
_ => None,
}
}
}
impl core::fmt::Debug for CompressionMethod {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.name() {
Some(name) => write!(f, "CompressionMethod::{name}"),
None => write!(f, "CompressionMethod({})", self.0),
}
}
}
impl core::fmt::Display for CompressionMethod {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} ({})", self.0, self.name().unwrap_or("UNKNOWN"))
}
}
impl From<u16> for CompressionMethod {
fn from(id: u16) -> Self {
Self(id)
}
}
#[allow(non_upper_case_globals)]
impl CompressionMethod {
#[deprecated(note = "use CompressionMethod::STORE")]
pub const Store: Self = Self::STORE;
#[deprecated(note = "use CompressionMethod::SHRUNK")]
pub const Shrunk: Self = Self::SHRUNK;
#[deprecated(note = "use CompressionMethod::REDUCE1")]
pub const Reduce1: Self = Self::REDUCE1;
#[deprecated(note = "use CompressionMethod::REDUCE2")]
pub const Reduce2: Self = Self::REDUCE2;
#[deprecated(note = "use CompressionMethod::REDUCE3")]
pub const Reduce3: Self = Self::REDUCE3;
#[deprecated(note = "use CompressionMethod::REDUCE4")]
pub const Reduce4: Self = Self::REDUCE4;
#[deprecated(note = "use CompressionMethod::IMPLODED")]
pub const Imploded: Self = Self::IMPLODED;
#[deprecated(note = "use CompressionMethod::TOKENIZING")]
pub const Tokenizing: Self = Self::TOKENIZING;
#[deprecated(note = "use CompressionMethod::DEFLATE")]
pub const Deflate: Self = Self::DEFLATE;
#[deprecated(note = "use CompressionMethod::DEFLATE64")]
pub const Deflate64: Self = Self::DEFLATE64;
#[deprecated(note = "use CompressionMethod::DCL_IMPLODE")]
pub const DclImplode: Self = Self::DCL_IMPLODE;
#[deprecated(note = "use CompressionMethod::BZIP2")]
pub const Bzip2: Self = Self::BZIP2;
#[deprecated(note = "use CompressionMethod::LZMA")]
pub const Lzma: Self = Self::LZMA;
#[deprecated(note = "use CompressionMethod::CMPSC")]
pub const Cmpsc: Self = Self::CMPSC;
#[deprecated(note = "use CompressionMethod::TERSE")]
pub const Terse: Self = Self::TERSE;
#[deprecated(note = "use CompressionMethod::LZ77")]
pub const Lz77: Self = Self::LZ77;
#[deprecated(note = "use CompressionMethod::ZSTD_DEPRECATED")]
pub const ZstdDeprecated: Self = Self::ZSTD_DEPRECATED;
#[deprecated(note = "use CompressionMethod::ZSTD")]
pub const Zstd: Self = Self::ZSTD;
#[deprecated(note = "use CompressionMethod::MP3")]
pub const Mp3: Self = Self::MP3;
#[deprecated(note = "use CompressionMethod::XZ")]
pub const Xz: Self = Self::XZ;
#[deprecated(note = "use CompressionMethod::JPEG")]
pub const Jpeg: Self = Self::JPEG;
#[deprecated(note = "use CompressionMethod::WAVPACK")]
pub const WavPack: Self = Self::WAVPACK;
#[deprecated(note = "use CompressionMethod::PPMD")]
pub const Ppmd: Self = Self::PPMD;
#[deprecated(note = "use CompressionMethod::AES")]
pub const Aes: Self = Self::AES;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ZipStr<'a>(&'a [u8]);
impl<'a> ZipStr<'a> {
#[inline]
pub fn new(data: &'a [u8]) -> Self {
Self(data)
}
#[inline]
pub fn as_bytes(&self) -> &'a [u8] {
self.0
}
#[cfg(feature = "alloc")]
#[inline]
pub fn into_owned(&self) -> ZipString {
ZipString::new(self.0.to_vec())
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ZipString(alloc::vec::Vec<u8>);
#[cfg(feature = "alloc")]
impl ZipString {
#[inline]
pub fn new(data: alloc::vec::Vec<u8>) -> Self {
Self(data)
}
#[inline]
pub fn as_str(&self) -> ZipStr<'_> {
ZipStr::new(self.0.as_slice())
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ZipFileHeaderRecord<'a> {
signature: u32,
version_made_by: u16,
version_needed: u16,
flags: EntryFlags,
compression_method: CompressionMethod,
last_mod_time: u16,
last_mod_date: u16,
crc32: u32,
compressed_size: u64,
uncompressed_size: u64,
file_name_len: u16,
extra_field_len: u16,
file_comment_len: u16,
disk_number_start: u32,
internal_file_attrs: u16,
external_file_attrs: u32,
local_header_offset: u64,
central_directory_offset: u64,
file_name: ZipFilePath<RawPath<'a>>,
extra_field: &'a [u8],
file_comment: ZipStr<'a>,
is_zip64: bool,
data_descriptor_uses_zip64_sizes: bool,
}
impl<'a> ZipFileHeaderRecord<'a> {
#[inline]
fn from_parts(
header: ZipFileHeaderFixed,
file_name: &'a [u8],
extra_field: &'a [u8],
file_comment: &'a [u8],
central_directory_offset: u64,
) -> Self {
let zip64_sizes =
header.uncompressed_size == u32::MAX || header.compressed_size == u32::MAX;
let data_descriptor_uses_zip64_sizes = zip64_sizes;
let mut result = Self {
signature: header.signature,
version_made_by: header.version_made_by,
version_needed: header.version_needed,
flags: header.flags,
compression_method: header.compression_method,
last_mod_time: header.last_mod_time,
last_mod_date: header.last_mod_date,
crc32: header.crc32,
compressed_size: u64::from(header.compressed_size),
uncompressed_size: u64::from(header.uncompressed_size),
file_name_len: header.file_name_len,
extra_field_len: header.extra_field_len,
file_comment_len: header.file_comment_len,
disk_number_start: u32::from(header.disk_number_start),
internal_file_attrs: header.internal_file_attrs,
external_file_attrs: header.external_file_attrs,
local_header_offset: u64::from(header.local_header_offset),
central_directory_offset,
file_name: ZipFilePath::from_bytes(file_name),
extra_field,
file_comment: ZipStr::new(file_comment),
is_zip64: false,
data_descriptor_uses_zip64_sizes,
};
if !zip64_sizes
&& result.local_header_offset != u64::from(u32::MAX)
&& result.disk_number_start != u32::from(u16::MAX)
{
return result;
}
let extra_fields = ExtraFields::new(extra_field);
for (field_id, field_data) in extra_fields {
if field_id != ExtraFieldId::ZIP64 {
continue;
}
let mut field = field_data;
result.is_zip64 = true;
if header.uncompressed_size == u32::MAX {
let Some(uncompressed_size) = field.get(..8).map(le_u64) else {
break;
};
result.uncompressed_size = uncompressed_size;
field = &field[8..];
}
if header.compressed_size == u32::MAX {
let Some(compressed_size) = field.get(..8).map(le_u64) else {
break;
};
result.compressed_size = compressed_size;
field = &field[8..];
}
if header.local_header_offset == u32::MAX {
let Some(local_header_offset) = field.get(..8).map(le_u64) else {
break;
};
result.local_header_offset = local_header_offset;
field = &field[8..];
}
if header.disk_number_start == u16::MAX {
let Some(disk_number_start) = field.get(..4).map(le_u32) else {
break;
};
result.disk_number_start = disk_number_start;
}
break;
}
result
}
#[inline]
pub fn is_dir(&self) -> bool {
self.file_name.is_dir()
}
#[inline]
pub fn flags(&self) -> EntryFlags {
self.flags
}
#[inline]
pub fn wayfinder(&self) -> ZipArchiveEntryWayfinder {
ZipArchiveEntryWayfinder {
uncompressed_size: self.uncompressed_size,
compressed_size: self.compressed_size,
local_header_offset: self.local_header_offset,
has_data_descriptor: self.flags().has_data_descriptor(),
crc: self.crc32,
data_descriptor_uses_zip64_sizes: self.data_descriptor_uses_zip64_sizes,
}
}
#[inline]
pub fn uncompressed_size_hint(&self) -> u64 {
self.uncompressed_size
}
#[inline]
pub fn compressed_size_hint(&self) -> u64 {
self.compressed_size
}
#[inline]
pub fn local_header_offset(&self) -> u64 {
self.local_header_offset
}
#[inline]
pub fn compression_method(&self) -> CompressionMethod {
self.compression_method
}
#[cfg_attr(
feature = "alloc",
doc = r#"
With the `alloc` feature, use [`ZipFilePath::try_normalize`] to create a safe path:
```rust
# use rawzip::ZipArchive;
# fn example() -> Result<(), Box<dyn std::error::Error>> {
# let data = include_bytes!("../assets/test.zip");
# let archive = ZipArchive::from_slice(data)?;
# let mut entries = archive.entries();
# let entry = entries.next_entry()?.unwrap();
let safe_path = entry.file_path().try_normalize()?;
println!("Safe path: {}", safe_path.as_ref());
# Ok(())
# }
# example()?;
# Ok::<(), Box<dyn std::error::Error>>(())
```
"#
)]
#[inline]
pub fn file_path(&self) -> ZipFilePath<RawPath<'a>> {
self.file_name
}
#[inline]
pub fn last_modified(&self) -> ZipDateTimeKind {
extract_best_timestamp(self.extra_fields(), self.last_mod_time, self.last_mod_date)
}
#[inline]
pub fn last_modified_dos(&self) -> DosDateTime {
DosDateTime::new(self.last_mod_time, self.last_mod_date)
}
#[inline]
pub fn mode(&self) -> EntryMode {
let mut mode = match self.version_made_by().creator_system() {
CreatorSystem::UNIX | CreatorSystem::MACOS => {
unix_mode_to_file_mode(self.external_file_attrs >> 16)
}
CreatorSystem::FAT | CreatorSystem::NTFS | CreatorSystem::MVS | CreatorSystem::VFAT => {
msdos_mode_to_file_mode(self.external_file_attrs)
}
_ => 0o644,
};
if self.is_dir() {
mode |= 0o040000; }
EntryMode::new(mode)
}
#[inline]
pub fn version_made_by(&self) -> VersionMadeBy {
VersionMadeBy::from_raw(self.version_made_by)
}
#[inline]
pub fn external_attributes(&self) -> u32 {
self.external_file_attrs
}
#[inline]
pub fn crc32(&self) -> u32 {
self.crc32
}
#[inline]
pub fn central_directory_offset(&self) -> u64 {
self.central_directory_offset
}
#[inline]
pub fn extra_fields(&self) -> ExtraFields<'_> {
ExtraFields::new(self.extra_field)
}
#[inline]
pub fn comment(&self) -> ZipStr<'_> {
self.file_comment
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ZipArchiveEntryWayfinder {
uncompressed_size: u64,
compressed_size: u64,
local_header_offset: u64,
crc: u32,
has_data_descriptor: bool,
data_descriptor_uses_zip64_sizes: bool,
}
impl ZipArchiveEntryWayfinder {
#[inline]
pub fn uncompressed_size_hint(&self) -> u64 {
self.uncompressed_size
}
#[inline]
pub fn compressed_size_hint(&self) -> u64 {
self.compressed_size
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(not(feature = "std"), allow(dead_code))]
pub(crate) struct ZipLocalFileHeaderFixed {
pub(crate) signature: u32,
pub(crate) version_needed: u16,
pub(crate) flags: EntryFlags,
pub(crate) compression_method: CompressionMethod,
pub(crate) last_mod_time: u16,
pub(crate) last_mod_date: u16,
pub(crate) crc32: u32,
pub(crate) compressed_size: u32,
pub(crate) uncompressed_size: u32,
pub(crate) file_name_len: u16,
pub(crate) extra_field_len: u16,
}
impl ZipLocalFileHeaderFixed {
const SIZE: usize = 30;
pub const SIGNATURE: u32 = 0x04034b50;
pub fn parse(data: &[u8]) -> Result<ZipLocalFileHeaderFixed, Error> {
if data.len() < Self::SIZE {
return Err(Error::from(ErrorKind::Eof));
}
let result = ZipLocalFileHeaderFixed {
signature: le_u32(&data[0..4]),
version_needed: le_u16(&data[4..6]),
flags: EntryFlags::new(le_u16(&data[6..8])),
compression_method: CompressionMethod::new(le_u16(&data[8..10])),
last_mod_time: le_u16(&data[10..12]),
last_mod_date: le_u16(&data[12..14]),
crc32: le_u32(&data[14..18]),
compressed_size: le_u32(&data[18..22]),
uncompressed_size: le_u32(&data[22..26]),
file_name_len: le_u16(&data[26..28]),
extra_field_len: le_u16(&data[28..30]),
};
if result.signature != Self::SIGNATURE {
return Err(Error::from(ErrorKind::InvalidSignature {
expected: Self::SIGNATURE,
actual: result.signature,
}));
}
Ok(result)
}
pub fn variable_length(&self) -> usize {
self.file_name_len as usize + self.extra_field_len as usize
}
#[cfg(feature = "std")]
pub fn write<W>(&self, mut writer: W) -> Result<(), Error>
where
W: Write,
{
let mut buffer = [0u8; 30];
buffer[..4].copy_from_slice(&self.signature.to_le_bytes());
buffer[4..6].copy_from_slice(&self.version_needed.to_le_bytes());
buffer[6..8].copy_from_slice(&self.flags.bits().to_le_bytes());
buffer[8..10].copy_from_slice(&self.compression_method.0.to_le_bytes());
buffer[10..12].copy_from_slice(&self.last_mod_time.to_le_bytes());
buffer[12..14].copy_from_slice(&self.last_mod_date.to_le_bytes());
buffer[14..18].copy_from_slice(&self.crc32.to_le_bytes());
buffer[18..22].copy_from_slice(&self.compressed_size.to_le_bytes());
buffer[22..26].copy_from_slice(&self.uncompressed_size.to_le_bytes());
buffer[26..28].copy_from_slice(&self.file_name_len.to_le_bytes());
buffer[28..30].copy_from_slice(&self.extra_field_len.to_le_bytes());
writer.write_all(&buffer)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub(crate) struct ZipFileHeaderFixed {
pub signature: u32,
pub version_made_by: u16,
pub version_needed: u16,
pub flags: EntryFlags,
pub compression_method: CompressionMethod,
pub last_mod_time: u16,
pub last_mod_date: u16,
pub crc32: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
pub file_name_len: u16,
pub extra_field_len: u16,
pub file_comment_len: u16,
pub disk_number_start: u16,
pub internal_file_attrs: u16,
pub external_file_attrs: u32,
pub local_header_offset: u32,
}
impl ZipFileHeaderFixed {
#[cfg(feature = "std")]
pub fn variable_length(&self) -> usize {
self.file_name_len as usize + self.extra_field_len as usize + self.file_comment_len as usize
}
}
type VariableFields<'a> = (
&'a [u8], &'a [u8], &'a [u8], &'a [u8], );
impl ZipFileHeaderFixed {
pub(crate) const SIZE: usize = 46;
#[inline]
pub fn parse(data: &[u8]) -> Result<ZipFileHeaderFixed, Error> {
if data.len() < Self::SIZE {
return Err(Error::from(ErrorKind::Eof));
}
let result = ZipFileHeaderFixed {
signature: le_u32(&data[0..4]),
version_made_by: le_u16(&data[4..6]),
version_needed: le_u16(&data[6..8]),
flags: EntryFlags::new(le_u16(&data[8..10])),
compression_method: CompressionMethod::new(le_u16(&data[10..12])),
last_mod_time: le_u16(&data[12..14]),
last_mod_date: le_u16(&data[14..16]),
crc32: le_u32(&data[16..20]),
compressed_size: le_u32(&data[20..24]),
uncompressed_size: le_u32(&data[24..28]),
file_name_len: le_u16(&data[28..30]),
extra_field_len: le_u16(&data[30..32]),
file_comment_len: le_u16(&data[32..34]),
disk_number_start: le_u16(&data[34..36]),
internal_file_attrs: le_u16(&data[36..38]),
external_file_attrs: le_u32(&data[38..42]),
local_header_offset: le_u32(&data[42..46]),
};
if result.signature != CENTRAL_HEADER_SIGNATURE {
return Err(Error::from(ErrorKind::InvalidSignature {
expected: CENTRAL_HEADER_SIGNATURE,
actual: result.signature,
}));
}
Ok(result)
}
#[inline]
fn parse_variable_length<'a>(&self, data: &'a [u8]) -> Option<VariableFields<'a>> {
if data.len() < self.file_name_len as usize {
return None;
}
let (file_name, rest) = data.split_at(self.file_name_len as usize);
if rest.len() < self.extra_field_len as usize {
return None;
}
let (extra_field, rest) = rest.split_at(self.extra_field_len as usize);
if rest.len() < self.file_comment_len as usize {
return None;
}
let (file_comment, rest) = rest.split_at(self.file_comment_len as usize);
Some((file_name, extra_field, file_comment, rest))
}
#[cfg(feature = "std")]
pub fn write<W>(&self, mut writer: W) -> Result<(), Error>
where
W: Write,
{
let mut buffer = [0u8; Self::SIZE];
buffer[0..4].copy_from_slice(&self.signature.to_le_bytes());
buffer[4..6].copy_from_slice(&self.version_made_by.to_le_bytes());
buffer[6..8].copy_from_slice(&self.version_needed.to_le_bytes());
buffer[8..10].copy_from_slice(&self.flags.bits().to_le_bytes());
buffer[10..12].copy_from_slice(&self.compression_method.0.to_le_bytes());
buffer[12..14].copy_from_slice(&self.last_mod_time.to_le_bytes());
buffer[14..16].copy_from_slice(&self.last_mod_date.to_le_bytes());
buffer[16..20].copy_from_slice(&self.crc32.to_le_bytes());
buffer[20..24].copy_from_slice(&self.compressed_size.to_le_bytes());
buffer[24..28].copy_from_slice(&self.uncompressed_size.to_le_bytes());
buffer[28..30].copy_from_slice(&self.file_name_len.to_le_bytes());
buffer[30..32].copy_from_slice(&self.extra_field_len.to_le_bytes());
buffer[32..34].copy_from_slice(&self.file_comment_len.to_le_bytes());
buffer[34..36].copy_from_slice(&self.disk_number_start.to_le_bytes());
buffer[36..38].copy_from_slice(&self.internal_file_attrs.to_le_bytes());
buffer[38..42].copy_from_slice(&self.external_file_attrs.to_le_bytes());
buffer[42..46].copy_from_slice(&self.local_header_offset.to_le_bytes());
writer.write_all(&buffer)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compression_method_display() {
assert_eq!(CompressionMethod::DEFLATE.to_string(), "8 (DEFLATE)");
assert_eq!(CompressionMethod::STORE.to_string(), "0 (STORE)");
assert_eq!(CompressionMethod::new(13).to_string(), "13 (UNKNOWN)");
}
#[test]
pub fn trunc_comment_zips() {
let data = [
80, 75, 6, 7, 21, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 10, 0, 59, 59, 80, 75, 5, 6, 0,
255, 255, 255, 255, 255, 255, 0, 0, 0, 80, 75, 6, 6, 0, 0, 0, 10,
];
let archive = ZipArchive::from_slice(data);
assert!(archive.is_err());
}
#[test]
pub fn trunc_eocd64() {
let data = [
80, 75, 6, 7, 21, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 10, 0, 59, 59, 80, 75, 5, 6, 0,
255, 255, 255, 255, 255, 255, 0, 0, 0, 80, 75, 6, 6, 0, 0, 6, 0, 0, 250, 255, 255, 255,
255, 251, 0, 0, 0, 0, 80, 5, 6, 0, 0, 0, 0, 56, 0, 0, 0, 0, 10,
];
let archive = ZipArchive::from_slice(data);
assert!(archive.is_err());
}
#[test]
pub fn trunc_eocd_entry() {
let data = [
80, 75, 1, 2, 159, 159, 159, 159, 159, 159, 159, 159, 159, 0, 241, 205, 0, 80, 75, 5,
6, 0, 48, 249, 0, 250, 255, 255, 255, 255, 251, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
35, 0,
];
let archive = ZipArchive::from_slice(data).unwrap();
let mut entries = archive.entries();
assert!(entries.next_entry().is_err());
}
#[test]
fn data_descriptor_parse_widths_and_signature() {
let mut buf = Vec::new();
buf.extend_from_slice(&DataDescriptor::SIGNATURE.to_le_bytes());
buf.extend_from_slice(&0xdead_beefu32.to_le_bytes()); buf.extend_from_slice(&100u32.to_le_bytes()); buf.extend_from_slice(&200u32.to_le_bytes()); let dd = DataDescriptor::parse(&buf, false).unwrap();
assert_eq!(dd.crc, 0xdead_beef);
assert_eq!(dd.compressed_size, 100);
assert_eq!(dd.uncompressed_size, 200);
let dd = DataDescriptor::parse(&buf[4..], false).unwrap();
assert_eq!(dd.crc, 0xdead_beef);
assert_eq!(dd.compressed_size, 100);
assert_eq!(dd.uncompressed_size, 200);
let mut buf = Vec::new();
buf.extend_from_slice(&DataDescriptor::SIGNATURE.to_le_bytes());
buf.extend_from_slice(&0x0102_0304u32.to_le_bytes()); buf.extend_from_slice(&0x1_0000_0000u64.to_le_bytes()); buf.extend_from_slice(&0x2_0000_0000u64.to_le_bytes()); let dd = DataDescriptor::parse(&buf, true).unwrap();
assert_eq!(dd.crc, 0x0102_0304);
assert_eq!(dd.compressed_size, 0x1_0000_0000);
assert_eq!(dd.uncompressed_size, 0x2_0000_0000);
assert!(DataDescriptor::parse(&buf[..12], true).is_err());
}
#[test]
fn data_descriptor_width_ignores_offset_only_zip64() {
let header = ZipFileHeaderFixed {
signature: 0x0201_4b50,
version_made_by: 0,
version_needed: 0,
flags: EntryFlags::new(0x08),
compression_method: CompressionMethod::STORE,
last_mod_time: 0,
last_mod_date: 0,
crc32: 0,
compressed_size: 100,
uncompressed_size: 200,
file_name_len: 0,
extra_field_len: 12,
file_comment_len: 0,
disk_number_start: 0,
internal_file_attrs: 0,
external_file_attrs: 0,
local_header_offset: u32::MAX,
};
let mut extra = Vec::new();
extra.extend_from_slice(&ExtraFieldId::ZIP64.as_u16().to_le_bytes());
extra.extend_from_slice(&8u16.to_le_bytes());
extra.extend_from_slice(&0x1_0000_0000u64.to_le_bytes());
let record = ZipFileHeaderRecord::from_parts(header, &[], &extra, &[], 0);
assert!(record.is_zip64);
assert_eq!(record.local_header_offset, 0x1_0000_0000);
assert!(!record.data_descriptor_uses_zip64_sizes);
assert!(!record.wayfinder().data_descriptor_uses_zip64_sizes);
}
}