use std::io::{Cursor, Read, Write};
use crate::format::streams::Folder;
use crate::{Error, READ_BUFFER_SIZE, Result, codec};
#[cfg(feature = "aes")]
use super::entries;
use super::{Archive, ExtractionLimits, map_io_error};
impl<R: Read + std::io::Seek> Archive<R> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn decompress_standard_entry(
&mut self,
packed_data: Vec<u8>,
folder: &Folder,
folder_idx: usize,
stream_index: Option<usize>,
entry_size: u64,
sink: &mut impl Write,
limits: &ExtractionLimits,
) -> Result<()> {
if self.is_solid_block(folder_idx) {
self.decompress_to_sink_solid(
packed_data,
folder,
folder_idx,
stream_index.unwrap_or(0),
sink,
limits,
)?;
} else {
self.decompress_to_sink_non_solid(packed_data, folder, entry_size, sink, limits)?;
}
Ok(())
}
pub(crate) fn decompress_to_sink_non_solid(
&self,
packed_data: Vec<u8>,
folder: &Folder,
expected_size: u64,
sink: &mut impl Write,
limits: &ExtractionLimits,
) -> Result<u64> {
if folder.coders.is_empty() {
return Err(Error::InvalidFormat("folder has no coders".into()));
}
let uncompressed_size = folder.final_unpack_size().unwrap_or(expected_size);
let compressed_size = packed_data.len() as u64;
let cursor = Cursor::new(packed_data);
let decoder = self.build_decoder_chain(cursor, folder, uncompressed_size)?;
let mut limited_decoder = limits.wrap_reader(decoder, compressed_size);
let mut total = 0u64;
let mut buf = [0u8; READ_BUFFER_SIZE];
loop {
let n = limited_decoder.read(&mut buf).map_err(map_io_error)?;
if n == 0 {
break;
}
sink.write_all(&buf[..n]).map_err(Error::Io)?;
total += n as u64;
}
Ok(total)
}
pub(crate) fn build_decoder_chain<T: Read + Send + 'static>(
&self,
input: T,
folder: &Folder,
uncompressed_size: u64,
) -> Result<Box<dyn Read + Send>> {
#[cfg(feature = "aes")]
if entries::folder_uses_encryption(folder) {
let password = self.password.as_ref().ok_or(Error::PasswordRequired)?;
let decoder =
codec::build_encrypted_folder_decoder(input, folder, uncompressed_size, password)?;
return Ok(Box::new(decoder));
}
codec::build_decoder_chain(input, folder, uncompressed_size)
}
pub(crate) fn decompress_to_sink_solid(
&self,
packed_data: Vec<u8>,
folder: &Folder,
folder_idx: usize,
stream_index: usize,
sink: &mut impl Write,
limits: &ExtractionLimits,
) -> Result<u64> {
if folder.coders.is_empty() {
return Err(Error::InvalidFormat("folder has no coders".into()));
}
let entry_sizes = self.get_solid_block_entry_sizes(folder_idx)?;
if stream_index >= entry_sizes.len() {
return Err(Error::InvalidFormat(format!(
"stream index {} out of range for solid block",
stream_index
)));
}
let uncompressed_size = folder.final_unpack_size().unwrap_or(0);
let compressed_size = packed_data.len() as u64;
let cursor = Cursor::new(packed_data);
let mut decoder = self.build_decoder_chain(cursor, folder, uncompressed_size)?;
for &skip_size in entry_sizes.iter().take(stream_index) {
let mut remaining = skip_size;
let mut buf = [0u8; READ_BUFFER_SIZE];
while remaining > 0 {
let to_read = buf.len().min(remaining as usize);
let n = decoder.read(&mut buf[..to_read]).map_err(Error::Io)?;
if n == 0 {
break;
}
remaining -= n as u64;
}
}
let target_size = entry_sizes[stream_index];
let mut limited_decoder = limits.wrap_reader(&mut decoder, compressed_size);
let mut remaining = target_size;
let mut total = 0u64;
let mut buf = [0u8; READ_BUFFER_SIZE];
while remaining > 0 {
let to_read = buf.len().min(remaining as usize);
let n = limited_decoder
.read(&mut buf[..to_read])
.map_err(map_io_error)?;
if n == 0 {
break;
}
sink.write_all(&buf[..n]).map_err(Error::Io)?;
total += n as u64;
remaining -= n as u64;
}
Ok(total)
}
}