pub use std::env::*;
use std::ffi::OsStr;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{path::PathBuf, sync::LazyLock};
static NON_INTERACTIVE: AtomicBool = AtomicBool::new(false);
pub fn set_non_interactive(value: bool) {
NON_INTERACTIVE.store(value, Ordering::Release);
}
pub fn is_non_interactive() -> bool {
NON_INTERACTIVE.load(Ordering::Acquire)
}
static ENV_MUTEX: Mutex<()> = Mutex::new(());
pub fn set_var<K: AsRef<OsStr>, V: AsRef<OsStr>>(key: K, val: V) {
let _lock = ENV_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
unsafe {
std::env::set_var(key, val);
}
}
pub static HOME_DIR: LazyLock<PathBuf> = LazyLock::new(|| dirs::home_dir().unwrap_or_default());
pub static FNOX_CONFIG_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
var_path("FNOX_CONFIG_DIR").unwrap_or_else(|| {
var_path("XDG_CONFIG_HOME")
.unwrap_or_else(|| {
#[cfg(unix)]
return HOME_DIR.join(".config");
#[cfg(windows)]
return HOME_DIR.join("AppData").join("Local");
})
.join("fnox")
})
});
pub static FNOX_STATE_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
var_path("FNOX_STATE_DIR").unwrap_or_else(|| {
var_path("XDG_STATE_HOME")
.unwrap_or_else(|| {
#[cfg(unix)]
return HOME_DIR.join(".local").join("state");
#[cfg(windows)]
return HOME_DIR.join("AppData").join("Local");
})
.join("fnox")
})
});
pub static FNOX_PROFILE: LazyLock<Option<String>> = LazyLock::new(|| {
var("FNOX_PROFILE").ok().and_then(|profile| {
if is_valid_profile_name(&profile) {
Some(profile)
} else {
eprintln!("Warning: Invalid FNOX_PROFILE value '{}' ignored (contains path separators or invalid characters)", profile);
None
}
})
});
pub static FNOX_AGE_KEY: LazyLock<Option<String>> = LazyLock::new(|| var("FNOX_AGE_KEY").ok());
pub static FNOX_PROMPT_AUTH: LazyLock<Option<bool>> = LazyLock::new(|| {
var("FNOX_PROMPT_AUTH")
.ok()
.map(|v| matches!(v.to_lowercase().as_str(), "1" | "true" | "yes"))
});
fn var_path(name: &str) -> Option<PathBuf> {
var(name)
.ok()
.filter(|s| !s.is_empty())
.map(PathBuf::from)
.filter(|p| p.is_absolute())
}
fn is_valid_profile_name(name: &str) -> bool {
if name.is_empty() {
return false;
}
if name == "." || name == ".." {
return false;
}
for ch in name.chars() {
match ch {
'/' | '\\' => return false,
'\0' => return false,
c if c.is_control() => return false,
_ => {}
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_var_path() {
unsafe {
set_var("FNOX_TEST_PATH", "/foo/bar");
assert_eq!(
var_path("FNOX_TEST_PATH").unwrap(),
PathBuf::from("/foo/bar")
);
set_var("FNOX_TEST_PATH", "");
assert_eq!(var_path("FNOX_TEST_PATH"), None);
set_var("FNOX_TEST_PATH", "relative/path");
assert_eq!(var_path("FNOX_TEST_PATH"), None);
remove_var("FNOX_TEST_PATH");
}
}
#[test]
fn test_valid_profile_names() {
assert!(is_valid_profile_name("production"));
assert!(is_valid_profile_name("staging"));
assert!(is_valid_profile_name("dev"));
assert!(is_valid_profile_name("test-env"));
assert!(is_valid_profile_name("test_env"));
assert!(is_valid_profile_name("prod-v2.0"));
assert!(is_valid_profile_name("env123"));
}
#[test]
fn test_invalid_profile_names() {
assert!(!is_valid_profile_name("../../../etc/passwd"));
assert!(!is_valid_profile_name(".."));
assert!(!is_valid_profile_name("."));
assert!(!is_valid_profile_name("../production"));
assert!(!is_valid_profile_name("production/../../etc/passwd"));
assert!(!is_valid_profile_name("/etc/passwd"));
assert!(!is_valid_profile_name("/tmp/evil"));
assert!(!is_valid_profile_name("C:\\Windows\\System32"));
assert!(!is_valid_profile_name("..\\..\\evil"));
assert!(!is_valid_profile_name(""));
assert!(!is_valid_profile_name("prod\0uction")); assert!(!is_valid_profile_name("prod\ntest")); assert!(!is_valid_profile_name("prod\rtest")); }
}