ares/cli/mod.rs
1// First-run configuration module
2mod first_run;
3pub use first_run::run_first_time_setup;
4
5use std::{fs::File, io::Read};
6
7use crate::cli_pretty_printing;
8use crate::cli_pretty_printing::panic_failure_both_input_and_fail_provided;
9use crate::config::{get_config_file_into_struct, load_wordlist, Config};
10/// This doc string acts as a help message when the uses run '--help' in CLI mode
11/// as do all doc strings on fields
12use clap::Parser;
13use log::trace;
14
15/// The struct for Clap CLI arguments
16#[derive(Parser)]
17#[command(author = "Bee <bee@skerritt.blog>", about, long_about = None)]
18pub struct Opts {
19 /// Some input. Because this isn't an Option<T> it's required to be used
20 #[arg(short, long)]
21 text: Option<String>,
22
23 /// A level of verbosity, and can be used multiple times
24 #[arg(short, long, action = clap::ArgAction::Count)]
25 verbose: u8,
26
27 /// Turn off human checker, perfect for APIs where you don't want input from humans
28 #[arg(short, long)]
29 disable_human_checker: bool,
30
31 /// Set timeout, if it is not decrypted after this time, it will return an error.
32 /// Default is 5 seconds.
33 // If we want to call it `timeout`, the short argument contends with the one for Text `ares -t`.
34 // I propose we just call it `cracking_timeout`.
35 #[arg(short, long)]
36 cracking_timeout: Option<u32>,
37 /// Run in API mode, this will return the results instead of printing them.
38 /// Default is false
39 #[arg(short, long)]
40 api_mode: Option<bool>,
41 /// Opens a file for decoding
42 /// Use instead of `--text`
43 #[arg(short, long)]
44 file: Option<String>,
45 /// If you have a crib (you know a piece of information in the plaintext)
46 /// Or you want to create a custom regex to check against, you can use the Regex checker below.
47 /// This turns off other checkers (English, LemmeKnow)
48 #[arg(short, long)]
49 regex: Option<String>,
50 /// Path to a wordlist file containing newline-separated words
51 /// The checker will match input against these words exactly
52 /// Takes precedence over config file if both specify a wordlist
53 #[arg(
54 long,
55 help = "Path to a wordlist file with newline-separated words for exact matching"
56 )]
57 wordlist: Option<String>,
58 /// Show all potential plaintexts found instead of exiting after the first one
59 /// Automatically disables the human checker
60 #[arg(long)]
61 top_results: bool,
62 /// Enables enhanced plaintext detection with BERT model.
63 #[arg(long)]
64 enable_enhanced_detection: bool,
65}
66
67/// Parse CLI Arguments turns a Clap Opts struct, seen above
68/// Into a library Struct for use within the program
69/// The library struct can be found in the [config](../config) folder.
70/// # Panics
71/// This function can panic when it gets both a file and text input at the same time.
72pub fn parse_cli_args() -> (String, Config) {
73 let mut opts: Opts = Opts::parse();
74 let min_log_level = match opts.verbose {
75 0 => "Warn",
76 1 => "Info",
77 2 => "Debug",
78 _ => "Trace",
79 };
80 env_logger::init_from_env(
81 env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, min_log_level),
82 );
83
84 // If both the file and text are proivded, panic because we're not sure which one to use
85 if opts.file.is_some() && opts.text.is_some() {
86 panic_failure_both_input_and_fail_provided();
87 }
88
89 let input_text: String = if opts.file.is_some() {
90 read_and_parse_file(opts.file.unwrap())
91 } else {
92 opts.text
93 .expect("Error. No input was provided. Please use ares --help")
94 };
95
96 // Fixes bug where opts.text and opts.file are partially borrowed
97 opts.text = None;
98 opts.file = None;
99
100 trace!("Program was called with CLI 😉");
101 trace!("Parsed the arguments");
102 trace!("The inputted text is {}", &input_text);
103
104 cli_args_into_config_struct(opts, input_text)
105}
106
107/// When the CLI is called with `-f` to open a file
108/// this function opens it
109/// # Panics
110/// This can panic when opening a file which does not exist!
111pub fn read_and_parse_file(file_path: String) -> String {
112 // TODO pretty match on the errors to provide better output
113 // Else it'll panic
114 let mut file = File::open(file_path).unwrap();
115 let mut contents = String::new();
116 file.read_to_string(&mut contents).unwrap();
117 // We can just put the file into the `Opts.text` and the program will work as normal
118 // On Unix systems a line is defined as "\n{text}\n"
119 // https://stackoverflow.com/a/729795
120 // Which means if a user creates a file on Unix, it'll have a new line appended.
121 // This is probably not what they wanted to decode (it is not what I wanted) so we are removing them
122 if contents.ends_with(['\n', '\r']) {
123 contents.strip_suffix(['\n', '\r']).unwrap().to_owned()
124 } else {
125 contents
126 }
127}
128
129/// Turns our CLI arguments into a config stuct
130fn cli_args_into_config_struct(opts: Opts, text: String) -> (String, Config) {
131 // Get configuration from file first
132 let mut config = get_config_file_into_struct();
133
134 // Update config with CLI arguments when they're explicitly set
135 config.verbose = opts.verbose;
136 config.human_checker_on = !opts.disable_human_checker;
137
138 if let Some(timeout) = opts.cracking_timeout {
139 config.timeout = timeout;
140 }
141
142 if let Some(api_mode) = opts.api_mode {
143 config.api_mode = api_mode;
144 }
145
146 if let Some(regex) = opts.regex {
147 config.regex = Some(regex);
148 }
149
150 // Handle wordlist if provided via CLI (takes precedence over config file)
151 if let Some(wordlist_path) = opts.wordlist {
152 config.wordlist_path = Some(wordlist_path.clone());
153
154 // Load the wordlist here in the CLI layer
155 match load_wordlist(&wordlist_path) {
156 Ok(wordlist) => {
157 config.wordlist = Some(wordlist);
158 }
159 Err(e) => {
160 // Critical error - exit if wordlist is specified but can't be loaded
161 eprintln!("Can't load wordlist at '{}': {}", wordlist_path, e);
162 std::process::exit(1);
163 }
164 }
165 }
166
167 // Set top_results mode if the flag is present
168 config.top_results = opts.top_results;
169
170 // If top_results is enabled, automatically disable the human checker
171 if config.top_results {
172 config.human_checker_on = false;
173 }
174
175 // Handle enhanced detection if enabled via CLI
176 if opts.enable_enhanced_detection {
177 // Simply enable enhanced detection without downloading a model
178 // since the current version of gibberish-or-not doesn't support model downloading
179 config.enhanced_detection = true;
180 eprintln!(
181 "{}",
182 cli_pretty_printing::statement("Enhanced detection enabled.", None)
183 );
184 }
185
186 (text, config)
187}