Skip to main content

verifyos_cli/core/
engine.rs

1use crate::parsers::plist_reader::{InfoPlist, PlistError};
2use crate::parsers::zip_extractor::{extract_ipa, ExtractionError};
3use crate::rules::core::{
4    AppStoreRule, ArtifactContext, RuleCategory, RuleError, RuleReport, Severity,
5};
6use std::path::Path;
7
8#[derive(Debug, thiserror::Error)]
9pub enum OrchestratorError {
10    #[error("Extraction failed: {0}")]
11    Extraction(#[from] ExtractionError),
12    #[error("Failed to parse Info.plist: {0}")]
13    PlistParse(#[from] PlistError),
14    #[error("Could not locate App Bundle (.app) inside IPAPayload")]
15    AppBundleNotFound,
16}
17
18pub struct EngineResult {
19    pub rule_id: &'static str,
20    pub rule_name: &'static str,
21    pub category: RuleCategory,
22    pub severity: Severity,
23    pub recommendation: &'static str,
24    pub report: Result<RuleReport, RuleError>,
25}
26
27pub struct Engine {
28    rules: Vec<Box<dyn AppStoreRule>>,
29}
30
31impl Engine {
32    pub fn new() -> Self {
33        Self { rules: Vec::new() }
34    }
35
36    pub fn register_rule(&mut self, rule: Box<dyn AppStoreRule>) {
37        self.rules.push(rule);
38    }
39
40    pub fn run<P: AsRef<Path>>(&self, ipa_path: P) -> Result<Vec<EngineResult>, OrchestratorError> {
41        let extracted_ipa = extract_ipa(ipa_path)?;
42
43        let app_bundle_path = extracted_ipa
44            .get_app_bundle_path()
45            .map_err(|e| OrchestratorError::Extraction(ExtractionError::Io(e)))?
46            .ok_or(OrchestratorError::AppBundleNotFound)?;
47
48        let info_plist_path = app_bundle_path.join("Info.plist");
49        let info_plist = if info_plist_path.exists() {
50            Some(InfoPlist::from_file(&info_plist_path)?)
51        } else {
52            None
53        };
54
55        let context = ArtifactContext {
56            app_bundle_path: &app_bundle_path,
57            info_plist: info_plist.as_ref(),
58        };
59
60        let mut results = Vec::new();
61
62        for rule in &self.rules {
63            let res = rule.evaluate(&context);
64            results.push(EngineResult {
65                rule_id: rule.id(),
66                rule_name: rule.name(),
67                category: rule.category(),
68                severity: rule.severity(),
69                recommendation: rule.recommendation(),
70                report: res,
71            });
72        }
73
74        Ok(results)
75    }
76}
77
78impl Default for Engine {
79    fn default() -> Self {
80        Self::new()
81    }
82}