From 4481fdb0dacb65e4a4d1fd0d4d26a48efeb89c74 Mon Sep 17 00:00:00 2001 From: Wesley Shields Date: Sun, 29 Sep 2024 17:14:19 -0400 Subject: [PATCH] Improve config file handling. Move the config structures and loading to a separate module, so that it can be used by each command. Pass the "fmt" section of the config to the "fmt" command. As we add support for other commands to use the config file we can easily pass their sub-structure into the command. Load the config file from %{HOME}/.yara-x.toml - or whatever the corresponding location is on Windows. The exact format of the config is still to be documented, but now that it is generic and not specific to formatting I've moved the docs to a new file that is more appropriately named. --- Cargo.lock | 3 +- Cargo.toml | 1 + cli/Cargo.toml | 2 +- cli/src/commands/fmt.rs | 67 +++-------------- cli/src/help.rs | 7 +- cli/src/main.rs | 21 +++++- ...Config Guide.md => YARA-X Config Guide.md} | 0 fmt/src/wxsfmt.toml | 10 --- lib/Cargo.toml | 1 + lib/src/config/mod.rs | 75 +++++++++++++++++++ lib/src/lib.rs | 3 + 11 files changed, 119 insertions(+), 71 deletions(-) rename docs/{YARA-X Formatting Config Guide.md => YARA-X Config Guide.md} (100%) delete mode 100644 fmt/src/wxsfmt.toml create mode 100644 lib/src/config/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 888bffee..10113858 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4630,6 +4630,7 @@ dependencies = [ "digest 0.10.7", "dsa", "ecdsa", + "figment", "fmmap", "globwalk", "goldenfile", @@ -4704,8 +4705,8 @@ dependencies = [ "enable-ansi-support", "encoding_rs", "env_logger", - "figment", "globwalk", + "home", "itertools 0.13.0", "log", "pprof", diff --git a/Cargo.toml b/Cargo.toml index b5d09d69..88419cf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ figment = "0.10.19" fmmap = "0.3.3" globwalk = "0.9.1" goldenfile = "1.6.1" +home = "0.5.9" ihex = "3.0.0" indenter = "0.3.3" indexmap = "2.2.6" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 6512a1d6..f7b4e8a7 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -44,10 +44,10 @@ anyhow = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"] } clap_complete = { workspace = true } globwalk = { workspace = true } +home = { workspace = true } itertools = { workspace = true } enable-ansi-support = { workspace = true } env_logger = { workspace = true, optional = true, features = ["auto-color"] } -figment = { workspace = true, features = ["toml"] } log = { workspace = true, optional = true } protobuf = { workspace = true } protobuf-json-mapping = { workspace = true } diff --git a/cli/src/commands/fmt.rs b/cli/src/commands/fmt.rs index 2850404a..d9d0b8f9 100644 --- a/cli/src/commands/fmt.rs +++ b/cli/src/commands/fmt.rs @@ -2,51 +2,11 @@ use std::fs::File; use std::path::PathBuf; use std::{fs, io, process}; -use crate::help::{FMT_CHECK_MODE, FMT_CONFIG_FILE}; +use crate::help::{CONFIG_FILE, FMT_CHECK_MODE}; use clap::{arg, value_parser, ArgAction, ArgMatches, Command}; -use figment::{ - providers::{Format, Serialized, Toml}, - Figment, -}; -use serde::{Deserialize, Serialize}; +use yara_x::config::{load_config_from_file, FormatConfig}; use yara_x_fmt::Formatter; -#[derive(Deserialize, Serialize, Debug)] -struct Config { - rule: Rule, - meta: Meta, - patterns: Patterns, -} - -#[derive(Deserialize, Serialize, Debug)] -struct Rule { - indent_section_headers: bool, - indent_section_contents: bool, -} - -#[derive(Deserialize, Serialize, Debug)] -struct Meta { - align_values: bool, -} - -#[derive(Deserialize, Serialize, Debug)] -struct Patterns { - align_values: bool, -} - -impl Default for Config { - fn default() -> Config { - Config { - rule: Rule { - indent_section_headers: true, - indent_section_contents: true, - }, - meta: Meta { align_values: true }, - patterns: Patterns { align_values: true }, - } - } -} - pub fn fmt() -> Command { super::command("fmt") .about("Format YARA source files") @@ -61,29 +21,26 @@ pub fn fmt() -> Command { .arg( arg!(-C --config "Config file") .value_parser(value_parser!(PathBuf)) - .long_help(FMT_CONFIG_FILE), + .long_help(CONFIG_FILE), ) } -pub fn exec_fmt(args: &ArgMatches) -> anyhow::Result<()> { +pub fn exec_fmt(args: &ArgMatches, main_config: FormatConfig) -> anyhow::Result<()> { let files = args.get_many::("FILE").unwrap(); let check = args.get_flag("check"); let config_file = args.get_one::("config"); - let formatter = if config_file.is_some() { - let config: Config = - Figment::from(Serialized::defaults(Config::default())) - .merge(Toml::file_exact(&config_file.unwrap())) - .extract()?; - Formatter::new() - .align_metadata(config.meta.align_values) - .align_patterns(config.patterns.align_values) - .indent_section_headers(config.rule.indent_section_headers) - .indent_section_contents(config.rule.indent_section_contents) + let config: FormatConfig = if config_file.is_some() { + load_config_from_file(&config_file.unwrap())?.fmt } else { - Formatter::new() + main_config }; + let formatter = Formatter::new() + .align_metadata(config.meta.align_values) + .align_patterns(config.patterns.align_values) + .indent_section_headers(config.rule.indent_section_headers) + .indent_section_contents(config.rule.indent_section_contents); let mut changed = false; for file in files { diff --git a/cli/src/help.rs b/cli/src/help.rs index d4369a03..44480803 100644 --- a/cli/src/help.rs +++ b/cli/src/help.rs @@ -151,9 +151,10 @@ pub const FMT_CHECK_MODE: &str = r#"Run in 'check' mode Doesn't modify the files. Exits with 0 if files are formatted correctly. Exits with 1 if formatting is required."#; -pub const FMT_CONFIG_FILE: &str = r#"Config file for formatting +pub const CONFIG_FILE: &str = r#"Config file for YARA-X -Config file which controls the behavior of the formatter. See XXX (FILL IN URL +Config file which controls the behavior of YARA-X. See XXX (FILL IN URL ONCE DOCS ARE WRITTEN) for supported options. -If config file is not specified the default formatting options are applied."#; +If config file is not specified, ${HOME}/.yara-x.toml is used. If that does not +exist the default options are applied."#; diff --git a/cli/src/main.rs b/cli/src/main.rs index 6a145cdf..33061b8f 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,9 +3,11 @@ mod help; mod walk; use crossterm::tty::IsTty; +use home; use std::{io, panic, process}; use yansi::Color::Red; use yansi::Paint; +use yara_x::config::{load_config_from_file, Config}; use crate::commands::cli; @@ -19,6 +21,7 @@ const APP_HELP_TEMPLATE: &str = r#"YARA-X {version}, the pattern matching swiss "#; const EXIT_ERROR: i32 = 1; +const CONFIG_FILE: &str = ".yara-x.toml"; fn main() -> anyhow::Result<()> { // Enable support for ANSI escape codes in Windows. In other platforms @@ -58,12 +61,28 @@ fn main() -> anyhow::Result<()> { process::exit(EXIT_ERROR); })); + let config: Config = match home::home_dir() { + Some(home_path) if !home_path.as_os_str().is_empty() => { + match load_config_from_file(&home_path.join(CONFIG_FILE)) { + Ok(c) => c, + Err(e) => { + println!("Error parsing config, using defaults: {}", e); + Config::default() + } + } + } + _ => { + println!("Unable to find home directory, using defaults."); + Config::default() + } + }; + let result = match args.subcommand() { #[cfg(feature = "debug-cmd")] Some(("debug", args)) => commands::exec_debug(args), Some(("check", args)) => commands::exec_check(args), Some(("fix", args)) => commands::exec_fix(args), - Some(("fmt", args)) => commands::exec_fmt(args), + Some(("fmt", args)) => commands::exec_fmt(args, config.fmt), Some(("scan", args)) => commands::exec_scan(args), Some(("dump", args)) => commands::exec_dump(args), Some(("compile", args)) => commands::exec_compile(args), diff --git a/docs/YARA-X Formatting Config Guide.md b/docs/YARA-X Config Guide.md similarity index 100% rename from docs/YARA-X Formatting Config Guide.md rename to docs/YARA-X Config Guide.md diff --git a/fmt/src/wxsfmt.toml b/fmt/src/wxsfmt.toml deleted file mode 100644 index 9793e391..00000000 --- a/fmt/src/wxsfmt.toml +++ /dev/null @@ -1,10 +0,0 @@ -# Config file for "yr fmt" - XXX: Change these values before merging. -[rule] -indent_section_headers = false -indent_section_contents = false - -[meta] -align_values = false - -[patterns] -align_values = false diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 0279c862..740a2b29 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -215,6 +215,7 @@ der-parser = { workspace = true, optional = true, features = ["bigint"] } digest = { workspace = true, optional = true } dsa = { workspace = true, optional = true } ecdsa = { workspace = true, optional = true } +figment = { workspace = true, features = ["toml"] } fmmap = { workspace = true } indexmap = { workspace = true, features = ["serde"] } intaglio = { workspace = true } diff --git a/lib/src/config/mod.rs b/lib/src/config/mod.rs new file mode 100644 index 00000000..4ad2a286 --- /dev/null +++ b/lib/src/config/mod.rs @@ -0,0 +1,75 @@ +use figment::{ + providers::{Format, Serialized, Toml}, + Figment, +}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +/// Configuration structure for "yr" commands. +#[derive(Deserialize, Serialize, Debug)] +pub struct Config { + /// Format specific configuration information. + pub fmt: FormatConfig, +} + +/// Format specific configuration information. +#[derive(Deserialize, Serialize, Debug)] +pub struct FormatConfig { + /// Rule specific formatting information. + pub rule: Rule, + /// Meta specific formatting information. + pub meta: Meta, + /// Pattern specific formatting information. + pub patterns: Patterns, +} + +/// Rule specific formatting information. +#[derive(Deserialize, Serialize, Debug)] +pub struct Rule { + /// Indent section headers (meta, strings, condition). + pub indent_section_headers: bool, + /// Indent section contents one level past section headers. + pub indent_section_contents: bool, +} + +/// Meta specific formatting information. +#[derive(Deserialize, Serialize, Debug)] +pub struct Meta { + /// Align values to longest key. + pub align_values: bool, +} + +/// Pattern specific formatting information. +#[derive(Deserialize, Serialize, Debug)] +pub struct Patterns { + /// Align patterns to longest name. + pub align_values: bool, +} + +impl Default for Config { + fn default() -> Config { + Config { + fmt: FormatConfig { + rule: Rule { + indent_section_headers: true, + indent_section_contents: true, + }, + meta: Meta { align_values: true }, + patterns: Patterns { align_values: true }, + }, + } + } +} + +/// Load config file from a given path. Path must contain a valid TOML file or +/// this function will propagate the error. For structure of the config file +/// see "YARA-X Config Guide.md". +pub fn load_config_from_file( + config_file: &PathBuf, +) -> Result { + let config: Config = + Figment::from(Serialized::defaults(Config::default())) + .merge(Toml::file_exact(config_file.as_path())) + .extract()?; + Ok(config) +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index aa1fdd18..be5b46c7 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -79,6 +79,9 @@ mod models; #[cfg(test)] mod tests; +/// Used to parse and handle YARA-X configuration options. +pub mod config; + pub mod errors { //! Errors returned by this crate. //!