diff --git a/Cargo.lock b/Cargo.lock index 7942de9..5f09147 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,7 +61,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "fassoc-proxy" -version = "0.1.0" +version = "2.0.0" dependencies = [ "chrono", "log", diff --git a/Cargo.toml b/Cargo.toml index b04f625..3d90117 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fassoc-proxy" -version = "0.1.0" +version = "2.0.0" edition = "2021" [dependencies] diff --git a/src/main.rs b/src/main.rs index 8be033d..412208f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,9 +10,9 @@ mod logging; use logging::MAIN_LOGGER; mod rules; -use rules::{FassocRules, Rule}; +use rules::{Command, FassocRules}; -use crate::winproc::create_process; +use crate::winproc::invoke_command; mod winproc; @@ -43,25 +43,25 @@ fn read_fassoc_rules(path: String) -> Result { } fn subst_arg_placeholders(rules: FassocRules, args: Vec) -> FassocRules { - let mut new_rules: FassocRules = rules; + let mut subst_rules: FassocRules = rules; for (index, argument) in args.iter().enumerate() { let placeholder = format!("~~${}", index); - for rule in new_rules.rules.values_mut() { - rule.command = rule.command.replace(&placeholder, argument); + for command in subst_rules.commands.values_mut() { + command.path = command.path.replace(&placeholder, argument); - rule.arguments = rule + command.arguments = command .arguments .to_owned() .map(|arg| arg.replace(&placeholder, argument)); - rule.cwd = rule + command.cwd = command .cwd .to_owned() .map(|cwd| cwd.replace(&placeholder, argument)); - rule.extras = rule.extras.to_owned().map(|mut extras| { + command.extras = command.extras.to_owned().map(|mut extras| { extras.title = extras.title.map(|str| str.replace(&placeholder, argument)); extras.desktop = extras .desktop @@ -71,7 +71,7 @@ fn subst_arg_placeholders(rules: FassocRules, args: Vec) -> FassocRules } } - new_rules + subst_rules } fn main() { @@ -102,19 +102,19 @@ fn main() { } }; - let proxy_rules_path: String = if cli_args.len() >= 3 { + let fassoc_rules_path: String = if cli_args.len() >= 3 { cli_args[2].clone() } else { - match env::var("FASSOC_PROXY_RULES") { + match env::var("FASSOC_RULES_PATH") { Ok(val) => val, Err(_) => { - log::error!("No argument or environment variable was given that points to the proxy rules file."); + log::error!("No argument or environment variable was given that points to the fassoc rules file."); panic!() } } }; - let fassoc_rules = match read_fassoc_rules(proxy_rules_path) { + let fassoc_rules = match read_fassoc_rules(fassoc_rules_path) { Ok(rules) => subst_arg_placeholders(rules, cli_args.to_owned()), Err(error) => { log::error!("Failure when reading fassoc rules ({})", error); @@ -122,11 +122,11 @@ fn main() { } }; - let suitable_rule: &Rule = match fassoc_rules.find_suitable_rule(target_file_path) { - Ok(rule) => rule, + let suitable_command: &Command = match fassoc_rules.find_suitable_command(target_file_path) { + Ok(command) => command, Err(error) => { log::error!( - "Could not find a suitable rule for the file \"{}\", because: {}", + "Could not find a suitable command for the file \"{}\", because: {}", target_file_name, error ); @@ -136,14 +136,14 @@ fn main() { log::debug!( "Creating process, path: \"{}\", args: \"{}\"", - suitable_rule.command.to_owned(), - suitable_rule + suitable_command.path.to_owned(), + suitable_command .arguments .to_owned() .unwrap_or(String::from("NONE")) ); - match create_process(&suitable_rule) { + match invoke_command(&suitable_command) { Ok(process_info) => { log::debug!("Process created, information: {:?}", process_info) } diff --git a/src/rules.rs b/src/rules.rs index c0d3f16..f7356d6 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -10,20 +10,20 @@ use serde::{Deserialize, Serialize}; // ---------------------------------------------------------------------------- #[derive(Debug)] -pub enum FindRuleError { +pub enum FindCommandError { CannotConvertPath, NoMappingFound, - NoRuleFound, + NoMatchFound, } -impl std::fmt::Display for FindRuleError { +impl std::fmt::Display for FindCommandError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - FindRuleError::CannotConvertPath => { + FindCommandError::CannotConvertPath => { write!(f, "Could not convert the path of the file into a string.",) } - FindRuleError::NoMappingFound => write!(f, "Could not find any applicable mappings."), - FindRuleError::NoRuleFound => write!(f, "Could not find any applicable rules."), + FindCommandError::NoMappingFound => write!(f, "No mapping could map the file to a matcher."), + FindCommandError::NoMatchFound => write!(f, "No matcher could match the file to a command."), } } } @@ -31,13 +31,14 @@ impl std::fmt::Display for FindRuleError { #[derive(Serialize, Deserialize, Debug)] pub struct FassocRules { pub mappings: HashMap>, - pub rules: HashMap, + pub matchers: HashMap, + pub commands: HashMap, } impl FassocRules { - pub fn find_suitable_rule(&self, file_path: &Path) -> Result<&Rule, FindRuleError> { + pub fn find_suitable_command(&self, file_path: &Path) -> Result<&Command, FindCommandError> { let file_name_str: String = file_path.file_name().and_then(|n| n.to_str()).map_or_else( - || Err(FindRuleError::CannotConvertPath), + || Err(FindCommandError::CannotConvertPath), |s| Ok(String::from(s)), )?; @@ -56,7 +57,7 @@ impl FassocRules { Some(mapping) => mapping, None => { // Neither the extension mapping nor the fallback mapping found. - return Err(FindRuleError::NoMappingFound); + return Err(FindCommandError::NoMappingFound); } }; @@ -80,48 +81,74 @@ impl FassocRules { } }; - for (index, rule_name) in final_mapping.iter().enumerate() { - log::debug!("Trying rule #{} - {}", index, rule_name); + let mapping_name = file_ext_str.to_owned().unwrap_or_else(|| String::from("*")); - let rule: &Rule = match self.rules.get(rule_name) { - Some(rule) => rule, + for (index, matcher_name) in final_mapping.iter().enumerate() { + log::debug!("Trying matcher #{} - {}", index, matcher_name); + + let matcher: &Matcher = match self.matchers.get(matcher_name) { + Some(matcher) => matcher, + None => { + match self.commands.get(matcher_name) { + Some(command) => { + log::debug!( + "Mapping \"{}\" referred to \"{}\" which isn't a valid matcher, but it is a valid command, mapping directly to command instead.", + mapping_name, + matcher_name + ); + + return Ok(command); + }, + + None => { + log::warn!( + "Ignored a matcher with mame \"{}\" from mapping \"{}\" because it doesn't point to anything that exists.", + matcher_name, + mapping_name + ); + + continue; + } + } + + } + }; + + let matcher_command: &Command = match self.commands.get(&matcher.command) { + Some(command) => command, None => { - log::warn!( - "Ignored a rule name \"{}\" from mapping \"{}\", because it doesn't exist.", - rule_name, - file_ext_str.to_owned().unwrap_or(String::from("*")) - ); + log::warn!("The command pointed to by matcher \"{}\" does not exist, ignoring this matcher.", matcher_name); continue; } }; - let mut valid = true; + let mut is_match: bool = true; - // If rule has file name RegEx, validate that the RegEx matches. - valid &= rule.regexf.as_ref().map_or(true, |_| { - rule.rmatch_file_name(file_name_str.to_owned()) + // If matcher has file name RegEx, match the RegEx against the file. + is_match &= matcher.regexf.as_ref().map_or(true, |_| { + matcher.rmatch_file_name(file_name_str.to_owned()) .unwrap_or_else(|error| { log::error!( - "Encountered RegEx error when evaluating mapped rules: {:?}", + "Encountered RegEx error when evaluating mapped matchers: {:?}", error ); false }) }); - // If already invalidated, continue. - if !valid { + // If match already failed, continue. + if !is_match { continue; } - // If rule has file content RegEx, validate that the RegEx matches. - valid &= rule.regexc.as_ref().map_or(true, |_| { + // If matcher has file content RegEx, match the RegEx against the file. + is_match &= matcher.regexc.as_ref().map_or(true, |_| { ensure_contents_read(&mut file_content); file_content.as_ref().map_or(false, |content| { - rule.rmatch_file_content(content).unwrap_or_else(|error| { + matcher.rmatch_file_content(content).unwrap_or_else(|error| { log::error!( - "Encountered RegEx error when evaluating mapped rules: {:?}", + "Encountered RegEx error when evaluating mapped matchers: {:?}", error, ); false @@ -129,73 +156,83 @@ impl FassocRules { }) }); - // If the rule is still valid after validation, return it. - if valid { + // If the matcher is still valid after validation, return it. + if is_match { log::debug!( - "Rule #{} - {} - is suitable for this file, using this rule.", + "Matcher #{} - {} - matched this file.", index, - rule_name + matcher_name ); - return Ok(rule); + + return Ok(matcher_command); } } - return Err(FindRuleError::NoRuleFound); + return Err(FindCommandError::NoMatchFound); } } // ---------------------------------------------------------------------------- -// Rule +// Matcher // ---------------------------------------------------------------------------- #[derive(Debug)] -pub enum RuleRegexError { +pub enum MatcherError { RegexCompileError(re::Error), NoRegexError, } #[derive(Serialize, Deserialize, Debug)] -pub struct Rule { +pub struct Matcher { pub command: String, - pub arguments: Option, - pub cwd: Option, pub regexf: Option, pub regexc: Option, - pub process_attributes: Option, - pub thread_attributes: Option, - pub inherit_handles: Option, - pub creation_flags: Option>, - // pub environment: Option>, -- This will be implemented later. - pub extras: Option, } -impl Rule { - fn rmatch_file(regstr: Option, content: &String) -> Result { - regstr.map_or(Err(RuleRegexError::NoRegexError), |regstr| { +impl Matcher { + fn rmatch_file(regstr: Option, content: &String) -> Result { + regstr.map_or(Err(MatcherError::NoRegexError), |regstr| { re::Regex::new(regstr.as_str()).map_or_else( - |error| Err(RuleRegexError::RegexCompileError(error)), + |error| Err(MatcherError::RegexCompileError(error)), |regex| Ok(regex.is_match(content.as_str())), ) }) } - pub fn rmatch_file_name(&self, file_name: String) -> Result { - Rule::rmatch_file(self.regexf.to_owned(), &file_name) + pub fn rmatch_file_name(&self, file_name: String) -> Result { + Matcher::rmatch_file(self.regexf.to_owned(), &file_name) } - pub fn rmatch_file_content(&self, file_content: &String) -> Result { - Rule::rmatch_file(self.regexc.to_owned(), file_content) + pub fn rmatch_file_content(&self, file_content: &String) -> Result { + Matcher::rmatch_file(self.regexc.to_owned(), file_content) } } -impl Clone for Rule { +// ---------------------------------------------------------------------------- +// Command +// ---------------------------------------------------------------------------- + +#[derive(Serialize, Deserialize, Debug)] +pub struct Command { + pub path: String, + pub arguments: Option, + pub cwd: Option, + pub process_attributes: Option, + pub thread_attributes: Option, + pub inherit_handles: Option, + pub creation_flags: Option>, + // pub environment: Option>, -- This will be implemented later. + pub extras: Option, +} + +impl Command {} + +impl Clone for Command { fn clone(&self) -> Self { - Rule { - command: self.command.clone(), + Command { + path: self.path.clone(), arguments: self.arguments.clone(), cwd: self.cwd.clone(), - regexf: self.regexf.clone(), - regexc: self.regexc.clone(), process_attributes: self.process_attributes.clone(), thread_attributes: self.thread_attributes.clone(), inherit_handles: self.inherit_handles.clone(), diff --git a/src/winproc.rs b/src/winproc.rs index 078b553..57ccda2 100644 --- a/src/winproc.rs +++ b/src/winproc.rs @@ -8,7 +8,7 @@ use windows::Win32::UI::WindowsAndMessaging::{ SW_SHOWNORMAL, }; -use super::rules::Rule; +use super::rules::Command; use std::collections::HashMap; use std::mem::size_of; use std::ptr; @@ -70,13 +70,13 @@ impl Default for ProcessCreationParameters { } impl ProcessCreationParameters { - pub fn from_rule(rule: &Rule) -> ProcessCreationParameters { + pub fn from_rule(rule: &Command) -> ProcessCreationParameters { let mut pcp = ProcessCreationParameters::default(); - pcp.command = CString::new(rule.command.to_owned()).map_or_else( + pcp.command = CString::new(rule.path.to_owned()).map_or_else( |error| { log::error!("Couldn't nativize the command string \"{}\" from the selected rule, due to error: {}", - rule.command, error); + rule.path, error); panic!(); }, @@ -324,29 +324,25 @@ impl std::fmt::Display for CreateProcessError { } } -pub fn create_process(rule: &Rule) -> Result { +pub fn invoke_command(rule: &Command) -> Result { let params = ProcessCreationParameters::from_rule(rule); - let command_path = std::path::Path::new(&rule.command); + let command_path = std::path::Path::new(&rule.path); if !command_path.exists() { - return Err(CreateProcessError::CommandDoesNotExist( - rule.command.clone(), - )); + return Err(CreateProcessError::CommandDoesNotExist(rule.path.clone())); } if !command_path.extension().map_or(false, |ext| { ext.to_str().map_or(false, |exts| exts.ends_with("exe")) }) { return Err(CreateProcessError::CommandNotExecutable( - rule.command.to_owned(), + rule.path.to_owned(), )); } if !command_path.is_absolute() { - return Err(CreateProcessError::CommandNotAbsolute( - rule.command.to_owned(), - )); + return Err(CreateProcessError::CommandNotAbsolute(rule.path.to_owned())); } unsafe {