use boreal::compiler::{CompilerProfile, ExternalValue};
use clap::parser::Values;
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
#[derive(Debug)]
pub struct CompilerOptions {
pub profile: Option<CompilerProfile>,
pub compute_statistics: bool,
pub max_strings_per_rule: Option<usize>,
pub max_condition_depth: Option<u32>,
pub parse_expr_recursion_limit: Option<u8>,
pub parse_string_recursion_limit: Option<u8>,
pub disable_includes: bool,
pub defines: Option<Values<(String, ExternalValue)>>,
pub rules_files: Vec<String>,
}
impl CompilerOptions {
pub fn from_args(args: &mut ArgMatches, rules_files: Option<Vec<String>>) -> Self {
let rules_files =
rules_files.unwrap_or_else(|| match args.remove_many::<String>("rules_file") {
Some(v) => v.into_iter().collect(),
None => Vec::new(),
});
Self {
profile: args.remove_one::<CompilerProfile>("profile"),
compute_statistics: args.get_flag("string_statistics"),
max_strings_per_rule: args.remove_one::<usize>("max_strings_per_rule"),
max_condition_depth: args.remove_one::<u32>("max_condition_depth"),
parse_expr_recursion_limit: args.remove_one::<u8>("parse_expr_recursion_limit"),
parse_string_recursion_limit: args.remove_one::<u8>("parse_string_recursion_limit"),
disable_includes: args.get_flag("disable_includes"),
defines: args.remove_many::<(String, ExternalValue)>("define"),
rules_files,
}
}
}
pub fn add_compiler_args(mut command: Command, in_yr_subcommand: bool) -> Command {
command = command
.next_help_heading("Compilation options")
.arg(
Arg::new("profile")
.long("profile")
.value_name("speed|memory")
.value_parser(parse_compiler_profile)
.help("Profile to use when compiling rules"),
)
.arg(
Arg::new("string_statistics")
.long("string-stats")
.action(ArgAction::SetTrue)
.help("Display statistics on rules' compilation"),
)
.arg(
Arg::new("max_strings_per_rule")
.long("max-strings-per-rule")
.value_name("NUMBER")
.value_parser(value_parser!(usize))
.help("Maximum number of strings in a single rule"),
)
.arg(
Arg::new("max_condition_depth")
.long("max-condition-depth")
.value_name("NUMBER")
.value_parser(value_parser!(u32))
.help("Maximum depth in a rule's condition AST")
.long_help(
"Maximum depth in a rule's condition AST.\n\n\
Defensive limit used to prevent stack overflow.\n\
Should this limit be reached when compiling rules, this parameter \
can be used to modify it.",
),
)
.arg(
Arg::new("parse_expr_recursion_limit")
.long("parse-expression-recursion-limit")
.value_name("NUMBER")
.value_parser(value_parser!(u8))
.help("Maximum recursion depth allowed when parsing an expression")
.long_help(
"Maximum recursion depth allowed when parsing an expression.\n\n\
Defensive limit used to prevent stack overflow.\n\
Should this limit be reached when compiling rules, this parameter \
can be used to modify it.",
),
)
.arg(
Arg::new("parse_string_recursion_limit")
.long("parse-string-recursion-limit")
.value_name("NUMBER")
.value_parser(value_parser!(u8))
.help("Maximum recursion depth allowed when parsing a regex or a hex-string")
.long_help(
"Maximum recursion depth allowed when parsing a regex or a hex-string.\n\n\
Defensive limit used to prevent stack overflow.\n\
Should this limit be reached when compiling rules, this parameter \
can be used to modify it.",
),
)
.arg(
Arg::new("disable_includes")
.long("disable-includes")
.action(ArgAction::SetTrue)
.help("Disable the possibility to include yara files"),
)
.arg(
Arg::new("define")
.short('d')
.long("define")
.value_name("VAR=VALUE")
.action(ArgAction::Append)
.value_parser(parse_define)
.help("Define a symbol that can be used in rules"),
);
if !in_yr_subcommand {
command = command.arg(
Arg::new("rules_file")
.short('f')
.long("rules-file")
.value_name("[NAMESPACE:]RULES_FILE")
.action(ArgAction::Append)
.help("Path to a file containing rules, can be repeated.")
.long_help(
"Path to a file containing rules, can be repeated.\n\n\
The path can be prefixed by the namespace in which to \
compile the rules, followed by a colon.\n\
This can notably be used to avoid name collisions when \
using multiple rules files.",
),
);
}
command
}
fn parse_compiler_profile(profile: &str) -> Result<CompilerProfile, String> {
match profile {
"speed" => Ok(CompilerProfile::Speed),
"memory" => Ok(CompilerProfile::Memory),
_ => Err("invalid value".to_string()),
}
}
fn parse_define(arg: &str) -> Result<(String, ExternalValue), String> {
let Some((name, value)) = arg.split_once('=') else {
return Err("missing '=' delimiter".to_owned());
};
let external_value = if value == "true" {
ExternalValue::Boolean(true)
} else if value == "false" {
ExternalValue::Boolean(false)
} else if value.contains('.') {
match value.parse::<f64>() {
Ok(v) => ExternalValue::Float(v),
Err(_) => ExternalValue::Bytes(value.as_bytes().to_vec()),
}
} else {
match value.parse::<i64>() {
Ok(v) => ExternalValue::Integer(v),
Err(_) => ExternalValue::Bytes(value.as_bytes().to_vec()),
}
};
Ok((name.to_owned(), external_value))
}