123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889 |
- //! Command-line option parsing.
- use std::ffi::OsStr;
- use std::fmt;
- use log::*;
- use dns::{QClass, Labels};
- use dns::record::RecordType;
- use crate::connect::TransportType;
- use crate::output::{OutputFormat, UseColours, TextFormat};
- use crate::requests::{RequestGenerator, Inputs, ProtocolTweaks, UseEDNS};
- use crate::resolve::Resolver;
- use crate::txid::TxidGenerator;
- /// The command-line options used when running dog.
- #[derive(PartialEq, Debug)]
- pub struct Options {
- /// The requests to make and how they should be generated.
- pub requests: RequestGenerator,
- /// Whether to display the time taken after every query.
- pub measure_time: bool,
- /// How to format the output data.
- pub format: OutputFormat,
- }
- impl Options {
- /// Parses and interprets a set of options from the user’s command-line
- /// arguments.
- ///
- /// This returns an `Ok` set of options if successful and running
- /// normally, a `Help` or `Version` variant if one of those options is
- /// specified, or an error variant if there’s an invalid option or
- /// inconsistency within the options after they were parsed.
- #[allow(unused_results)]
- pub fn getopts<C>(args: C) -> OptionsResult
- where C: IntoIterator,
- C::Item: AsRef<OsStr>,
- {
- let mut opts = getopts::Options::new();
- // Query options
- opts.optmulti("q", "query", "Host name or domain name to query", "HOST");
- opts.optmulti("t", "type", "Type of the DNS record being queried (A, MX, NS...)", "TYPE");
- opts.optmulti("n", "nameserver", "Address of the nameserver to send packets to", "ADDR");
- opts.optmulti("", "class", "Network class of the DNS record being queried (IN, CH, HS)", "CLASS");
- // Sending options
- opts.optopt ("", "edns", "Whether to OPT in to EDNS (disable, hide, show)", "SETTING");
- opts.optopt ("", "txid", "Set the transaction ID to a specific value", "NUMBER");
- opts.optmulti("Z", "", "Set uncommon protocol tweaks", "TWEAKS");
- // Protocol options
- opts.optflag ("U", "udp", "Use the DNS protocol over UDP");
- opts.optflag ("T", "tcp", "Use the DNS protocol over TCP");
- opts.optflag ("S", "tls", "Use the DNS-over-TLS protocol");
- opts.optflag ("H", "https", "Use the DNS-over-HTTPS protocol");
- // Output options
- opts.optopt ("", "color", "When to use terminal colors", "WHEN");
- opts.optopt ("", "colour", "When to use terminal colours", "WHEN");
- opts.optflag ("J", "json", "Display the output as JSON");
- opts.optflag ("", "seconds", "Do not format durations, display them as seconds");
- opts.optflag ("1", "short", "Short mode: display nothing but the first result");
- opts.optflag ("", "time", "Print how long the response took to arrive");
- // Meta options
- opts.optflag ("v", "version", "Print version information");
- opts.optflag ("?", "help", "Print list of command-line options");
- let matches = match opts.parse(args) {
- Ok(m) => m,
- Err(e) => return OptionsResult::InvalidOptionsFormat(e),
- };
- let uc = UseColours::deduce(&matches);
- if matches.opt_present("version") {
- OptionsResult::Version(uc)
- }
- else if matches.opt_present("help") {
- OptionsResult::Help(HelpReason::Flag, uc)
- }
- else {
- match Self::deduce(matches) {
- Ok(opts) => {
- if opts.requests.inputs.domains.is_empty() {
- OptionsResult::Help(HelpReason::NoDomains, uc)
- }
- else {
- OptionsResult::Ok(opts)
- }
- }
- Err(e) => {
- OptionsResult::InvalidOptions(e)
- }
- }
- }
- }
- fn deduce(matches: getopts::Matches) -> Result<Self, OptionsError> {
- let measure_time = matches.opt_present("time");
- let format = OutputFormat::deduce(&matches);
- let requests = RequestGenerator::deduce(matches)?;
- Ok(Self { requests, measure_time, format })
- }
- }
- impl RequestGenerator {
- fn deduce(matches: getopts::Matches) -> Result<Self, OptionsError> {
- let edns = UseEDNS::deduce(&matches)?;
- let txid_generator = TxidGenerator::deduce(&matches)?;
- let protocol_tweaks = ProtocolTweaks::deduce(&matches)?;
- let inputs = Inputs::deduce(matches)?;
- Ok(Self { inputs, txid_generator, edns, protocol_tweaks })
- }
- }
- impl Inputs {
- fn deduce(matches: getopts::Matches) -> Result<Self, OptionsError> {
- let mut inputs = Self::default();
- inputs.load_transport_types(&matches);
- inputs.load_named_args(&matches)?;
- inputs.load_free_args(matches)?;
- inputs.check_for_missing_nameserver()?;
- inputs.load_fallbacks();
- Ok(inputs)
- }
- fn load_transport_types(&mut self, matches: &getopts::Matches) {
- if matches.opt_present("https") {
- self.transport_types.push(TransportType::HTTPS);
- }
- if matches.opt_present("tls") {
- self.transport_types.push(TransportType::TLS);
- }
- if matches.opt_present("tcp") {
- self.transport_types.push(TransportType::TCP);
- }
- if matches.opt_present("udp") {
- self.transport_types.push(TransportType::UDP);
- }
- }
- fn load_named_args(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> {
- for domain in matches.opt_strs("query") {
- match Labels::encode(&domain) {
- Ok(d) => self.domains.push(d),
- Err(e) => return Err(OptionsError::InvalidDomain(e.into())),
- }
- }
- for qtype in matches.opt_strs("type") {
- self.add_type(&qtype)?;
- }
- for ns in matches.opt_strs("nameserver") {
- self.add_nameserver(&ns)?;
- }
- for qclass in matches.opt_strs("class") {
- self.add_class(&qclass)?;
- }
- Ok(())
- }
- fn add_type(&mut self, input: &str) -> Result<(), OptionsError> {
- if input.eq_ignore_ascii_case("OPT") {
- return Err(OptionsError::QueryTypeOPT);
- }
- let input_uppercase = input.to_ascii_uppercase();
- if let Some(rt) = RecordType::from_type_name(&input_uppercase) {
- self.types.push(rt);
- Ok(())
- }
- else if let Ok(type_number) = input.parse::<u16>() {
- self.types.push(RecordType::from(type_number));
- Ok(())
- }
- else {
- Err(OptionsError::InvalidQueryType(input.into()))
- }
- }
- fn add_nameserver(&mut self, input: &str) -> Result<(), OptionsError> {
- self.resolvers.push(Resolver::specified(input.into()));
- Ok(())
- }
- fn parse_class_name(&self, input: &str) -> Option<QClass> {
- if input.eq_ignore_ascii_case("IN") {
- Some(QClass::IN)
- }
- else if input.eq_ignore_ascii_case("CH") {
- Some(QClass::CH)
- }
- else if input.eq_ignore_ascii_case("HS") {
- Some(QClass::HS)
- }
- else {
- None
- }
- }
- fn add_class(&mut self, input: &str) -> Result<(), OptionsError> {
- let qclass = self.parse_class_name(input)
- .or_else(|| input.parse().ok().map(QClass::Other));
- match qclass {
- Some(c) => Ok(self.classes.push(c)),
- None => Err(OptionsError::InvalidQueryClass(input.into())),
- }
- }
- fn load_free_args(&mut self, matches: getopts::Matches) -> Result<(), OptionsError> {
- for argument in matches.free {
- if let Some(nameserver) = argument.strip_prefix('@') {
- trace!("Got nameserver -> {:?}", nameserver);
- self.add_nameserver(nameserver)?;
- }
- else if Self::is_constant_name(&argument) {
- if let Some(class) = self.parse_class_name(&argument) {
- trace!("Got qclass -> {:?}", &argument);
- self.classes.push(class);
- }
- else {
- trace!("Got qtype -> {:?}", &argument);
- self.add_type(&argument)?;
- }
- }
- else {
- trace!("Got domain -> {:?}", &argument);
- match Labels::encode(&argument) {
- Ok(d) => self.domains.push(d),
- Err(e) => return Err(OptionsError::InvalidDomain(e.into())),
- }
- }
- }
- Ok(())
- }
- fn is_constant_name(argument: &str) -> bool {
- let first_char = match argument.chars().next() {
- Some(c) => c,
- None => return false,
- };
- if ! first_char.is_ascii_alphabetic() {
- return false;
- }
- argument.chars().all(|c| c.is_ascii_alphanumeric())
- }
- fn load_fallbacks(&mut self) {
- if self.types.is_empty() {
- self.types.push(RecordType::A);
- }
- if self.classes.is_empty() {
- self.classes.push(QClass::IN);
- }
- if self.resolvers.is_empty() {
- self.resolvers.push(Resolver::system_default());
- }
- if self.transport_types.is_empty() {
- self.transport_types.push(TransportType::Automatic);
- }
- }
- fn check_for_missing_nameserver(&self) -> Result<(), OptionsError> {
- if self.resolvers.is_empty() && self.transport_types == [TransportType::HTTPS] {
- Err(OptionsError::MissingHttpsUrl)
- }
- else {
- Ok(())
- }
- }
- }
- impl TxidGenerator {
- fn deduce(matches: &getopts::Matches) -> Result<Self, OptionsError> {
- if let Some(starting_txid) = matches.opt_str("txid") {
- if let Some(start) = parse_dec_or_hex(&starting_txid) {
- Ok(Self::Sequence(start))
- }
- else {
- Err(OptionsError::InvalidTxid(starting_txid))
- }
- }
- else {
- Ok(Self::Random)
- }
- }
- }
- fn parse_dec_or_hex(input: &str) -> Option<u16> {
- if let Some(hex_str) = input.strip_prefix("0x") {
- match u16::from_str_radix(hex_str, 16) {
- Ok(num) => {
- Some(num)
- }
- Err(e) => {
- warn!("Error parsing hex number: {}", e);
- None
- }
- }
- }
- else {
- match input.parse() {
- Ok(num) => {
- Some(num)
- }
- Err(e) => {
- warn!("Error parsing number: {}", e);
- None
- }
- }
- }
- }
- impl OutputFormat {
- fn deduce(matches: &getopts::Matches) -> Self {
- if matches.opt_present("short") {
- let summary_format = TextFormat::deduce(matches);
- Self::Short(summary_format)
- }
- else if matches.opt_present("json") {
- Self::JSON
- }
- else {
- let use_colours = UseColours::deduce(matches);
- let summary_format = TextFormat::deduce(matches);
- Self::Text(use_colours, summary_format)
- }
- }
- }
- impl UseColours {
- fn deduce(matches: &getopts::Matches) -> Self {
- match matches.opt_str("color").or_else(|| matches.opt_str("colour")).unwrap_or_default().as_str() {
- "automatic" | "auto" | "" => Self::Automatic,
- "always" | "yes" => Self::Always,
- "never" | "no" => Self::Never,
- otherwise => {
- warn!("Unknown colour setting {:?}", otherwise);
- Self::Automatic
- },
- }
- }
- }
- impl TextFormat {
- fn deduce(matches: &getopts::Matches) -> Self {
- let format_durations = ! matches.opt_present("seconds");
- Self { format_durations }
- }
- }
- impl UseEDNS {
- fn deduce(matches: &getopts::Matches) -> Result<Self, OptionsError> {
- if let Some(edns) = matches.opt_str("edns") {
- match edns.as_str() {
- "disable" | "off" => Ok(Self::Disable),
- "hide" => Ok(Self::SendAndHide),
- "show" => Ok(Self::SendAndShow),
- oh => Err(OptionsError::InvalidEDNS(oh.into())),
- }
- }
- else {
- Ok(Self::SendAndHide)
- }
- }
- }
- impl ProtocolTweaks {
- fn deduce(matches: &getopts::Matches) -> Result<Self, OptionsError> {
- let mut tweaks = Self::default();
- for tweak_str in matches.opt_strs("Z") {
- match &*tweak_str {
- "aa" | "authoritative" => {
- tweaks.set_authoritative_flag = true;
- }
- "ad" | "authentic" => {
- tweaks.set_authentic_flag = true;
- }
- "cd" | "checking-disabled" => {
- tweaks.set_checking_disabled_flag = true;
- }
- otherwise => {
- if let Some(remaining_num) = tweak_str.strip_prefix("bufsize=") {
- match remaining_num.parse() {
- Ok(parsed_bufsize) => {
- tweaks.udp_payload_size = Some(parsed_bufsize);
- continue;
- }
- Err(e) => {
- warn!("Failed to parse buffer size: {}", e);
- }
- }
- }
- return Err(OptionsError::InvalidTweak(otherwise.into()));
- }
- }
- }
- Ok(tweaks)
- }
- }
- /// The result of the `Options::getopts` function.
- #[derive(PartialEq, Debug)]
- pub enum OptionsResult {
- /// The options were parsed successfully.
- Ok(Options),
- /// There was an error (from `getopts`) parsing the arguments.
- InvalidOptionsFormat(getopts::Fail),
- /// There was an error with the combination of options the user selected.
- InvalidOptions(OptionsError),
- /// Can’t run any checks because there’s help to display!
- Help(HelpReason, UseColours),
- /// One of the arguments was `--version`, to display the version number.
- Version(UseColours),
- }
- /// The reason that help is being displayed. If it’s for the `--help` flag,
- /// then we shouldn’t return an error exit status.
- #[derive(PartialEq, Debug, Copy, Clone)]
- pub enum HelpReason {
- /// Help was requested with the `--help` flag.
- Flag,
- /// There were no domains being queried, so display help instead.
- /// Unlike `dig`, we don’t implicitly search for the root domain.
- NoDomains,
- }
- /// Something wrong with the combination of options the user has picked.
- #[derive(PartialEq, Debug)]
- pub enum OptionsError {
- InvalidDomain(String),
- InvalidEDNS(String),
- InvalidQueryType(String),
- InvalidQueryClass(String),
- InvalidTxid(String),
- InvalidTweak(String),
- QueryTypeOPT,
- MissingHttpsUrl,
- }
- impl fmt::Display for OptionsError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::InvalidDomain(domain) => write!(f, "Invalid domain {:?}", domain),
- Self::InvalidEDNS(edns) => write!(f, "Invalid EDNS setting {:?}", edns),
- Self::InvalidQueryType(qt) => write!(f, "Invalid query type {:?}", qt),
- Self::InvalidQueryClass(qc) => write!(f, "Invalid query class {:?}", qc),
- Self::InvalidTxid(txid) => write!(f, "Invalid transaction ID {:?}", txid),
- Self::InvalidTweak(tweak) => write!(f, "Invalid protocol tweak {:?}", tweak),
- Self::QueryTypeOPT => write!(f, "OPT request is sent by default (see -Z flag)"),
- Self::MissingHttpsUrl => write!(f, "You must pass a URL as a nameserver when using --https"),
- }
- }
- }
- #[cfg(test)]
- mod test {
- use super::*;
- use pretty_assertions::assert_eq;
- impl Inputs {
- fn fallbacks() -> Self {
- Inputs {
- domains: vec![ /* No domains by default */ ],
- types: vec![ RecordType::A ],
- classes: vec![ QClass::IN ],
- resolvers: vec![ Resolver::system_default() ],
- transport_types: vec![ TransportType::Automatic ],
- }
- }
- }
- impl OptionsResult {
- fn unwrap(self) -> Options {
- match self {
- Self::Ok(o) => o,
- _ => panic!("{:?}", self),
- }
- }
- }
- // help tests
- #[test]
- fn help() {
- assert_eq!(Options::getopts(&[ "--help" ]),
- OptionsResult::Help(HelpReason::Flag, UseColours::Automatic));
- }
- #[test]
- fn help_no_colour() {
- assert_eq!(Options::getopts(&[ "--help", "--colour=never" ]),
- OptionsResult::Help(HelpReason::Flag, UseColours::Never));
- }
- #[test]
- fn version() {
- assert_eq!(Options::getopts(&[ "--version" ]),
- OptionsResult::Version(UseColours::Automatic));
- }
- #[test]
- fn version_yes_color() {
- assert_eq!(Options::getopts(&[ "--version", "--color", "always" ]),
- OptionsResult::Version(UseColours::Always));
- }
- #[test]
- fn fail() {
- assert_eq!(Options::getopts(&[ "--pear" ]),
- OptionsResult::InvalidOptionsFormat(getopts::Fail::UnrecognizedOption("pear".into())));
- }
- #[test]
- fn empty() {
- let nothing: Vec<&str> = vec![];
- assert_eq!(Options::getopts(nothing),
- OptionsResult::Help(HelpReason::NoDomains, UseColours::Automatic));
- }
- #[test]
- fn an_unrelated_argument() {
- assert_eq!(Options::getopts(&[ "--time" ]),
- OptionsResult::Help(HelpReason::NoDomains, UseColours::Automatic));
- }
- // query tests
- #[test]
- fn just_domain() {
- let options = Options::getopts(&[ "lookup.dog" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn just_named_domain() {
- let options = Options::getopts(&[ "-q", "lookup.dog" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn domain_and_type() {
- let options = Options::getopts(&[ "lookup.dog", "SOA" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- types: vec![ RecordType::SOA ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn domain_and_type_lowercase() {
- let options = Options::getopts(&[ "lookup.dog", "soa" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- types: vec![ RecordType::SOA ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn domain_and_nameserver() {
- let options = Options::getopts(&[ "lookup.dog", "@1.1.1.1" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- resolvers: vec![ Resolver::specified("1.1.1.1".into()) ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn domain_and_class() {
- let options = Options::getopts(&[ "lookup.dog", "CH" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- classes: vec![ QClass::CH ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn domain_and_class_lowercase() {
- let options = Options::getopts(&[ "lookup.dog", "ch" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- classes: vec![ QClass::CH ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn all_free() {
- let options = Options::getopts(&[ "lookup.dog", "CH", "NS", "@1.1.1.1" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- classes: vec![ QClass::CH ],
- types: vec![ RecordType::NS ],
- resolvers: vec![ Resolver::specified("1.1.1.1".into()) ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn all_parameters() {
- let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "CH", "--type", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- classes: vec![ QClass::CH ],
- types: vec![ RecordType::SOA ],
- resolvers: vec![ Resolver::specified("1.1.1.1".into()) ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn all_parameters_lowercase() {
- let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "ch", "--type", "soa", "--nameserver", "1.1.1.1" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- classes: vec![ QClass::CH ],
- types: vec![ RecordType::SOA ],
- resolvers: vec![ Resolver::specified("1.1.1.1".into()) ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn two_types() {
- let options = Options::getopts(&[ "-q", "lookup.dog", "--type", "SRV", "--type", "AAAA" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- types: vec![ RecordType::SRV, RecordType::AAAA ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn two_classes() {
- let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "IN", "--class", "CH" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- classes: vec![ QClass::IN, QClass::CH ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn all_mixed_1() {
- let options = Options::getopts(&[ "lookup.dog", "--class", "CH", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- classes: vec![ QClass::CH ],
- types: vec![ RecordType::SOA ],
- resolvers: vec![ Resolver::specified("1.1.1.1".into()) ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn all_mixed_2() {
- let options = Options::getopts(&[ "CH", "SOA", "MX", "IN", "-q", "lookup.dog", "--class", "HS" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- classes: vec![ QClass::HS, QClass::CH, QClass::IN ],
- types: vec![ RecordType::SOA, RecordType::MX ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn all_mixed_3() {
- let options = Options::getopts(&[ "lookup.dog", "--nameserver", "1.1.1.1", "--nameserver", "1.0.0.1" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("lookup.dog").unwrap() ],
- resolvers: vec![ Resolver::specified("1.1.1.1".into()),
- Resolver::specified("1.0.0.1".into()), ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn explicit_numerics() {
- let options = Options::getopts(&[ "11", "--class", "22", "--type", "33" ]).unwrap();
- assert_eq!(options.requests.inputs, Inputs {
- domains: vec![ Labels::encode("11").unwrap() ],
- classes: vec![ QClass::Other(22) ],
- types: vec![ RecordType::from(33) ],
- .. Inputs::fallbacks()
- });
- }
- #[test]
- fn edns_and_tweaks() {
- let options = Options::getopts(&[ "dom.ain", "--edns", "show", "-Z", "authentic" ]).unwrap();
- assert_eq!(options.requests.edns, UseEDNS::SendAndShow);
- assert_eq!(options.requests.protocol_tweaks.set_authentic_flag, true);
- }
- #[test]
- fn two_more_tweaks() {
- let options = Options::getopts(&[ "dom.ain", "-Z", "aa", "-Z", "cd" ]).unwrap();
- assert_eq!(options.requests.protocol_tweaks.set_authoritative_flag, true);
- assert_eq!(options.requests.protocol_tweaks.set_checking_disabled_flag, true);
- }
- #[test]
- fn udp_size() {
- let options = Options::getopts(&[ "dom.ain", "-Z", "bufsize=4096" ]).unwrap();
- assert_eq!(options.requests.protocol_tweaks.udp_payload_size, Some(4096));
- }
- #[test]
- fn udp_size_invalid() {
- assert_eq!(Options::getopts(&[ "-Z", "bufsize=null" ]),
- OptionsResult::InvalidOptions(OptionsError::InvalidTweak("bufsize=null".into())));
- }
- #[test]
- fn udp_size_too_big() {
- assert_eq!(Options::getopts(&[ "-Z", "bufsize=999999999" ]),
- OptionsResult::InvalidOptions(OptionsError::InvalidTweak("bufsize=999999999".into())));
- }
- #[test]
- fn short_mode() {
- let tf = TextFormat { format_durations: true };
- let options = Options::getopts(&[ "dom.ain", "--short" ]).unwrap();
- assert_eq!(options.format, OutputFormat::Short(tf));
- let tf = TextFormat { format_durations: false };
- let options = Options::getopts(&[ "dom.ain", "--short", "--seconds" ]).unwrap();
- assert_eq!(options.format, OutputFormat::Short(tf));
- }
- #[test]
- fn json_output() {
- let options = Options::getopts(&[ "dom.ain", "--json" ]).unwrap();
- assert_eq!(options.format, OutputFormat::JSON);
- }
- #[test]
- fn specific_txid() {
- let options = Options::getopts(&[ "dom.ain", "--txid", "1234" ]).unwrap();
- assert_eq!(options.requests.txid_generator,
- TxidGenerator::Sequence(1234));
- }
- #[test]
- fn all_transport_types() {
- use crate::connect::TransportType::*;
- let options = Options::getopts(&[ "dom.ain", "--https", "--tls", "--tcp", "--udp" ]).unwrap();
- assert_eq!(options.requests.inputs.transport_types,
- vec![ HTTPS, TLS, TCP, UDP ]);
- }
- // invalid options tests
- #[test]
- fn invalid_named_class() {
- assert_eq!(Options::getopts(&[ "lookup.dog", "--class", "tubes" ]),
- OptionsResult::InvalidOptions(OptionsError::InvalidQueryClass("tubes".into())));
- }
- #[test]
- fn invalid_named_class_too_big() {
- assert_eq!(Options::getopts(&[ "lookup.dog", "--class", "999999" ]),
- OptionsResult::InvalidOptions(OptionsError::InvalidQueryClass("999999".into())));
- }
- #[test]
- fn invalid_named_type() {
- assert_eq!(Options::getopts(&[ "lookup.dog", "--type", "tubes" ]),
- OptionsResult::InvalidOptions(OptionsError::InvalidQueryType("tubes".into())));
- }
- #[test]
- fn invalid_named_type_too_big() {
- assert_eq!(Options::getopts(&[ "lookup.dog", "--type", "999999" ]),
- OptionsResult::InvalidOptions(OptionsError::InvalidQueryType("999999".into())));
- }
- #[test]
- fn invalid_capsword() {
- assert_eq!(Options::getopts(&[ "SMH", "lookup.dog" ]),
- OptionsResult::InvalidOptions(OptionsError::InvalidQueryType("SMH".into())));
- }
- #[test]
- fn invalid_txid() {
- assert_eq!(Options::getopts(&[ "lookup.dog", "--txid=0x10000" ]),
- OptionsResult::InvalidOptions(OptionsError::InvalidTxid("0x10000".into())));
- }
- #[test]
- fn invalid_edns() {
- assert_eq!(Options::getopts(&[ "--edns=yep" ]),
- OptionsResult::InvalidOptions(OptionsError::InvalidEDNS("yep".into())));
- }
- #[test]
- fn invalid_tweaks() {
- assert_eq!(Options::getopts(&[ "-Zsleep" ]),
- OptionsResult::InvalidOptions(OptionsError::InvalidTweak("sleep".into())));
- }
- #[test]
- fn opt() {
- assert_eq!(Options::getopts(&[ "OPT", "lookup.dog" ]),
- OptionsResult::InvalidOptions(OptionsError::QueryTypeOPT));
- }
- #[test]
- fn opt_lowercase() {
- assert_eq!(Options::getopts(&[ "opt", "lookup.dog" ]),
- OptionsResult::InvalidOptions(OptionsError::QueryTypeOPT));
- }
- #[test]
- fn missing_https_url() {
- assert_eq!(Options::getopts(&[ "--https", "lookup.dog" ]),
- OptionsResult::InvalidOptions(OptionsError::MissingHttpsUrl));
- }
- // txid tests
- #[test]
- fn number_parsing() {
- assert_eq!(parse_dec_or_hex("1234"), Some(1234));
- assert_eq!(parse_dec_or_hex("0x1234"), Some(0x1234));
- assert_eq!(parse_dec_or_hex("0xABcd"), Some(0xABcd));
- assert_eq!(parse_dec_or_hex("65536"), None);
- assert_eq!(parse_dec_or_hex("0x65536"), None);
- assert_eq!(parse_dec_or_hex(""), None);
- assert_eq!(parse_dec_or_hex("0x"), None);
- }
- }
|