use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use crate::format::header::detect_sfx;
use crate::format::parser::ArchiveHeader;
#[cfg(not(feature = "aes"))]
use crate::format::parser::read_archive_header_with_offset;
#[cfg(feature = "aes")]
use crate::format::parser::read_archive_header_with_offset_and_password;
use crate::format::streams::ResourceLimits;
use crate::safety::LimitedReader;
use crate::{Error, Result};
#[cfg(feature = "aes")]
use crate::Password;
use super::multivolume::{detect_multivolume_base, open_multivolume_as_single};
use super::{Archive, ArchiveInfo, Entry, entries};
pub(crate) struct OpenResult<R> {
pub reader: R,
pub header: ArchiveHeader,
pub entries: Vec<Entry>,
pub info: ArchiveInfo,
pub sfx_offset: u64,
}
pub(crate) struct ExtractionLimits {
pub max_entry_bytes: u64,
pub max_total_bytes: u64,
pub max_ratio: Option<u32>,
pub total_tracker: Arc<AtomicU64>,
}
impl ExtractionLimits {
pub fn from_resource_limits(limits: &ResourceLimits) -> Self {
Self {
max_entry_bytes: limits.max_entry_unpacked,
max_total_bytes: limits.max_total_unpacked,
max_ratio: limits.ratio_limit.as_ref().map(|r| r.max_ratio),
total_tracker: Arc::new(AtomicU64::new(0)),
}
}
pub fn wrap_reader<RD: Read>(&self, reader: RD, compressed_size: u64) -> LimitedReader<RD> {
let mut limited = LimitedReader::new(reader)
.max_entry_bytes(self.max_entry_bytes)
.compressed_size(compressed_size)
.total_tracker(self.total_tracker.clone(), self.max_total_bytes);
if let Some(ratio) = self.max_ratio {
limited = limited.max_ratio(ratio);
}
limited
}
pub fn unlimited() -> Self {
Self {
max_entry_bytes: u64::MAX,
max_total_bytes: u64::MAX,
max_ratio: None,
total_tracker: Arc::new(AtomicU64::new(0)),
}
}
}
pub(crate) fn map_io_error(e: std::io::Error) -> Error {
if let Some(inner) = e.get_ref() {
if let Some(Error::ResourceLimitExceeded(msg)) = inner.downcast_ref::<Error>() {
return Error::ResourceLimitExceeded(msg.clone());
}
}
if e.kind() == std::io::ErrorKind::Other {
let msg = e.to_string();
if msg.contains("exceeds limit") || msg.contains("Compression ratio") {
return Error::ResourceLimitExceeded(msg);
}
}
Error::Io(e)
}
impl Archive<BufReader<File>> {
pub fn open_path(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
if let Some(base_path) = detect_multivolume_base(path) {
return open_multivolume_as_single(&base_path);
}
let file = File::open(path).map_err(Error::Io)?;
let reader = BufReader::new(file);
Self::open(reader)
}
pub fn open_path_with_limits(path: impl AsRef<Path>, limits: ResourceLimits) -> Result<Self> {
let file = File::open(path.as_ref()).map_err(Error::Io)?;
let reader = BufReader::new(file);
Self::open_with_limits(reader, limits)
}
#[cfg(feature = "aes")]
pub fn open_path_with_password(
path: impl AsRef<Path>,
password: impl Into<Password>,
) -> Result<Self> {
let file = File::open(path.as_ref()).map_err(Error::Io)?;
let reader = BufReader::new(file);
Self::open_with_password(reader, password)
}
}
impl<R: Read + Seek> Archive<R> {
pub fn open(reader: R) -> Result<Self> {
Self::open_internal(reader, None, None)
}
pub fn open_with_limits(reader: R, limits: ResourceLimits) -> Result<Self> {
Self::open_internal(reader, None, Some(limits))
}
#[cfg(feature = "aes")]
pub fn open_with_password(reader: R, password: impl Into<Password>) -> Result<Self> {
Self::open_internal(reader, Some(password.into()), None)
}
#[cfg(feature = "aes")]
pub fn open_with_password_and_limits(
reader: R,
password: impl Into<Password>,
limits: ResourceLimits,
) -> Result<Self> {
Self::open_internal(reader, Some(password.into()), Some(limits))
}
#[cfg(feature = "aes")]
fn open_common(
mut reader: R,
password: Option<Password>,
limits: Option<ResourceLimits>,
) -> Result<OpenResult<R>> {
let sfx_offset = match detect_sfx(&mut reader)? {
Some(sfx_info) => {
reader
.seek(SeekFrom::Start(sfx_info.archive_offset))
.map_err(Error::Io)?;
sfx_info.archive_offset
}
None => 0,
};
let limits = limits.unwrap_or_default();
let (_start_header, header) = read_archive_header_with_offset_and_password(
&mut reader,
Some(limits),
sfx_offset,
password,
)?;
let entries = entries::build_entries(&header);
let info = entries::build_info(&header, &entries);
Ok(OpenResult {
reader,
header,
entries,
info,
sfx_offset,
})
}
#[cfg(not(feature = "aes"))]
fn open_common(
mut reader: R,
_password: Option<()>,
limits: Option<ResourceLimits>,
) -> Result<OpenResult<R>> {
let sfx_offset = match detect_sfx(&mut reader)? {
Some(sfx_info) => {
reader
.seek(SeekFrom::Start(sfx_info.archive_offset))
.map_err(Error::Io)?;
sfx_info.archive_offset
}
None => 0,
};
let limits = limits.unwrap_or_default();
let (_start_header, header) =
read_archive_header_with_offset(&mut reader, Some(limits), sfx_offset)?;
let entries = entries::build_entries(&header);
let info = entries::build_info(&header, &entries);
Ok(OpenResult {
reader,
header,
entries,
info,
sfx_offset,
})
}
#[cfg(feature = "aes")]
fn open_internal(
reader: R,
password: Option<Password>,
limits: Option<ResourceLimits>,
) -> Result<Self> {
let result = Self::open_common(reader, password.clone(), limits)?;
Ok(Self {
reader: result.reader,
header: result.header,
entries: result.entries,
info: result.info,
password,
volume_info: None,
sfx_offset: result.sfx_offset,
})
}
#[cfg(not(feature = "aes"))]
fn open_internal(
reader: R,
_password: Option<()>,
limits: Option<ResourceLimits>,
) -> Result<Self> {
let result = Self::open_common(reader, None, limits)?;
Ok(Self {
reader: result.reader,
header: result.header,
entries: result.entries,
info: result.info,
volume_info: None,
sfx_offset: result.sfx_offset,
})
}
}