use crate::crc::crc32_chunk;
use crate::errors::{Error, ErrorKind};
use crate::extra_fields::{ExtraFieldId, ExtraFields};
use crate::mode::{
msdos_mode_to_file_mode, unix_mode_to_file_mode, EntryMode, CREATOR_FAT, CREATOR_MACOS,
CREATOR_NTFS, CREATOR_UNIX, CREATOR_VFAT,
};
use crate::path::{RawPath, ZipFilePath};
use crate::reader_at::{FileReader, MutexReader, RangeReader, ReaderAt, ReaderAtExt};
use crate::time::{extract_best_timestamp, ZipDateTimeKind};
use crate::utils::{le_u16, le_u32, le_u64};
use crate::{EndOfCentralDirectory, EndOfCentralDirectoryRecordFixed, ZipLocator};
use std::io::{Read, Seek, Write};
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;
#[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 as_bytes(&self) -> &[u8] {
self.data.as_ref()
}
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])
}
#[deprecated(note = "Use `ZipSliceArchive::into_zip_archive` instead")]
pub fn into_reader(self) -> ZipArchive<T> {
ZipArchive {
reader: self.data,
eocd: self.eocd,
}
}
pub fn into_zip_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, rest) = header.split_at(total_size as usize);
let expected_crc = if entry.has_data_descriptor {
DataDescriptor::parse(rest)?.crc
} else {
entry.crc
};
Ok(ZipSliceEntry {
data: entire_entry,
verifier: ZipVerification {
crc: expected_crc,
uncompressed_size: entry.uncompressed_size_hint(),
},
local_header_offset: entry.local_header_offset,
data_start_offset: header_size,
})
}
}
#[derive(Debug, Clone)]
pub struct ZipSliceEntry<'a> {
data: &'a [u8],
verifier: ZipVerification,
local_header_offset: u64,
data_start_offset: u32,
}
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 verifying_reader<D>(&self, reader: D) -> ZipSliceVerifier<D>
where
D: std::io::Read,
{
ZipSliceVerifier {
reader,
verifier: self.verifier,
crc: 0,
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 extra_fields(&self) -> ExtraFields<'_> {
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 extra_field_start = ZipLocalFileHeaderFixed::SIZE + file_name_len;
let extra_field_end = extra_field_start + extra_field_len;
ExtraFields::new(&self.data[extra_field_start..extra_field_end])
}
pub fn file_path(&self) -> ZipFilePath<RawPath<'_>> {
let header =
ZipLocalFileHeaderFixed::parse(self.data).expect("header has already been parsed");
let file_name_len = header.file_name_len as usize;
let filename_start = ZipLocalFileHeaderFixed::SIZE;
let filename_end = filename_start + file_name_len;
ZipFilePath::from_bytes(&self.data[filename_start..filename_end])
}
}
#[derive(Debug, Clone)]
pub struct ZipSliceVerifier<D> {
reader: D,
crc: u32,
size: u64,
verifier: ZipVerification,
}
impl<D> ZipSliceVerifier<D> {
pub fn into_inner(self) -> D {
self.reader
}
}
impl<D> std::io::Read for ZipSliceVerifier<D>
where
D: std::io::Read,
{
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let read = self.reader.read(buf)?;
self.crc = crc32_chunk(&buf[..read], self.crc);
self.size += read as u64;
if read == 0 || self.size >= self.verifier.size() {
self.verifier
.valid(ZipVerification {
crc: self.crc,
uncompressed_size: self.size,
})
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
}
Ok(read)
}
}
#[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()
}
}
#[derive(Debug, Clone)]
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)
}
pub fn from_file(
file: std::fs::File,
buffer: &mut [u8],
) -> Result<ZipArchive<FileReader>, Error> {
ZipLocator::new()
.locate_in_file(file, buffer)
.map_err(|(_, e)| e)
}
pub fn from_seekable<R>(
mut reader: R,
buffer: &mut [u8],
) -> Result<ZipArchive<MutexReader<R>>, Error>
where
R: Read + Seek,
{
let end_offset = reader.seek(std::io::SeekFrom::End(0))?;
let reader = MutexReader::new(reader);
ZipLocator::new()
.locate_in_reader(reader, buffer, end_offset)
.map_err(|(_, e)| e)
}
}
impl<R> ZipArchive<R> {
pub(crate) fn new(reader: R, eocd: EndOfCentralDirectory) -> Self {
ZipArchive { reader, eocd }
}
pub fn get_ref(&self) -> &R {
&self.reader
}
pub fn into_inner(self) -> R {
self.reader
}
pub fn entries<'archive, 'buf>(
&'archive self,
buffer: &'buf mut [u8],
) -> ZipEntries<'archive, 'buf, R> {
ZipEntries {
buffer,
archive: self,
pos: 0,
end: 0,
offset: self.eocd.directory_offset(),
base_offset: self.eocd.base_offset(),
central_dir_end_pos: self.eocd.head_eocd_offset(),
}
}
pub fn entries_hint(&self) -> u64 {
self.eocd.entries()
}
pub fn comment(&self) -> RangeReader<&R> {
let comment_start =
self.eocd.tail_eocd_offset() + EndOfCentralDirectoryRecordFixed::SIZE as u64;
let comment_end = comment_start + self.eocd.comment_len() as u64;
RangeReader::new(&self.reader, comment_start..comment_end)
}
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().remaining()
}
}
impl<R> ZipArchive<R>
where
R: ReaderAt,
{
pub fn get_entry(&self, entry: ZipArchiveEntryWayfinder) -> Result<ZipEntry<'_, R>, Error> {
let mut buffer = [0u8; ZipLocalFileHeaderFixed::SIZE];
self.reader
.read_exact_at(&mut buffer, entry.local_header_offset)?;
let file_header = ZipLocalFileHeaderFixed::parse(&buffer)?;
let (body_offset, o1) = entry
.local_header_offset
.overflowing_add(ZipLocalFileHeaderFixed::SIZE as u64);
let (body_offset, o2) = body_offset.overflowing_add(file_header.variable_length() as u64);
let (body_end_offset, o3) = body_offset.overflowing_add(entry.compressed_size);
if o1 || o2 || o3 {
return Err(Error::from(ErrorKind::Eof));
}
Ok(ZipEntry {
archive: self,
entry,
body_offset,
body_end_offset,
})
}
}
#[derive(Debug, Clone)]
pub struct ZipEntry<'archive, R> {
archive: &'archive ZipArchive<R>,
body_offset: u64,
body_end_offset: u64,
entry: ZipArchiveEntryWayfinder,
}
impl<'archive, R> ZipEntry<'archive, R>
where
R: ReaderAt,
{
pub fn reader(&self) -> ZipReader<&'archive R> {
ZipReader {
entry: self.entry,
range_reader: RangeReader::new(
self.archive.get_ref(),
self.body_offset..self.body_end_offset,
),
}
}
pub fn verifying_reader<D>(&self, reader: D) -> ZipVerifier<D, &'archive R>
where
D: std::io::Read,
{
ZipVerifier {
reader,
crc: 0,
size: 0,
archive: self.archive.get_ref(),
end_offset: self.body_end_offset,
wayfinder: self.entry,
}
}
pub fn compressed_data_range(&self) -> (u64, u64) {
(self.body_offset, self.body_end_offset)
}
pub fn local_header<'a>(&self, buffer: &'a mut [u8]) -> Result<ZipLocalFileHeader<'a>, Error> {
let mut header_buffer = [0u8; ZipLocalFileHeaderFixed::SIZE];
self.archive
.get_ref()
.read_exact_at(&mut header_buffer, self.entry.local_header_offset)?;
let local_header_fixed =
ZipLocalFileHeaderFixed::parse(&header_buffer).expect("header has already been parsed");
let file_name_len = local_header_fixed.file_name_len as usize;
let extra_field_len = local_header_fixed.extra_field_len as usize;
let total_variable_len = file_name_len + extra_field_len;
if buffer.len() < total_variable_len {
return Err(Error::from(ErrorKind::BufferTooSmall));
}
let variable_data = &mut buffer[..total_variable_len];
let variable_data_offset =
self.entry.local_header_offset + ZipLocalFileHeaderFixed::SIZE as u64;
self.archive
.get_ref()
.read_exact_at(variable_data, variable_data_offset)?;
let (filename_data, extra_field_data) = variable_data.split_at(file_name_len);
Ok(ZipLocalFileHeader {
file_path: ZipFilePath::from_bytes(filename_data),
extra_fields: ExtraFields::new(extra_field_data),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ZipVerification {
pub crc: u32,
pub uncompressed_size: u64,
}
impl ZipVerification {
pub fn crc(&self) -> u32 {
self.crc
}
pub fn size(&self) -> u64 {
self.uncompressed_size
}
pub fn valid(&self, rhs: ZipVerification) -> Result<(), Error> {
if self.size() != rhs.size() {
return Err(Error::from(ErrorKind::InvalidSize {
expected: self.size(),
actual: rhs.size(),
}));
}
if self.crc() != 0 && self.crc() != rhs.crc() {
return Err(Error::from(ErrorKind::InvalidChecksum {
expected: self.crc(),
actual: rhs.crc(),
}));
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ZipVerifier<Decompressor, ReaderAt> {
reader: Decompressor,
crc: u32,
size: u64,
archive: ReaderAt,
end_offset: u64,
wayfinder: ZipArchiveEntryWayfinder,
}
impl<Decompressor, ReaderAt> ZipVerifier<Decompressor, ReaderAt> {
pub fn into_inner(self) -> Decompressor {
self.reader
}
}
impl<Decompressor, Reader> std::io::Read for ZipVerifier<Decompressor, Reader>
where
Decompressor: std::io::Read,
Reader: ReaderAt,
{
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let read = self.reader.read(buf)?;
self.crc = crc32_chunk(&buf[..read], self.crc);
self.size += read as u64;
if read == 0 || self.size >= self.wayfinder.uncompressed_size_hint() {
let crc = if self.wayfinder.has_data_descriptor {
DataDescriptor::read_at(&self.archive, self.end_offset).map(|x| x.crc)
} else {
Ok(self.wayfinder.crc)
};
crc.and_then(|crc| {
let expected = ZipVerification {
crc,
uncompressed_size: self.wayfinder.uncompressed_size_hint(),
};
expected.valid(ZipVerification {
crc: self.crc,
uncompressed_size: self.size,
})
})
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
}
Ok(read)
}
}
#[derive(Debug, Clone)]
pub struct ZipReader<R> {
entry: ZipArchiveEntryWayfinder,
range_reader: RangeReader<R>,
}
impl<R> ZipReader<R>
where
R: ReaderAt,
{
pub fn claim_verifier(self) -> Result<ZipVerification, Error> {
let expected_size = self.entry.uncompressed_size_hint();
let expected_crc = if self.entry.has_data_descriptor {
let end_offset = self.range_reader.end_offset();
let archive = self.range_reader.into_inner();
DataDescriptor::read_at(archive, end_offset).map(|x| x.crc)?
} else {
self.entry.crc
};
Ok(ZipVerification {
crc: expected_crc,
uncompressed_size: expected_size,
})
}
}
impl<R> Read for ZipReader<R>
where
R: ReaderAt,
{
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.range_reader.read(buf)
}
}
#[derive(Debug)]
pub struct ZipLocalFileHeader<'a> {
file_path: ZipFilePath<RawPath<'a>>,
extra_fields: ExtraFields<'a>,
}
impl<'a> ZipLocalFileHeader<'a> {
pub fn file_path(&self) -> ZipFilePath<RawPath<'a>> {
self.file_path
}
pub fn extra_fields(&self) -> ExtraFields<'a> {
self.extra_fields
}
}
#[derive(Debug, Clone)]
pub(crate) struct DataDescriptor {
crc: u32,
}
impl DataDescriptor {
const SIZE: usize = 8;
pub const SIGNATURE: u32 = 0x08074b50;
fn parse(data: &[u8]) -> Result<DataDescriptor, Error> {
if data.len() < Self::SIZE {
return Err(Error::from(ErrorKind::Eof));
}
let mut pos = 0;
let potential_signature = le_u32(&data[0..4]);
if potential_signature == Self::SIGNATURE {
pos += 4;
}
Ok(DataDescriptor {
crc: le_u32(&data[pos..pos + 4]),
})
}
fn read_at<R>(reader: R, offset: u64) -> Result<DataDescriptor, Error>
where
R: ReaderAt,
{
let mut buffer = [0u8; Self::SIZE];
reader.read_exact_at(&mut buffer, offset)?;
Self::parse(&buffer)
}
}
#[derive(Debug)]
pub struct ZipEntries<'archive, 'buf, R> {
buffer: &'buf mut [u8],
archive: &'archive ZipArchive<R>,
pos: usize,
end: usize,
offset: u64,
base_offset: u64,
central_dir_end_pos: u64,
}
impl<R> ZipEntries<'_, '_, R>
where
R: ReaderAt,
{
#[inline]
pub fn next_entry(&mut self) -> Result<Option<ZipFileHeaderRecord<'_>>, Error> {
if self.pos + ZipFileHeaderFixed::SIZE >= self.end {
if self.offset >= self.central_dir_end_pos {
return Ok(None);
}
let remaining = self.end - self.pos;
self.buffer.copy_within(self.pos..self.end, 0);
let max_read = ((self.central_dir_end_pos - self.offset) as usize)
.min(self.buffer.len() - remaining);
let read = self.archive.reader.read_at_least_at(
&mut self.buffer[remaining..][..max_read],
ZipFileHeaderFixed::SIZE,
self.offset,
)?;
self.offset += read as u64;
self.pos = 0;
self.end = remaining + read;
}
let central_directory_offset = self.offset - (self.end - self.pos) as u64;
let data = &self.buffer[self.pos..self.end];
let file_header = ZipFileHeaderFixed::parse(data)?;
self.pos += ZipFileHeaderFixed::SIZE;
let variable_length = file_header.variable_length();
if self.pos + variable_length > self.end {
let remaining = self.end - self.pos;
self.buffer.copy_within(self.pos..self.end, 0);
let max_read = ((self.central_dir_end_pos - self.offset) as usize)
.min(self.buffer.len() - remaining);
let read = self.archive.reader.read_at_least_at(
&mut self.buffer[remaining..][..max_read],
variable_length - remaining,
self.offset,
)?;
self.offset += read as u64;
self.pos = 0;
self.end = remaining + read;
}
let data = &self.buffer[self.pos..self.end];
let (file_name, extra_field, file_comment, _) = file_header
.parse_variable_length(data)
.expect("variable length precheck failed");
let mut file_header = ZipFileHeaderRecord::from_parts(
file_header,
file_name,
extra_field,
file_comment,
central_directory_offset,
);
file_header.local_header_offset += self.base_offset;
self.pos += variable_length;
Ok(Some(file_header))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct VersionMadeBy(u16);
#[allow(dead_code)]
impl VersionMadeBy {
pub fn as_u16(&self) -> u16 {
self.0
}
pub fn version(&self) -> (u8, u8) {
let v = (self.0 >> 8) as u8;
(v / 10, v % 10)
}
}
#[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(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(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompressionMethodId(u16);
impl CompressionMethodId {
#[inline]
pub fn as_u16(&self) -> u16 {
self.0
}
#[inline]
pub fn as_method(&self) -> CompressionMethod {
match self.0 {
0 => CompressionMethod::Store,
1 => CompressionMethod::Shrunk,
2 => CompressionMethod::Reduce1,
3 => CompressionMethod::Reduce2,
4 => CompressionMethod::Reduce3,
5 => CompressionMethod::Reduce4,
6 => CompressionMethod::Imploded,
7 => CompressionMethod::Tokenizing,
8 => CompressionMethod::Deflate,
9 => CompressionMethod::Deflate64,
10 => CompressionMethod::Terse,
12 => CompressionMethod::Bzip2,
14 => CompressionMethod::Lzma,
18 => CompressionMethod::Lz77,
20 => CompressionMethod::ZstdDeprecated,
93 => CompressionMethod::Zstd,
94 => CompressionMethod::Mp3,
95 => CompressionMethod::Xz,
96 => CompressionMethod::Jpeg,
97 => CompressionMethod::WavPack,
98 => CompressionMethod::Ppmd,
99 => CompressionMethod::Aes,
_ => CompressionMethod::Unknown(self.0),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum CompressionMethod {
Store = 0,
Shrunk = 1,
Reduce1 = 2,
Reduce2 = 3,
Reduce3 = 4,
Reduce4 = 5,
Imploded = 6,
Tokenizing = 7,
Deflate = 8,
Deflate64 = 9,
Terse = 10,
Bzip2 = 12,
Lzma = 14,
Lz77 = 18,
ZstdDeprecated = 20,
Zstd = 93,
Mp3 = 94,
Xz = 95,
Jpeg = 96,
WavPack = 97,
Ppmd = 98,
Aes = 99,
Unknown(u16),
}
impl CompressionMethod {
#[inline]
pub fn as_id(&self) -> CompressionMethodId {
let value = match self {
CompressionMethod::Store => 0,
CompressionMethod::Shrunk => 1,
CompressionMethod::Reduce1 => 2,
CompressionMethod::Reduce2 => 3,
CompressionMethod::Reduce3 => 4,
CompressionMethod::Reduce4 => 5,
CompressionMethod::Imploded => 6,
CompressionMethod::Tokenizing => 7,
CompressionMethod::Deflate => 8,
CompressionMethod::Deflate64 => 9,
CompressionMethod::Terse => 10,
CompressionMethod::Bzip2 => 12,
CompressionMethod::Lzma => 14,
CompressionMethod::Lz77 => 18,
CompressionMethod::ZstdDeprecated => 20,
CompressionMethod::Zstd => 93,
CompressionMethod::Mp3 => 94,
CompressionMethod::Xz => 95,
CompressionMethod::Jpeg => 96,
CompressionMethod::WavPack => 97,
CompressionMethod::Ppmd => 98,
CompressionMethod::Aes => 99,
CompressionMethod::Unknown(id) => *id,
};
CompressionMethodId(value)
}
}
impl From<u16> for CompressionMethod {
fn from(id: u16) -> Self {
CompressionMethodId(id).as_method()
}
}
#[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
}
#[inline]
pub fn into_owned(&self) -> ZipString {
ZipString::new(self.0.to_vec())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ZipString(Vec<u8>);
impl ZipString {
#[inline]
pub fn new(data: 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: u16,
compression_method: CompressionMethodId,
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,
}
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 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,
};
if result.uncompressed_size != u64::from(u32::MAX)
&& result.compressed_size != u64::from(u32::MAX)
&& 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 has_data_descriptor(&self) -> bool {
self.flags & 0x08 != 0
}
#[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.has_data_descriptor(),
crc: self.crc32,
}
}
#[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.as_method()
}
#[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 mode(&self) -> EntryMode {
let creator_version = self.version_made_by >> 8;
let mut mode = match creator_version {
CREATOR_UNIX | CREATOR_MACOS => unix_mode_to_file_mode(self.external_file_attrs >> 16),
CREATOR_NTFS | CREATOR_VFAT | CREATOR_FAT => {
msdos_mode_to_file_mode(self.external_file_attrs)
}
_ => 0o644,
};
if self.is_dir() {
mode |= 0o040000; }
EntryMode::new(mode)
}
#[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)
}
}
#[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,
}
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)]
pub(crate) struct ZipLocalFileHeaderFixed {
pub(crate) signature: u32,
pub(crate) version_needed: u16,
pub(crate) flags: u16,
pub(crate) compression_method: CompressionMethodId,
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: le_u16(&data[6..8]),
compression_method: CompressionMethodId(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
}
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.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: u16,
pub compression_method: CompressionMethodId,
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 {
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: le_u16(&data[8..10]),
compression_method: CompressionMethodId(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))
}
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.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::*;
use std::io::Cursor;
#[test]
pub fn blank_zip_archive() {
let data = [80, 75, 5, 6];
let mut buf = vec![0u8; RECOMMENDED_BUFFER_SIZE];
let archive = ZipArchive::from_seekable(Cursor::new(data), &mut buf);
assert!(archive.is_err());
}
#[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 mut buf = vec![0u8; RECOMMENDED_BUFFER_SIZE];
let archive = ZipArchive::from_seekable(Cursor::new(data), &mut buf);
assert!(archive.is_err());
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());
let mut buf = vec![0u8; RECOMMENDED_BUFFER_SIZE];
let archive = ZipArchive::from_seekable(Cursor::new(data), &mut buf);
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());
let mut buf = vec![0u8; RECOMMENDED_BUFFER_SIZE];
let archive = ZipArchive::from_seekable(Cursor::new(data), &mut buf).unwrap();
let mut entries = archive.entries(&mut buf);
assert!(entries.next_entry().is_err());
}
#[test]
fn test_compressed_data_range() {
let test_zip = std::fs::read("assets/test.zip").unwrap();
let slice_archive = ZipArchive::from_slice(&test_zip).unwrap();
let slice_header_records: Vec<_> = slice_archive
.entries()
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(slice_header_records.len(), 2);
let entry1_wayfinder = slice_header_records[0].wayfinder();
let slice_entry1 = slice_archive.get_entry(entry1_wayfinder).unwrap();
let slice_range1 = slice_entry1.compressed_data_range();
assert_eq!(
slice_range1,
(66, 91),
"test.txt compressed data should be at bytes 66-91"
);
let entry2_wayfinder = slice_header_records[1].wayfinder();
let slice_entry2 = slice_archive.get_entry(entry2_wayfinder).unwrap();
let slice_range2 = slice_entry2.compressed_data_range();
assert_eq!(
slice_range2,
(169, 954),
"gophercolor16x16.png compressed data should be at bytes 169-954"
);
let file = std::fs::File::open("assets/test.zip").unwrap();
let mut buffer = vec![0u8; RECOMMENDED_BUFFER_SIZE];
let reader_archive = ZipArchive::from_file(file, &mut buffer).unwrap();
let reader_entry1 = reader_archive.get_entry(entry1_wayfinder).unwrap();
let reader_range1 = reader_entry1.compressed_data_range();
let reader_entry2 = reader_archive.get_entry(entry2_wayfinder).unwrap();
let reader_range2 = reader_entry2.compressed_data_range();
assert_eq!(slice_range1, reader_range1);
assert_eq!(slice_range2, reader_range2);
}
}