use anyhow::{anyhow, Result};
use forbidden_regex::RegexSet;
use std::fs;
use crate::{compile_rules, load_precompiled};
pub(crate) struct ScanSet {
pub(crate) set: RegexSet,
pub(crate) base: usize,
pub(crate) names: Vec<Option<String>>,
}
pub struct LoadedRules {
sets: Vec<ScanSet>,
}
impl LoadedRules {
pub(crate) fn iter_sets(&self) -> impl Iterator<Item = &ScanSet> {
return self.sets.iter()
}
}
fn parse_builtin_names(text: &str, expected: usize) -> Result<Vec<Option<String>>> {
let names: Vec<Option<String>> = text
.lines()
.map(|line| return (!line.is_empty()).then(|| return line.to_string()))
.collect();
if names.len() != expected {
return Err(anyhow!(
"builtin baseline: name sidecar holds {} entries for {} rules",
names.len(),
expected,
));
}
return Ok(names)
}
pub fn load(
rules_path: &str,
builtin_rules: bool,
explicit: bool,
precompiled: &[u8],
builtin_names: &str,
) -> Result<LoadedRules> {
let mut sets: Vec<ScanSet> = Vec::new();
let user_rules = match fs::read_to_string(rules_path) {
Ok(text) => Some(
compile_rules(&text)
.map_err(|reason| return anyhow!("rules {}: {}", rules_path, reason))?,
),
Err(error)
if builtin_rules
&& !explicit
&& error.kind() == std::io::ErrorKind::NotFound =>
{
None
}
Err(error) => return Err(anyhow!("read rules {}: {}", rules_path, error)),
};
let mut next_base = 0;
if let Some(compiled) = user_rules {
next_base = compiled.set.len();
sets.push(ScanSet { set: compiled.set, base: 0, names: compiled.names });
}
if builtin_rules {
let set = load_precompiled(precompiled)
.map_err(|reason| return anyhow!("builtin baseline: {}", reason))?;
let names = parse_builtin_names(builtin_names, set.len())?;
let builtin_set: std::collections::HashSet<&str> =
names.iter().flatten().map(|name| return name.as_str()).collect();
for user_name in sets.iter().flat_map(|scan_set| return scan_set.names.iter().flatten()) {
if builtin_set.contains(user_name.as_str()) {
return Err(anyhow!(
"rules {}: rule name '{}' collides with a builtin baseline rule name",
rules_path,
user_name,
));
}
}
sets.push(ScanSet { set, base: next_base, names });
}
return Ok(LoadedRules { sets })
}
#[cfg(feature = "fuzzing")]
pub fn load_from_text(text: &str) -> std::result::Result<LoadedRules, crate::LoadError> {
let compiled = compile_rules(text)?;
return Ok(LoadedRules {
sets: vec![ScanSet { set: compiled.set, base: 0, names: compiled.names }],
})
}
#[cfg(test)]
#[path = "frx_load_tests.rs"]
mod tests;