Codex Handbook
execpolicy-legacy/src/program.rs 247 lines
use serde::Serialize;use std::collections::HashMap;use std::collections::HashSet;use crate::ArgType;use crate::ExecCall;use crate::arg_matcher::ArgMatcher;use crate::arg_resolver::PositionalArg;use crate::arg_resolver::resolve_observed_args_with_patterns;use crate::error::Error;use crate::error::Result;use crate::opt::Opt;use crate::opt::OptMeta;use crate::valid_exec::MatchedFlag;use crate::valid_exec::MatchedOpt;use crate::valid_exec::ValidExec;#[derive(Debug)]pub struct ProgramSpec {    pub program: String,    pub system_path: Vec<String>,    pub option_bundling: bool,    pub combined_format: bool,    pub allowed_options: HashMap<String, Opt>,    pub arg_patterns: Vec<ArgMatcher>,    forbidden: Option<String>,    required_options: HashSet<String>,    should_match: Vec<Vec<String>>,    should_not_match: Vec<Vec<String>>,}impl ProgramSpec {    pub fn new(        program: String,        system_path: Vec<String>,        option_bundling: bool,        combined_format: bool,        allowed_options: HashMap<String, Opt>,        arg_patterns: Vec<ArgMatcher>,        forbidden: Option<String>,        should_match: Vec<Vec<String>>,        should_not_match: Vec<Vec<String>>,    ) -> Self {        let required_options = allowed_options            .iter()            .filter_map(|(name, opt)| {                if opt.required {                    Some(name.clone())                } else {                    None                }            })            .collect();        Self {            program,            system_path,            option_bundling,            combined_format,            allowed_options,            arg_patterns,            forbidden,            required_options,            should_match,            should_not_match,        }    }}#[derive(Clone, Debug, Eq, PartialEq, Serialize)]pub enum MatchedExec {    Match { exec: ValidExec },    Forbidden { cause: Forbidden, reason: String },}#[derive(Clone, Debug, Eq, PartialEq, Serialize)]pub enum Forbidden {    Program {        program: String,        exec_call: ExecCall,    },    Arg {        arg: String,        exec_call: ExecCall,    },    Exec {        exec: ValidExec,    },}impl ProgramSpec {    // TODO(mbolin): The idea is that there should be a set of rules defined for    // a program and the args should be checked against the rules to determine    // if the program should be allowed to run.    pub fn check(&self, exec_call: &ExecCall) -> Result<MatchedExec> {        let mut expecting_option_value: Option<(String, ArgType)> = None;        let mut args = Vec::<PositionalArg>::new();        let mut matched_flags = Vec::<MatchedFlag>::new();        let mut matched_opts = Vec::<MatchedOpt>::new();        for (index, arg) in exec_call.args.iter().enumerate() {            if let Some(expected) = expecting_option_value {                // If we are expecting an option value, then the next argument                // should be the value for the option.                // This had better not be another option!                let (name, arg_type) = expected;                if arg.starts_with("-") {                    return Err(Error::OptionFollowedByOptionInsteadOfValue {                        program: self.program.clone(),                        option: name,                        value: arg.clone(),                    });                }                matched_opts.push(MatchedOpt::new(&name, arg, arg_type)?);                expecting_option_value = None;            } else if arg == "--" {                return Err(Error::DoubleDashNotSupportedYet {                    program: self.program.clone(),                });            } else if arg.starts_with("-") {                match self.allowed_options.get(arg) {                    Some(opt) => {                        match &opt.meta {                            OptMeta::Flag => {                                matched_flags.push(MatchedFlag { name: arg.clone() });                                // A flag does not expect an argument: continue.                                continue;                            }                            OptMeta::Value(arg_type) => {                                expecting_option_value = Some((arg.clone(), arg_type.clone()));                                continue;                            }                        }                    }                    None => {                        // It could be an --option=value style flag...                    }                }                return Err(Error::UnknownOption {                    program: self.program.clone(),                    option: arg.clone(),                });            } else {                args.push(PositionalArg {                    index,                    value: arg.clone(),                });            }        }        if let Some(expected) = expecting_option_value {            let (name, _arg_type) = expected;            return Err(Error::OptionMissingValue {                program: self.program.clone(),                option: name,            });        }        let matched_args =            resolve_observed_args_with_patterns(&self.program, args, &self.arg_patterns)?;        // Verify all required options are present.        let matched_opt_names: HashSet<String> = matched_opts            .iter()            .map(|opt| opt.name().to_string())            .collect();        if !matched_opt_names.is_superset(&self.required_options) {            let mut options = self                .required_options                .difference(&matched_opt_names)                .map(String::from)                .collect::<Vec<_>>();            options.sort();            return Err(Error::MissingRequiredOptions {                program: self.program.clone(),                options,            });        }        let exec = ValidExec {            program: self.program.clone(),            flags: matched_flags,            opts: matched_opts,            args: matched_args,            system_path: self.system_path.clone(),        };        match &self.forbidden {            Some(reason) => Ok(MatchedExec::Forbidden {                cause: Forbidden::Exec { exec },                reason: reason.clone(),            }),            None => Ok(MatchedExec::Match { exec }),        }    }    pub fn verify_should_match_list(&self) -> Vec<PositiveExampleFailedCheck> {        let mut violations = Vec::new();        for good in &self.should_match {            let exec_call = ExecCall {                program: self.program.clone(),                args: good.clone(),            };            match self.check(&exec_call) {                Ok(_) => {}                Err(error) => {                    violations.push(PositiveExampleFailedCheck {                        program: self.program.clone(),                        args: good.clone(),                        error,                    });                }            }        }        violations    }    pub fn verify_should_not_match_list(&self) -> Vec<NegativeExamplePassedCheck> {        let mut violations = Vec::new();        for bad in &self.should_not_match {            let exec_call = ExecCall {                program: self.program.clone(),                args: bad.clone(),            };            if self.check(&exec_call).is_ok() {                violations.push(NegativeExamplePassedCheck {                    program: self.program.clone(),                    args: bad.clone(),                });            }        }        violations    }}#[derive(Debug, Eq, PartialEq)]pub struct PositiveExampleFailedCheck {    pub program: String,    pub args: Vec<String>,    pub error: Error,}#[derive(Debug, Eq, PartialEq)]pub struct NegativeExamplePassedCheck {    pub program: String,    pub args: Vec<String>,}