use lemmeknow::Identifier;
use memmap2::Mmap;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fs::{self, File};
use std::io::{self, BufRead, BufReader};
use std::io::{Read, Write};
use std::path::Path;
#[derive(Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
pub verbose: u8,
#[serde(skip)]
pub lemmeknow_config: Identifier,
#[serde(default)]
pub lemmeknow_min_rarity: f32,
#[serde(default)]
pub lemmeknow_max_rarity: f32,
#[serde(default)]
pub lemmeknow_tags: Vec<String>,
#[serde(default)]
pub lemmeknow_exclude_tags: Vec<String>,
#[serde(default)]
pub lemmeknow_boundaryless: bool,
pub human_checker_on: bool,
pub timeout: u32,
pub top_results: bool,
pub api_mode: bool,
pub regex: Option<String>,
pub wordlist_path: Option<String>,
#[serde(skip)]
pub wordlist: Option<HashSet<String>>,
pub colourscheme: HashMap<String, String>,
pub enhanced_detection: bool,
pub model_path: Option<String>,
}
static CONFIG: OnceCell<Config> = OnceCell::new();
pub fn set_global_config(config: Config) {
CONFIG.set(config).ok(); }
pub fn get_config() -> &'static Config {
CONFIG.get_or_init(Config::default)
}
const LEMMEKNOW_DEFAULT_CONFIG: Identifier = Identifier {
min_rarity: 0.0_f32,
max_rarity: 0.0_f32,
tags: vec![],
exclude_tags: vec![],
file_support: false,
boundaryless: false,
};
fn make_identifier_from_config(config: &Config) -> Identifier {
Identifier {
min_rarity: config.lemmeknow_min_rarity,
max_rarity: config.lemmeknow_max_rarity,
tags: config.lemmeknow_tags.clone(),
exclude_tags: config.lemmeknow_exclude_tags.clone(),
file_support: false, boundaryless: config.lemmeknow_boundaryless,
}
}
fn update_identifier_in_config(config: &mut Config) {
config.lemmeknow_config = make_identifier_from_config(config);
}
impl Default for Config {
fn default() -> Self {
let mut config = Config {
verbose: 0,
lemmeknow_config: LEMMEKNOW_DEFAULT_CONFIG,
lemmeknow_min_rarity: 0.0_f32,
lemmeknow_max_rarity: 0.0_f32,
lemmeknow_tags: vec![],
lemmeknow_exclude_tags: vec![],
lemmeknow_boundaryless: false,
human_checker_on: false,
timeout: 5,
top_results: false,
api_mode: false,
regex: None,
wordlist_path: None,
wordlist: None,
enhanced_detection: false,
model_path: None,
colourscheme: HashMap::new(),
};
config
.colourscheme
.insert(String::from("informational"), String::from("255,215,0")); config
.colourscheme
.insert(String::from("warning"), String::from("255,0,0")); config
.colourscheme
.insert(String::from("success"), String::from("0,255,0")); config
.colourscheme
.insert(String::from("error"), String::from("255,0,0"));
config
.colourscheme
.insert(String::from("question"), String::from("255,215,0")); config
}
}
pub fn get_config_file_path() -> std::path::PathBuf {
let mut path = dirs::home_dir().expect("Could not find home directory");
path.push(".ares");
fs::create_dir_all(&path).expect("Could not create Ares directory");
path.push("config.toml");
path
}
pub fn create_default_config_file() -> std::io::Result<()> {
let config = Config::default();
let toml_string = toml::to_string_pretty(&config).expect("Could not serialize config");
let path = get_config_file_path();
let mut file = File::create(path)?;
file.write_all(toml_string.as_bytes())?;
Ok(())
}
fn read_config_file() -> std::io::Result<String> {
let path = get_config_file_path();
let mut file = File::open(&path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn parse_toml_with_unknown_keys(contents: &str) -> Config {
let parsed_value: toml::Value = toml::from_str(contents).expect("Could not parse config file");
if let toml::Value::Table(table) = &parsed_value {
let known_keys = [
"verbose",
"lemmeknow_min_rarity",
"enhanced_detection",
"model_path",
"lemmeknow_max_rarity",
"lemmeknow_tags",
"lemmeknow_exclude_tags",
"lemmeknow_boundaryless",
"human_checker_on",
"timeout",
"top_results",
"api_mode",
"regex",
"wordlist_path",
"question",
"colourscheme",
];
for key in table.keys() {
if !known_keys.contains(&key.as_str()) {
crate::cli_pretty_printing::warning_unknown_config_key(key);
}
}
}
let mut config: Config = toml::from_str(contents).expect("Could not parse config file");
update_identifier_in_config(&mut config);
config
}
pub fn load_wordlist<P: AsRef<Path>>(path: P) -> io::Result<HashSet<String>> {
let file = File::open(path)?;
let file_size = file.metadata()?.len();
if file_size < 10_000_000 {
let reader = BufReader::new(file);
let mut wordlist = HashSet::new();
for line in reader.lines() {
if let Ok(word) = line {
let trimmed = word.trim().to_string();
if !trimmed.is_empty() {
wordlist.insert(trimmed);
}
}
}
Ok(wordlist)
} else {
let mmap = unsafe { Mmap::map(&file)? };
if std::str::from_utf8(&mmap).is_err() {
panic!("Wordlist file contains invalid UTF-8");
}
let mut wordlist = HashSet::new();
let content = unsafe { std::str::from_utf8_unchecked(&mmap) };
for line in content.lines() {
let trimmed = line.trim();
if !trimmed.is_empty() {
wordlist.insert(trimmed.to_string());
}
}
Ok(wordlist)
}
}
pub fn get_config_file_into_struct() -> Config {
let path = get_config_file_path();
if !path.exists() {
let first_run_config = crate::cli::run_first_time_setup();
let mut config = Config::default();
config.colourscheme = first_run_config
.iter()
.filter(|(k, _)| !k.starts_with("wordlist") && *k != "timeout")
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
if let Some(timeout) = first_run_config.get("timeout") {
config.timeout = timeout.parse().unwrap_or(5);
}
if let Some(wordlist_path) = first_run_config.get("wordlist_path") {
config.wordlist_path = Some(wordlist_path.clone());
match load_wordlist(wordlist_path) {
Ok(wordlist) => {
config.wordlist = Some(wordlist);
}
Err(e) => {
eprintln!(
"Warning: Could not load wordlist at '{}': {}",
wordlist_path, e
);
}
}
}
save_config_to_file(&config, &path);
config
} else {
match read_config_file() {
Ok(contents) => {
let mut config = parse_toml_with_unknown_keys(&contents);
if let Some(wordlist_path) = &config.wordlist_path {
match load_wordlist(wordlist_path) {
Ok(wordlist) => {
config.wordlist = Some(wordlist);
}
Err(_e) => {
eprintln!("Can't load wordlist at '{}'. Either fix or remove wordlist from config file at '{}'",
wordlist_path, path.display());
std::process::exit(1);
}
}
}
config
}
Err(e) => {
eprintln!("Error reading config file: {}. Using defaults.", e);
Config::default()
}
}
}
}
fn save_config_to_file(config: &Config, path: &std::path::Path) {
let toml_string = toml::to_string_pretty(config).expect("Could not serialize config");
let mut file = File::create(path).expect("Could not create config file");
file.write_all(toml_string.as_bytes())
.expect("Could not write to config file");
}