use serde::{Deserialize, Serialize};
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvasionConfig {
pub encoding_enabled: bool,
pub content_type_switching: bool,
pub fingerprint_rotation: bool,
pub header_obfuscation: bool,
pub grammar_mutations: bool,
pub smuggling_enabled: bool,
pub h2_evasion_enabled: bool,
pub max_attempts: u32,
pub insecure_tls: bool,
#[serde(default)]
pub proxies: Vec<String>,
#[serde(default)]
pub origin_bypass: std::collections::HashMap<String, std::net::IpAddr>,
}
impl Default for EvasionConfig {
fn default() -> Self {
Self {
encoding_enabled: true,
content_type_switching: true,
fingerprint_rotation: true,
header_obfuscation: true,
grammar_mutations: true,
smuggling_enabled: true,
h2_evasion_enabled: true,
max_attempts: 5,
insecure_tls: false,
proxies: Vec::new(),
origin_bypass: std::collections::HashMap::new(),
}
}
}
impl EvasionConfig {
#[must_use]
pub fn encoding_only() -> Self {
Self {
encoding_enabled: true,
content_type_switching: false,
fingerprint_rotation: false,
header_obfuscation: false,
grammar_mutations: false,
smuggling_enabled: false,
h2_evasion_enabled: false,
max_attempts: 3,
insecure_tls: false,
proxies: Vec::new(),
origin_bypass: std::collections::HashMap::new(),
}
}
#[must_use]
pub fn maximum() -> Self {
Self {
encoding_enabled: true,
content_type_switching: true,
fingerprint_rotation: true,
header_obfuscation: true,
grammar_mutations: true,
smuggling_enabled: true,
h2_evasion_enabled: true,
max_attempts: 10,
insecure_tls: false,
proxies: Vec::new(),
origin_bypass: std::collections::HashMap::new(),
}
}
pub fn validate(&self) -> Result<(), String> {
if self.insecure_tls {
eprintln!(
"WARNING: TLS certificate validation is disabled (--insecure-tls). Do not use in production."
);
}
if self.grammar_mutations && !self.encoding_enabled {
eprintln!(
"WARNING: Grammar mutations are enabled but encoding is disabled. Mutations may require encoding to bypass effectively."
);
}
if self.max_attempts == 0 {
return Err("max_attempts must be greater than 0".to_string());
}
self.validate_proxies()?;
Ok(())
}
fn validate_proxies(&self) -> Result<(), String> {
for proxy in &self.proxies {
if !proxy.starts_with("http://")
&& !proxy.starts_with("https://")
&& !proxy.starts_with("socks5://")
&& !proxy.starts_with("socks5h://")
{
return Err(format!(
"Invalid proxy URL '{}': must start with http://, https://, socks5://, or socks5h://",
proxy
));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_all_enabled() {
let config = EvasionConfig::default();
assert!(config.encoding_enabled);
assert!(config.content_type_switching);
assert!(config.fingerprint_rotation);
assert!(config.header_obfuscation);
assert!(config.grammar_mutations);
assert!(config.smuggling_enabled);
assert!(config.h2_evasion_enabled);
assert_eq!(config.max_attempts, 5);
assert!(!config.insecure_tls);
}
#[test]
fn encoding_only_config() {
let config = EvasionConfig::encoding_only();
assert!(config.encoding_enabled);
assert!(!config.content_type_switching);
assert!(!config.grammar_mutations);
}
#[test]
fn maximum_config() {
let config = EvasionConfig::maximum();
assert!(config.grammar_mutations);
assert_eq!(config.max_attempts, 10);
}
#[test]
fn config_serde_roundtrip() {
let config = EvasionConfig::default();
let json = serde_json::to_string(&config).expect("serialize");
let deserialized: EvasionConfig = serde_json::from_str(&json).expect("deserialize");
assert_eq!(deserialized.max_attempts, config.max_attempts);
}
}