use std::{
io::{self, Read},
path::PathBuf,
sync::Arc,
};
use crate::archives::{StoredArchive, TypedArchive};
#[cfg(all(feature = "zip", not(test)))]
const MAX_BUFFERED_ZIP_ENTRY_SIZE: u64 = 512 * 1024 * 1024;
#[cfg(all(feature = "zip", test))]
const MAX_BUFFERED_ZIP_ENTRY_SIZE: u64 = 64;
#[derive(Debug, Clone)]
pub struct ArchiveReference {
pub(super) path: PathBuf,
pub(super) raw_path: Vec<u8>,
#[cfg(feature = "zip")]
pub(super) zip_index: Option<usize>,
pub(super) parent_archive: Arc<StoredArchive>,
}
impl ArchiveReference {
pub(super) fn new(path: &str, parent_archive: Arc<StoredArchive>) -> Self {
Self {
path: PathBuf::from(path),
raw_path: path.as_bytes().to_vec(),
#[cfg(feature = "zip")]
zip_index: None,
parent_archive,
}
}
#[cfg(feature = "zip")]
pub(super) fn new_zip(
path: &str,
zip_index: usize,
parent_archive: Arc<StoredArchive>,
) -> Self {
Self {
path: PathBuf::from(path),
raw_path: path.as_bytes().to_vec(),
zip_index: Some(zip_index),
parent_archive,
}
}
pub(super) fn from_bytes(path: &[u8], parent_archive: Arc<StoredArchive>) -> Self {
let display_path = String::from_utf8_lossy(path).into_owned();
Self {
path: PathBuf::from(display_path),
raw_path: path.to_vec(),
#[cfg(feature = "zip")]
zip_index: None,
parent_archive,
}
}
}
pub(super) fn open(archive_ref: &ArchiveReference) -> io::Result<Box<dyn Read + '_>> {
let parent = archive_ref.parent_archive.handle();
match parent {
#[cfg(feature = "beth-archives")]
TypedArchive::Bethesda(archive) => {
let reader = archive
.open_file_required(&archive_ref.raw_path)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(reader)
}
#[cfg(feature = "zip")]
TypedArchive::Zip(archive) => {
let mut guard = archive
.lock()
.map_err(|_| io::Error::other("zip mutex poisoned"))?;
let buf = {
let Some(zip_index) = archive_ref.zip_index else {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"zip archive reference is missing central-directory index",
));
};
let entry = guard
.by_index(zip_index)
.map_err(|e| io::Error::new(io::ErrorKind::NotFound, e.to_string()))?;
if entry.size() > MAX_BUFFERED_ZIP_ENTRY_SIZE {
return Err(io::Error::new(
io::ErrorKind::OutOfMemory,
format!(
"zip entry '{}' is {} bytes, exceeding the buffered entry limit of {} bytes",
entry.name(),
entry.size(),
MAX_BUFFERED_ZIP_ENTRY_SIZE
),
));
}
let capacity = usize::try_from(entry.size()).map_err(|_| {
io::Error::new(
io::ErrorKind::OutOfMemory,
format!("zip entry '{}' is too large to buffer", entry.name()),
)
})?;
let mut buf = Vec::with_capacity(capacity);
let mut limited_entry = entry.take(MAX_BUFFERED_ZIP_ENTRY_SIZE + 1);
limited_entry.read_to_end(&mut buf)?;
if u64::try_from(buf.len()).unwrap_or(u64::MAX) > MAX_BUFFERED_ZIP_ENTRY_SIZE {
return Err(io::Error::new(
io::ErrorKind::OutOfMemory,
format!(
"zip entry '{}' exceeded the buffered entry limit of {} bytes while reading",
archive_ref.path.display(),
MAX_BUFFERED_ZIP_ENTRY_SIZE
),
));
}
buf
};
Ok(Box::new(std::io::Cursor::new(buf)))
}
}
}