use crate::CompressionMethod;
use crate::DateTime;
use crate::ExtraField;
use crate::HasZipMetadata;
use crate::ZIP64_BYTES_THR;
use crate::read::RootDirFilter;
use crate::read::make_writable_dir_all;
use crate::read::readers::{ZipFileReader, ZipFileSeekReader};
use crate::result::ZipResult;
use crate::result::invalid;
use crate::types::ZipFileData;
use crate::types::{SimpleFileOptions, ffi};
use core::mem::replace;
use std::borrow::Cow;
use std::ffi::OsStr;
use std::io::{self, Read, Seek, SeekFrom, copy, sink};
use std::path::{Component, 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>,
}
pub struct ZipFileSeek<'a, R> {
pub(crate) data: Cow<'a, ZipFileData>,
pub(crate) reader: ZipFileSeekReader<'a, R>,
}
impl<'a, R: Read + ?Sized> ZipFile<'a, R> {
pub(crate) fn take_raw_reader(&mut self) -> io::Result<io::Take<&'a mut R>> {
replace(&mut self.reader, ZipFileReader::NoReader).into_inner()
}
pub fn version_made_by(&self) -> (u8, u8) {
(
self.get_metadata().version_made_by / 10,
self.get_metadata().version_made_by % 10,
)
}
pub fn name(&self) -> ZipResult<Cow<'_, str>> {
self.data.name(&self.file_name_raw)
}
pub fn name_raw(&self) -> &[u8] {
&self.file_name_raw
}
pub fn mangled_name(&self) -> ZipResult<PathBuf> {
let file_name = self.name()?;
let sanitized = self.data.file_name_sanitized(&file_name);
Ok(sanitized)
}
pub fn enclosed_name(&self) -> Option<PathBuf> {
self.data.enclosed_name(&self.name().ok()?)
}
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[..],
};
let components_len = components.len();
for (is_last, component) in components
.iter()
.enumerate()
.map(|(i, c)| (i == components_len - 1, c))
{
outpath.push(component);
for limit in (0..5u8).rev() {
let meta = match std::fs::symlink_metadata(&outpath) {
Ok(meta) => meta,
Err(e) if e.kind() == io::ErrorKind::NotFound => {
if !is_last {
make_writable_dir_all(&outpath)?;
}
break;
}
Err(e) => return Err(e.into()),
};
if !meta.is_symlink() {
break;
}
if limit == 0 {
return Err(invalid!("Extraction followed a symlink too deep"));
}
let target = std::fs::read_link(&outpath)?;
if !crate::path::simplified_components(&target)
.ok_or(invalid!("Invalid symlink target path"))?
.starts_with(
&crate::path::simplified_components(base_path)
.ok_or(invalid!("Invalid base path"))?,
)
{
let is_absolute_enclosed = base_path
.components()
.map(Some)
.chain(std::iter::once(None))
.zip(target.components().map(Some).chain(std::iter::repeat(None)))
.all(|(a, b)| match (a, b) {
(Some(Component::Normal(a)), Some(Component::Normal(b))) => a == b,
(None, None) => true,
(Some(_), None) => false,
(None, Some(Component::CurDir | Component::Normal(_))) => true,
_ => false,
});
if !is_absolute_enclosed {
return Err(invalid!("Symlink is not inherently safe"));
}
}
outpath.push(target);
}
}
Ok(())
}
pub fn comment(&self) -> &str {
&self.get_metadata().file_comment
}
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.data.last_modified_time
}
pub fn is_dir(&self) -> bool {
self.data.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 unix_mode(&self) -> Option<u32> {
self.get_metadata().unix_mode()
}
pub fn crc32(&self) -> u32 {
self.get_metadata().crc32
}
pub fn extra_data(&self) -> Option<&[u8]> {
self.get_metadata().extra_field.as_deref()
}
pub fn data_start(&self) -> Option<u64> {
self.data.data_start.get().copied()
}
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 options(&self) -> SimpleFileOptions {
let mut options = SimpleFileOptions::default()
.large_file(self.compressed_size().max(self.size()) > ZIP64_BYTES_THR)
.compression_method(self.compression())
.unix_permissions(self.unix_mode().unwrap_or(0o644) | ffi::S_IFREG)
.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_mode {
use crate::types::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.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<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());
}
}
}
}