use std::net::IpAddr;
use clap::Parser;
const AFTER_HELP: &str = "Environment variables:
Proxy security:
CC_REVERSE_PROXY_IPS Trusted proxy IPs (enables strict mode)
TRUSTED_PROXY_IPS_VAR Alt variable name to read proxy IPs from
Filtering:
BLOCKED_IPS Comma-separated client IPs to block
BLOCKED_METHODS HTTP methods to block (e.g. PUT,DELETE)
BLOCKED_PATTERNS URL substrings to block (case-insensitive)
Rate limiting:
RATE_LIMIT_REQUESTS Max requests per window (default: 100)
RATE_LIMIT_WINDOW_SECS Window duration in seconds (default: 60)
RATE_LIMIT_CLEANUP_THRESHOLD Entries before auto-cleanup (default: 10000, 0=off)
RATE_LIMIT_CLEANUP_INTERVAL_SECS Min seconds between cleanups (default: 60)
Authentication:
CC_HTTP_BASIC_AUTH user:password (also _1, _2, ... for more users)
CC_HTTP_BASIC_AUTH_REALM WWW-Authenticate realm (default: WiseGate)
CC_BEARER_TOKEN Bearer token (RFC 6750)
CC_FORWARD_AUTH_HEADER Forward Authorization upstream (default: false)
Proxy behaviour:
PROXY_TIMEOUT_SECS Upstream request timeout (default: 30)
MAX_BODY_SIZE_MB Max body size in MB (default: 100, 0=unlimited)
MAX_CONNECTIONS Max concurrent connections (default: 10000, 0=unlimited)
Full reference: https://crates.io/crates/wisegate";
#[derive(Parser)]
#[command(name = env!("CARGO_PKG_NAME"))]
#[command(about = env!("CARGO_PKG_DESCRIPTION"))]
#[command(version = env!("CARGO_PKG_VERSION"))]
#[command(author = env!("CARGO_PKG_AUTHORS"))]
#[command(
long_about = "🧙 \"You shall not pass!\" - A wise guardian for your network gates\nAn efficient, secure reverse proxy with built-in rate limiting and IP filtering\n\nExample usage:\n wisegate --listen 8080 --forward 9000\n wisegate -l 8080 -f 9000 --verbose"
)]
#[command(after_help = AFTER_HELP)]
pub struct Args {
#[arg(
long,
short = 'b',
help = "Bind address for listening and forwarding",
value_name = "ADDRESS",
default_value = "0.0.0.0"
)]
pub bind: String,
#[arg(
long,
short = 'l',
help = "Listen port for incoming connections",
value_name = "PORT",
default_value = "8080"
)]
pub listen: u16,
#[arg(
long,
short = 'f',
help = "Destination port for forwarded requests",
value_name = "PORT",
default_value = "9000"
)]
pub forward: u16,
#[arg(
long,
short = 'v',
help = "Show detailed configuration and startup information"
)]
pub verbose: bool,
#[arg(
long,
short = 'q',
help = "Suppress configuration output, show only essential messages",
conflicts_with = "verbose"
)]
pub quiet: bool,
#[arg(long, help = "Output logs in JSON format for structured logging")]
pub json_logs: bool,
}
impl Args {
pub fn validate(&self) -> Result<IpAddr, String> {
if self.listen == self.forward {
return Err("Listen and forward ports cannot be the same".to_string());
}
if self.listen == 0 || self.forward == 0 {
return Err("Ports must be greater than 0".to_string());
}
self.bind
.parse::<IpAddr>()
.map_err(|_| format!("Invalid bind address: '{}'", self.bind))
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn test_validate_valid_configuration() {
let args = Args::try_parse_from(["wisegate", "-l", "8080", "-f", "9000"]).unwrap();
let result = args.validate();
assert!(result.is_ok());
assert_eq!(result.unwrap().to_string(), "0.0.0.0");
}
#[test]
fn test_validate_same_ports_error() {
let args = Args::try_parse_from(["wisegate", "-l", "8080", "-f", "8080"]).unwrap();
let result = args.validate();
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
"Listen and forward ports cannot be the same"
);
}
#[test]
fn test_validate_listen_port_zero_error() {
let args = Args::try_parse_from(["wisegate", "-l", "0", "-f", "9000"]).unwrap();
let result = args.validate();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Ports must be greater than 0");
}
#[test]
fn test_validate_forward_port_zero_error() {
let args = Args::try_parse_from(["wisegate", "-l", "8080", "-f", "0"]).unwrap();
let result = args.validate();
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Ports must be greater than 0");
}
#[test]
fn test_validate_both_ports_zero_error() {
let args = Args::try_parse_from(["wisegate", "-l", "0", "-f", "0"]).unwrap();
let result = args.validate();
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
"Listen and forward ports cannot be the same"
);
}
#[test]
fn test_validate_invalid_bind_address() {
let args = Args::try_parse_from(["wisegate", "-l", "8080", "-f", "9000", "-b", "invalid"])
.unwrap();
let result = args.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid bind address"));
}
#[test]
fn test_validate_valid_ipv4_bind() {
let args =
Args::try_parse_from(["wisegate", "-l", "8080", "-f", "9000", "-b", "127.0.0.1"])
.unwrap();
let result = args.validate();
assert!(result.is_ok());
assert_eq!(result.unwrap().to_string(), "127.0.0.1");
}
#[test]
fn test_validate_valid_ipv6_bind() {
let args =
Args::try_parse_from(["wisegate", "-l", "8080", "-f", "9000", "-b", "::1"]).unwrap();
let result = args.validate();
assert!(result.is_ok());
assert_eq!(result.unwrap().to_string(), "::1");
}
#[test]
fn test_default_values() {
let args = Args::try_parse_from(["wisegate"]).unwrap();
assert_eq!(args.bind, "0.0.0.0");
assert_eq!(args.listen, 8080);
assert_eq!(args.forward, 9000);
assert!(!args.verbose);
assert!(!args.quiet);
assert!(!args.json_logs);
}
#[test]
fn test_verbose_flag() {
let args = Args::try_parse_from(["wisegate", "--verbose"]).unwrap();
assert!(args.verbose);
assert!(!args.quiet);
}
#[test]
fn test_quiet_flag() {
let args = Args::try_parse_from(["wisegate", "--quiet"]).unwrap();
assert!(!args.verbose);
assert!(args.quiet);
}
#[test]
fn test_json_logs_flag() {
let args = Args::try_parse_from(["wisegate", "--json-logs"]).unwrap();
assert!(args.json_logs);
}
#[test]
fn test_verbose_and_quiet_conflict() {
let result = Args::try_parse_from(["wisegate", "--verbose", "--quiet"]);
assert!(result.is_err());
}
#[test]
fn test_short_flags() {
let args = Args::try_parse_from([
"wisegate",
"-l",
"3000",
"-f",
"4000",
"-b",
"127.0.0.1",
"-v",
])
.unwrap();
assert_eq!(args.listen, 3000);
assert_eq!(args.forward, 4000);
assert_eq!(args.bind, "127.0.0.1");
assert!(args.verbose);
}
#[test]
fn test_max_port_value() {
let args = Args::try_parse_from(["wisegate", "-l", "65535", "-f", "65534"]).unwrap();
let result = args.validate();
assert!(result.is_ok());
}
#[test]
fn test_port_1() {
let args = Args::try_parse_from(["wisegate", "-l", "1", "-f", "2"]).unwrap();
let result = args.validate();
assert!(result.is_ok());
}
}