use std::io::{self, Read};
use compcol::{Algorithm, Decoder, Status};
use super::scan::{CabMethod, CfData, Folder};
use crate::block::BlockDevice;
use crate::{Error, Result};
pub fn decode_folder_reader<'a>(
dev: &'a mut dyn BlockDevice,
folder: &Folder,
) -> Result<Box<dyn Read + 'a>> {
let blocks = folder.blocks.clone();
match folder.method {
CabMethod::None => Ok(Box::new(PayloadReader::new(dev, blocks))),
CabMethod::MsZip => Ok(Box::new(MsZipReader {
payloads: PayloadReader::new(dev, blocks),
dec: compcol::deflate::Decoder::new(),
started: false,
out: Vec::new(),
pos: 0,
})),
CabMethod::Lzx { window_bits } => {
let total = u32::try_from(folder.total_uncomp)
.map_err(|_| Error::Unsupported("cab: LZX folder larger than 4 GiB".into()))?;
let mut header = Vec::with_capacity(5);
header.push(window_bits as u8);
header.extend_from_slice(&total.to_le_bytes());
let framed = io::Cursor::new(header).chain(PayloadReader::new(dev, blocks));
Ok(Box::new(compcol::io::DecoderReader::new(
framed,
compcol::lzx::Lzx::decoder(),
)))
}
CabMethod::Quantum { window_bits } => {
let dec = compcol::quantum::Decoder::with_window_bits(window_bits)
.map_err(|e| Error::InvalidImage(format!("cab: bad Quantum window: {e}")))?;
Ok(Box::new(compcol::io::DecoderReader::new(
PayloadReader::new(dev, blocks),
dec,
)))
}
CabMethod::Unsupported(id) => Err(Error::Unsupported(format!(
"cab: compression method {id} not supported"
))),
}
}
pub fn skip_exact(r: &mut dyn Read, mut n: u64) -> Result<()> {
let mut scratch = [0u8; 64 * 1024];
while n > 0 {
let want = n.min(scratch.len() as u64) as usize;
let got = r.read(&mut scratch[..want]).map_err(crate::Error::from)?;
if got == 0 {
return Err(Error::InvalidImage(
"cab: folder stream ended before the file offset".into(),
));
}
n -= got as u64;
}
Ok(())
}
struct PayloadReader<'a> {
dev: &'a mut dyn BlockDevice,
blocks: Vec<CfData>,
idx: usize,
buf: Vec<u8>,
pos: usize,
}
impl<'a> PayloadReader<'a> {
fn new(dev: &'a mut dyn BlockDevice, blocks: Vec<CfData>) -> Self {
Self {
dev,
blocks,
idx: 0,
buf: Vec::new(),
pos: 0,
}
}
}
impl Read for PayloadReader<'_> {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
while self.pos >= self.buf.len() {
if self.idx >= self.blocks.len() {
return Ok(0);
}
let b = self.blocks[self.idx];
self.idx += 1;
self.buf = vec![0u8; b.comp_len as usize];
self.dev
.read_at(b.offset, &mut self.buf)
.map_err(io::Error::other)?;
self.pos = 0;
}
let n = (self.buf.len() - self.pos).min(out.len());
out[..n].copy_from_slice(&self.buf[self.pos..self.pos + n]);
self.pos += n;
Ok(n)
}
}
struct MsZipReader<'a> {
payloads: PayloadReader<'a>,
dec: compcol::deflate::Decoder,
started: bool,
out: Vec<u8>,
pos: usize,
}
impl MsZipReader<'_> {
fn next_block(&mut self) -> io::Result<Option<Vec<u8>>> {
let p = &mut self.payloads;
if p.idx >= p.blocks.len() {
return Ok(None);
}
let b = p.blocks[p.idx];
p.idx += 1;
let mut payload = vec![0u8; b.comp_len as usize];
p.dev
.read_at(b.offset, &mut payload)
.map_err(io::Error::other)?;
Ok(Some(payload))
}
}
impl Read for MsZipReader<'_> {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
while self.pos >= self.out.len() {
let Some(payload) = self.next_block()? else {
return Ok(0);
};
if payload.len() < 2 || &payload[0..2] != b"CK" {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"cab: MSZIP block missing 'CK' signature",
));
}
if self.started {
self.dec.reset_keep_window();
}
self.started = true;
self.out.clear();
self.pos = 0;
decode_deflate_block(&mut self.dec, &payload[2..], &mut self.out)?;
}
let n = (self.out.len() - self.pos).min(out.len());
out[..n].copy_from_slice(&self.out[self.pos..self.pos + n]);
self.pos += n;
Ok(n)
}
}
fn decode_deflate_block(
dec: &mut compcol::deflate::Decoder,
input: &[u8],
out: &mut Vec<u8>,
) -> io::Result<()> {
let mut scratch = vec![0u8; 64 * 1024];
let mut consumed = 0usize;
let err = |e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("cab: MSZIP decode: {e}"),
)
};
loop {
let (p, status) = dec.decode(&input[consumed..], &mut scratch).map_err(err)?;
out.extend_from_slice(&scratch[..p.written]);
consumed += p.consumed;
match status {
Status::StreamEnd => return Ok(()),
Status::OutputFull => continue,
Status::InputEmpty => break,
}
}
loop {
let (p, status) = dec.finish(&mut scratch).map_err(err)?;
out.extend_from_slice(&scratch[..p.written]);
if matches!(status, Status::StreamEnd) || p.written == 0 {
break;
}
}
Ok(())
}