use crate::CompressionMethod;
use crate::DateTime;
use crate::HasZipMetadata;
use crate::ZIP64_BYTES_THR;
use crate::ZipReadOptions;
use crate::format::data_descriptor::ZipDataDescriptor;
use crate::format::ffi;
use crate::format::flags::ZipFileFlags;
use crate::format::system::System;
use crate::read::ExtraField;
use crate::read::extract::RootDirFilter;
use crate::read::readers::{ZipFileReader, ZipFileSeekReader};
use crate::result::ZipResult;
use crate::result::invalid;
use crate::types::ZipFileData;
use crate::write::options::SimpleFileOptions;
use core::mem::replace;
use std::borrow::Cow;
use std::ffi::OsStr;
use std::io::{self, Cursor, Read, Seek, SeekFrom, copy, sink};
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct ZipFile<'a, R: Read + ?Sized> {
pub(crate) file_name_raw: Cow<'a, [u8]>,
pub(crate) data: Cow<'a, ZipFileData>,
pub(crate) reader: ZipFileReader<'a, R>,
}
#[derive(Debug)]
pub struct ZipFileEntryWithDataDescriptor<'a> {
pub(crate) file_name_raw: Cow<'a, [u8]>,
pub(crate) data: Cow<'a, ZipFileData>,
pub(crate) data_descriptor: Option<ZipDataDescriptor>,
}
#[derive(Debug)]
pub struct ZipFileEntry<'a> {
pub(crate) file_name_raw: Cow<'a, [u8]>,
pub(crate) data: Cow<'a, ZipFileData>,
}
pub struct ZipFileSeek<'a, R> {
pub(crate) file_name_raw: Cow<'a, [u8]>,
pub(crate) data: Cow<'a, ZipFileData>,
pub(crate) reader: ZipFileSeekReader<'a, R>,
}
macro_rules! zip_file_methods {
() => {
pub fn name_raw(&self) -> &[u8] {
&self.file_name_raw
}
pub fn name(&self) -> ZipResult<Cow<'_, str>> {
self.get_metadata().name(&self.file_name_raw)
}
pub fn mangled_name(&self) -> ZipResult<PathBuf> {
let file_name = self.name()?;
let sanitized = self.get_metadata().file_name_sanitized(&file_name);
Ok(sanitized)
}
pub fn enclosed_name(&self) -> Option<PathBuf> {
self.get_metadata().enclosed_name(&self.name().ok()?)
}
pub fn header_start(&self) -> u64 {
self.get_metadata().header_start
}
pub fn central_header_start(&self) -> u64 {
self.get_metadata().central_header_start
}
pub fn version_made_by(&self) -> (u8, u8) {
(
self.get_metadata().version_made_by / 10,
self.get_metadata().version_made_by % 10,
)
}
pub fn version_needed(&self) -> u16 {
self.get_metadata().version_needed()
}
pub fn comment(&self) -> &str {
&self.get_metadata().file_comment
}
pub fn system(&self) -> System {
self.get_metadata().system
}
pub fn external_attributes(&self) -> u32 {
self.get_metadata().external_attributes
}
pub fn flags(&self) -> ZipFileFlags {
self.get_metadata().flags
}
pub fn compression(&self) -> CompressionMethod {
self.get_metadata().compression_method
}
pub fn encrypted(&self) -> bool {
self.data.is_encrypted()
}
pub fn compressed_size(&self) -> u64 {
self.get_metadata().compressed_size
}
pub fn size(&self) -> u64 {
self.get_metadata().uncompressed_size
}
pub fn last_modified(&self) -> Option<DateTime> {
self.get_metadata().last_modified_time
}
pub fn is_dir(&self) -> bool {
self.get_metadata().is_dir(&self.file_name_raw)
}
pub fn is_symlink(&self) -> bool {
self.unix_mode()
.is_some_and(|mode| mode & ffi::S_IFLNK == ffi::S_IFLNK)
}
pub fn is_file(&self) -> bool {
!self.is_dir() && !self.is_symlink()
}
pub fn is_read_only(&self) -> bool {
self.unix_mode().is_some_and(|mode| mode & 0o222 == 0)
}
pub fn unix_mode(&self) -> Option<u32> {
self.get_metadata().unix_mode()
}
pub fn crc32(&self) -> u32 {
self.get_metadata().crc32
}
};
}
impl<'a> ZipFileEntryWithDataDescriptor<'a> {
zip_file_methods!();
pub fn data_descriptor(&self) -> &Option<ZipDataDescriptor> {
&self.data_descriptor
}
}
impl HasZipMetadata for ZipFileEntryWithDataDescriptor<'_> {
fn get_metadata(&self) -> &ZipFileData {
self.data.as_ref()
}
}
impl<'a> ZipFileEntry<'a> {
zip_file_methods!();
pub fn with_reader<R: Read + Seek>(
self,
reader: &'a mut R,
options: ZipReadOptions<'_>,
) -> ZipResult<ZipFile<'a, R>> {
let limit_reader = self.data.find_content(reader)?;
let reader = self.data.make_reader(limit_reader, options)?;
Ok(ZipFile {
file_name_raw: self.file_name_raw,
data: self.data,
reader,
})
}
}
impl HasZipMetadata for ZipFileEntry<'_> {
fn get_metadata(&self) -> &ZipFileData {
self.data.as_ref()
}
}
impl<'a, R: Read + ?Sized> ZipFile<'a, R> {
zip_file_methods!();
pub(crate) fn take_raw_reader(&mut self) -> io::Result<io::Take<&'a mut R>> {
replace(&mut self.reader, ZipFileReader::NoReader).into_inner()
}
pub(crate) fn safe_prepare_path(
&self,
base_path: &Path,
outpath: &mut PathBuf,
root_dir: Option<&(Vec<&OsStr>, impl RootDirFilter)>,
) -> ZipResult<()> {
let file_name = self.name()?;
let components = self
.data
.simplified_components(&file_name)
.ok_or(invalid!("Invalid file path"))?;
let components = match root_dir {
Some((root_dir, filter)) => match components.strip_prefix(&**root_dir) {
Some(components) => components,
None => {
debug_assert!(
!filter(&PathBuf::from_iter(components.iter())),
"Root directory filter should not match at this point"
);
&components[..]
}
},
None => &components[..],
};
*outpath = crate::path::resolve_enclosed(
base_path,
base_path,
components.iter().map(|c| c.to_os_string()),
true, )?;
Ok(())
}
pub fn extra_data(&self) -> Option<Vec<u8>> {
let out_buffer = Vec::new();
let mut cursor = Cursor::new(out_buffer);
let extra_fields = self.data.extra_fields.local_extra_fields();
for one_extra_field in extra_fields {
one_extra_field.write(&mut cursor, true).ok()?;
}
let extra_fields_data = cursor.into_inner();
if extra_fields_data.is_empty() {
None
} else {
Some(extra_fields_data)
}
}
pub fn data_start(&self) -> Option<u64> {
self.data.data_start.get().copied()
}
pub fn options(&self) -> SimpleFileOptions {
let mut options = SimpleFileOptions::default()
.large_file(self.compressed_size().max(self.size()) >= ZIP64_BYTES_THR)
.compression_method(self.compression())
.system(self.system())
.external_attributes(self.external_attributes())
.last_modified_time(
self.last_modified()
.filter(DateTime::is_valid)
.unwrap_or_else(DateTime::default_for_write),
);
options.normalize();
#[cfg(feature = "aes-crypto")]
if let Some((mode, vendor_version)) = self.get_metadata().aes_settings() {
use crate::write::options::EncryptWith;
options.encrypt_with = Some(EncryptWith::Aes {
mode,
vendor_version,
salt: None,
password: None,
});
}
options
}
}
impl<R: Read> ZipFile<'_, R> {
pub fn extra_data_fields(&self) -> impl Iterator<Item = &ExtraField> {
self.data.extra_fields.inner.iter()
}
}
impl<R: Read + ?Sized> HasZipMetadata for ZipFile<'_, R> {
fn get_metadata(&self) -> &ZipFileData {
self.data.as_ref()
}
}
impl<R: Read + ?Sized> Read for ZipFile<'_, R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.reader.read(buf)
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
self.reader.read_to_end(buf)
}
fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
self.reader.read_to_string(buf)
}
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
self.reader.read_exact(buf)
}
}
impl<R: Read> Read for ZipFileSeek<'_, R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match &mut self.reader {
ZipFileSeekReader::Raw(r) => r.read(buf),
}
}
}
impl<R: Seek> Seek for ZipFileSeek<'_, R> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
match &mut self.reader {
ZipFileSeekReader::Raw(r) => r.seek(pos),
}
}
}
impl<R> HasZipMetadata for ZipFileSeek<'_, R> {
fn get_metadata(&self) -> &ZipFileData {
self.data.as_ref()
}
}
impl<'a, R> ZipFileSeek<'a, R> {
zip_file_methods!();
}
impl<R: Read + ?Sized> Drop for ZipFile<'_, R> {
fn drop(&mut self) {
if let Cow::Owned(_) = self.data {
if let Ok(mut inner) = self.take_raw_reader() {
let _ = copy(&mut inner, &mut sink());
}
}
}
}