use crate::{config::Config, PROGRAM_NAME};
use std::{
env,
error::Error,
io::{stdout, Write},
net::{IpAddr, Ipv4Addr},
process,
time::Duration,
};
use viaspf::Sender;
const USAGE_TEXT: &str = "\
[options...] <sender> [<ip>]
Options:
-H, --helo-domain <name> The client's HELO domain name
-h, --help Print usage information
-R, --hostname <name> The receiving host's domain name
-w, --line-width <number> Maximum width of wrapped lines
-n, --max-lookups <number> Maximum number of DNS lookups
-o, --max-void-lookups <number> Maximum number of void lookups
-s, --system-resolver Use the resolver configured on the system
-t, --time Display query and lookup times
-T, --timeout-secs <number> Seconds until a query times out
-V, --version Print version information
";
pub struct Args {
pub config: Config,
pub sender: Sender,
pub ip: IpAddr,
}
impl Args {
pub fn parse() -> Result<Self, Box<dyn Error>> {
let mut args = env::args_os()
.skip(1)
.map(|s| s.into_string().map_err(|_| "invalid UTF-8 bytes in argument"));
let mut config = Config::default();
let sender = loop {
let arg = args.next().ok_or("required argument <sender> missing")??;
let missing_value = || format!("missing value for option {arg}");
let invalid_value = |s: &str| format!("invalid value for option {arg}: \"{s}\"");
match arg.as_str() {
"-h" | "--help" => {
write!(stdout(), "Usage: {PROGRAM_NAME} {USAGE_TEXT}")?;
process::exit(0);
}
"-V" | "--version" => {
writeln!(stdout(), "{PROGRAM_NAME} {}", env!("CARGO_PKG_VERSION"))?;
process::exit(0);
}
"--debug" => {
config.debug = true;
}
"-s" | "--system-resolver" => {
config.system_resolver = true;
}
"-t" | "--time" => {
config.time = true;
}
"-H" | "--helo-domain" => {
let arg = args.next().ok_or_else(missing_value)??;
let helo_domain = arg.parse().map_err(|_| invalid_value(&arg))?;
config.helo_domain = Some(helo_domain);
}
"-R" | "--hostname" => {
let hostname = args.next().ok_or_else(missing_value)??;
config.hostname = Some(hostname);
}
"-w" | "--line-width" => {
let arg = args.next().ok_or_else(missing_value)??;
let line_width = arg.parse().map_err(|_| invalid_value(&arg))?;
config.line_width = to_usize(line_width)?;
}
"-n" | "--max-lookups" => {
let arg = args.next().ok_or_else(missing_value)??;
let max_lookups = arg.parse().map_err(|_| invalid_value(&arg))?;
config.max_lookups = Some(to_usize(max_lookups)?);
}
"-o" | "--max-void-lookups" => {
let arg = args.next().ok_or_else(missing_value)??;
let max_void_lookups = arg.parse().map_err(|_| invalid_value(&arg))?;
config.max_void_lookups = Some(to_usize(max_void_lookups)?);
}
"-T" | "--timeout-secs" => {
let arg = args.next().ok_or_else(missing_value)??;
let timeout_secs = arg.parse::<u32>().map_err(|_| invalid_value(&arg))?;
config.timeout = Duration::from_secs(timeout_secs.into());
}
arg => {
if arg.starts_with('-')
&& arg.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
{
return Err(format!("unrecognized option: \"{arg}\"").into());
}
break Sender::new(arg)
.map_err(|_| format!("invalid sender identity: \"{arg}\""))?;
}
}
};
let ip = match args.next() {
Some(arg) => {
let arg = arg?;
arg.parse()
.map_err(|_| format!("invalid IP address: \"{arg}\""))?
}
None => IpAddr::V4(Ipv4Addr::UNSPECIFIED),
};
if args.next().is_some() {
return Err("too many arguments".into());
}
Ok(Self { config, sender, ip })
}
}
fn to_usize(n: u32) -> Result<usize, &'static str> {
n.try_into().map_err(|_| "unsupported pointer size")
}