use super::admit::RefusalScope;
use super::admit::block_charge;
use super::admit::sevenz_refusals;
use super::decode::decode_block_entries;
use super::decode::is_sevenz_payload;
use super::header::BlockIndex;
use super::header::SevenZHeader;
use super::links::BlockScan;
use super::links::BlockStop;
use super::links::LinkCapture;
use super::lzma2::DecodeKnobs;
use crate::Result;
use crate::formats::common;
use crate::inspection::integrity::DecodeFailure;
use crate::inspection::integrity::DecodeOutcome;
use crate::inspection::integrity::IntegrityPass;
use crate::inspection::integrity::Slot;
use crate::inspection::report::UnverifiedReason;
use std::collections::BTreeSet;
use std::io::Read;
use std::io::Seek;
use std::path::Path;
use std::path::PathBuf;
pub(super) type PayloadFile = (usize, Slot);
pub(super) fn payload_by_block(
header: &SevenZHeader,
slots: &[Option<Slot>],
) -> Vec<Vec<PayloadFile>> {
let mut blocks = vec![Vec::new(); header.block_count()];
for (index, file) in header.files().iter().enumerate() {
let block = header.file_block(index);
if let (Some(block), Some(Some(slot)), true) =
(block, slots.get(index), is_sevenz_payload(file))
{
blocks[block.get()].push((index, *slot));
}
}
blocks
}
pub fn verify_sevenz_blocks<R: Read + Seek>(
header: &SevenZHeader,
source: &mut R,
pass: &mut IntegrityPass<'_>,
slots: &[Option<Slot>],
mut links: Option<LinkCapture<'_>>,
) -> Result<()> {
let payload = payload_by_block(header, slots);
let mut refused = vec![None; header.block_count()];
for refusal in sevenz_refusals(header, pass.config()) {
match refusal.scope {
RefusalScope::Archive => {
let indices = || payload.iter().flatten().map(|&(index, _)| index);
if let Some(capture) = links.as_mut() {
capture.mark_unreadable(header, indices());
}
mark_files(pass, header, refusal.unverified(), indices());
return Ok(());
}
RefusalScope::Block(index) => refused[index.get()] = Some(refusal.unverified()),
}
}
let decode = DecodeContext {
slots,
knobs: DecodeKnobs::production(),
};
for (block_index, files) in header.block_indices().zip(&payload) {
if files.is_empty() {
continue;
}
if let Some(reason) = refused[block_index.get()] {
let indices = || files.iter().map(|&(index, _)| index);
if let Some(capture) = links.as_mut() {
capture.mark_unreadable(header, indices());
}
mark_files(pass, header, reason, indices());
} else {
let capture = links.as_mut().map(|capture| LinkCapture {
links: capture.links,
set: &mut *capture.set,
});
verify_sevenz_block(header, source, pass, decode, block_index, files, capture)?;
}
}
Ok(())
}
pub(super) fn file_path(header: &SevenZHeader, index: usize) -> PathBuf {
PathBuf::from(common::normalize_entry_name(&header.files()[index].name))
}
pub(super) fn mark_files(
pass: &mut IntegrityPass<'_>,
header: &SevenZHeader,
reason: UnverifiedReason,
indices: impl Iterator<Item = usize>,
) {
for index in indices {
pass.mark_unverified(reason, &file_path(header, index));
}
}
pub(super) fn record_block_failure(
pass: &mut IntegrityPass<'_>,
header: &SevenZHeader,
lead: (&Path, Slot),
failure: DecodeFailure,
others: &[usize],
) -> Result<()> {
let reason = match failure {
DecodeFailure::ResourceLimit(_) => UnverifiedReason::ResourceLimit,
_ => UnverifiedReason::NotDecodable,
};
pass.record(lead.1, lead.0, DecodeOutcome::Failed(failure))?;
mark_files(pass, header, reason, others.iter().copied());
Ok(())
}
pub(super) fn entries_uncovered_by_block_crc(
block_has_crc: bool,
entry_sizes: u64,
block_size: u64,
entries: impl Iterator<Item = (usize, bool)>,
) -> Vec<usize> {
if !block_has_crc || entry_sizes == block_size {
return Vec::new();
}
entries
.filter(|&(_, has_crc)| !has_crc)
.map(|(index, _)| index)
.collect()
}
#[derive(Clone, Copy)]
pub(super) struct DecodeContext<'a> {
slots: &'a [Option<Slot>],
knobs: DecodeKnobs,
}
pub(super) fn verify_sevenz_block<R: Read + Seek>(
header: &SevenZHeader,
source: &mut R,
pass: &mut IntegrityPass<'_>,
decode: DecodeContext<'_>,
block_index: BlockIndex,
payload: &[PayloadFile],
links: Option<LinkCapture<'_>>,
) -> Result<()> {
let DecodeContext { slots, knobs } = decode;
let block = header.block(block_index);
let all = payload.iter().map(|&(index, _)| index);
if payload.iter().any(|&(_, slot)| !pass.is_admitted(slot)) {
mark_files(pass, header, UnverifiedReason::ResourceLimit, all);
return Ok(());
}
if !pass.try_charge(block_charge(block)) {
mark_files(pass, header, UnverifiedReason::OverBudget, all);
return Ok(());
}
let first = header.block_first_file(block_index);
let mut scan = BlockScan {
pass,
slots,
links: links.map(|capture| LinkCapture {
links: capture.links,
set: &mut *capture.set,
}),
next_file: first,
visited: BTreeSet::new(),
stop: None,
};
let parallel = scan.pass.config().parallel_decode;
let result = decode_block_entries(
source,
header,
block_index,
parallel,
knobs,
&mut |file, reader| Ok(scan.visit(file, reader)),
);
let (stop, visited) = (scan.stop.take(), scan.visited);
let unvisited: Vec<usize> = all.filter(|index| !visited.contains(index)).collect();
match (stop, result) {
(Some(BlockStop::Fatal(e)), _) => return Err(e),
(Some(BlockStop::Abandoned(reason)), _) => {
mark_files(pass, header, reason, unvisited.into_iter());
}
(None, Err(e)) => {
let &(index, slot) = payload
.iter()
.rev()
.find(|(index, _)| visited.contains(index))
.unwrap_or(&payload[0]);
let path = file_path(header, index);
let others: Vec<usize> = unvisited.into_iter().filter(|&i| i != index).collect();
return record_block_failure(
pass,
header,
(path.as_path(), slot),
DecodeFailure::from_sevenz(e),
&others,
);
}
(None, Ok(_)) => {
mark_files(
pass,
header,
UnverifiedReason::NotDecodable,
unvisited.into_iter(),
);
let entry_sizes = payload
.iter()
.map(|&(index, _)| header.files()[index].size)
.fold(0, u64::saturating_add);
let own_crcs = payload
.iter()
.filter(|(index, _)| visited.contains(index))
.map(|&(index, _)| (index, header.files()[index].has_crc));
let uncovered = entries_uncovered_by_block_crc(
block.has_crc,
entry_sizes,
block.get_unpack_size(),
own_crcs,
);
mark_files(
pass,
header,
UnverifiedReason::UncoveredBlockCrc,
uncovered.into_iter(),
);
}
}
Ok(())
}