use crate::candidate_error::{CandidateError, CandidateFailure};
use crate::candidate_known_content::{ContentBasis, KnownContent, find_known_content};
use crate::candidate_object::CandidateMode;
use crate::candidate_prediction::{CandidateRequest, PreparedCandidates};
use crate::candidate_store::CandidateStore;
use crate::candidate_version::{Candidate, CandidateVersion};
use crate::direct_fix_install::InstallChange;
use crate::policy_engine::PolicyOutcome;
use crate::repository_facts::RepositoryFacts;
use crate::scanner_adapter::CandidateScanner;
use crate::scanner_failure_code::candidate_failure_code;
use crate::scanner_run::{CandidateBytes, ScanRunError, scan_version_skipping};
use forbidden_strings::CandidateScan;
use std::collections::{BTreeMap, HashSet};
use std::path::Path;
use std::rc::Rc;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum LifecycleContent {
None,
Requested(
CandidateRequest,
),
}
pub struct ContentState {
prepared: Option<Result<PreparedCandidates, CandidateError>>,
corrected: BTreeMap<Vec<u8>, InstallChange>,
proposals: Vec<Correction>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Correction {
pub path: Vec<u8>,
pub mode: CandidateMode,
pub before: Rc<[u8]>,
pub after: Rc<[u8]>,
}
struct OverlaidStore<'a> {
store: &'a mut CandidateStore,
corrected: &'a BTreeMap<Vec<u8>, InstallChange>,
}
impl CandidateBytes for OverlaidStore<'_> {
fn candidate_bytes(&mut self, candidate: &Candidate) -> Result<Rc<[u8]>, CandidateError> {
return overlaid_bytes(self.store, self.corrected, candidate);
}
}
fn overlaid_bytes(
store: &mut CandidateStore,
corrected: &BTreeMap<Vec<u8>, InstallChange>,
candidate: &Candidate,
) -> Result<Rc<[u8]>, CandidateError> {
if let Some(found) = corrected.get(&candidate.path) {
return Ok(Rc::clone(&found.replacement));
}
return store.bytes(candidate);
}
pub fn candidate_failure(error: &CandidateError) -> PolicyOutcome {
return PolicyOutcome::Failed {
code: candidate_failure_code(error.failure),
message: error.message.clone(),
path: None,
};
}
impl ContentState {
pub fn new() -> ContentState {
return ContentState {
prepared: None,
corrected: BTreeMap::new(),
proposals: Vec::new(),
};
}
pub fn prepare<F: RepositoryFacts>(
&mut self,
lifecycle: &LifecycleContent,
facts: &mut F,
) -> Result<Option<Rc<CandidateVersion>>, CandidateError> {
let LifecycleContent::Requested(request) = lifecycle else {
return Ok(None);
};
if self.prepared.is_none() {
self.prepared = Some(facts.candidates(request));
}
match &self.prepared {
Some(Ok(prepared)) => return Ok(Some(Rc::clone(&prepared.version))),
Some(Err(error)) => return Err(error.clone()),
None => return Err(not_prepared_error()),
}
}
pub fn version<F: RepositoryFacts>(
&mut self,
lifecycle: &LifecycleContent,
facts: &mut F,
) -> Result<Option<Rc<CandidateVersion>>, PolicyOutcome> {
match self.prepare(lifecycle, facts) {
Ok(found) => return Ok(found),
Err(error) => return Err(candidate_failure(&error)),
}
}
pub fn bytes(&mut self, candidate: &Candidate) -> Result<Rc<[u8]>, PolicyOutcome> {
match &mut self.prepared {
Some(Ok(prepared)) => {
match overlaid_bytes(&mut prepared.store, &self.corrected, candidate) {
Ok(read) => return Ok(read),
Err(error) => return Err(candidate_failure(&error)),
}
}
Some(Err(_)) | None => return Err(candidate_failure(¬_prepared_error())),
}
}
pub fn text_unset(&self, paths: &[&[u8]]) -> Result<HashSet<Vec<u8>>, PolicyOutcome> {
match &self.prepared {
Some(Ok(prepared)) => match prepared.attributes.text_unset(paths) {
Ok(unset) => return Ok(unset),
Err(error) => return Err(candidate_failure(&error)),
},
Some(Err(_)) | None => return Err(candidate_failure(¬_prepared_error())),
}
}
pub fn real_index(&self) -> Option<&Path> {
match &self.prepared {
Some(Ok(prepared)) => return Some(prepared.real_index.as_path()),
Some(Err(_)) | None => return None,
}
}
}
impl ContentState {
pub fn propose(&mut self, correction: Correction) {
self.proposals.push(correction);
}
pub fn take_proposals(&mut self) -> Vec<Correction> {
return std::mem::take(&mut self.proposals);
}
pub fn apply(&mut self, corrections: Vec<Correction>) {
for correction in corrections {
let original: Rc<[u8]> = match self.corrected.get(&correction.path) {
Some(file) => Rc::clone(&file.original),
None => correction.before,
};
if *original == *correction.after {
self.corrected.remove(&correction.path);
} else {
self.corrected.insert(
correction.path.clone(),
InstallChange {
path: correction.path,
mode: correction.mode,
original,
replacement: correction.after,
},
);
}
}
}
pub fn corrected_state(&self) -> BTreeMap<Vec<u8>, InstallChange> {
return self.corrected.clone();
}
pub fn install_changes(&self) -> Vec<InstallChange> {
return self.corrected.values().cloned().collect();
}
}
impl ContentState {
pub fn scan(
&mut self,
scanner: &CandidateScanner,
rules_paths: &[Vec<u8>],
) -> Result<Vec<CandidateScan>, ScanRunError> {
return self.scan_skipping(scanner, rules_paths, &KnownContent::default());
}
pub fn scan_skipping(
&mut self,
scanner: &CandidateScanner,
rules_paths: &[Vec<u8>],
known: &KnownContent,
) -> Result<Vec<CandidateScan>, ScanRunError> {
match &mut self.prepared {
Some(Ok(prepared)) => {
let mut reader: OverlaidStore<'_> = OverlaidStore {
store: &mut prepared.store,
corrected: &self.corrected,
};
return scan_version_skipping(
scanner,
&mut reader,
&prepared.version,
rules_paths,
known,
);
}
Some(Err(_)) | None => return Err(ScanRunError::Candidate(not_prepared_error())),
}
}
pub fn known_content(
&mut self,
request: &CandidateRequest,
rules_paths: &[Vec<u8>],
) -> Result<KnownContent, CandidateError> {
match &mut self.prepared {
Some(Ok(prepared)) => {
return find_known_content(
&mut prepared.store,
&prepared.version,
&ContentBasis::of(request),
rules_paths,
&self.corrected,
);
}
Some(Err(_)) | None => return Err(not_prepared_error()),
}
}
}
impl ContentState {
pub fn store_with_corrections(
&mut self,
) -> Option<(&mut CandidateStore, &BTreeMap<Vec<u8>, InstallChange>)> {
match &mut self.prepared {
Some(Ok(prepared)) => return Some((&mut prepared.store, &self.corrected)),
Some(Err(_)) | None => return None,
}
}
}
fn not_prepared_error() -> CandidateError {
return CandidateError::new(
CandidateFailure::StaleCandidate,
"cli-git read candidate content before the candidates were prepared; this is a defect in cli-git.",
);
}
impl Default for ContentState {
fn default() -> ContentState {
return ContentState::new();
}
}
#[cfg(test)]
#[path = "policy_content_tests.rs"]
mod tests;