options.rs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  1. //! Command-line option parsing.
  2. use std::ffi::OsStr;
  3. use std::fmt;
  4. use log::*;
  5. use dns::{QClass, Labels};
  6. use dns::record::RecordType;
  7. use crate::connect::TransportType;
  8. use crate::output::{OutputFormat, UseColours, TextFormat};
  9. use crate::requests::{RequestGenerator, Inputs, ProtocolTweaks, UseEDNS};
  10. use crate::resolve::Resolver;
  11. use crate::txid::TxidGenerator;
  12. /// The command-line options used when running dog.
  13. #[derive(PartialEq, Debug)]
  14. pub struct Options {
  15. /// The requests to make and how they should be generated.
  16. pub requests: RequestGenerator,
  17. /// Whether to display the time taken after every query.
  18. pub measure_time: bool,
  19. /// How to format the output data.
  20. pub format: OutputFormat,
  21. }
  22. impl Options {
  23. /// Parses and interprets a set of options from the user’s command-line
  24. /// arguments.
  25. ///
  26. /// This returns an `Ok` set of options if successful and running
  27. /// normally, a `Help` or `Version` variant if one of those options is
  28. /// specified, or an error variant if there’s an invalid option or
  29. /// inconsistency within the options after they were parsed.
  30. #[allow(unused_results)]
  31. pub fn getopts<C>(args: C) -> OptionsResult
  32. where C: IntoIterator,
  33. C::Item: AsRef<OsStr>,
  34. {
  35. let mut opts = getopts::Options::new();
  36. // Query options
  37. opts.optmulti("q", "query", "Host name or domain name to query", "HOST");
  38. opts.optmulti("t", "type", "Type of the DNS record being queried (A, MX, NS...)", "TYPE");
  39. opts.optmulti("n", "nameserver", "Address of the nameserver to send packets to", "ADDR");
  40. opts.optmulti("", "class", "Network class of the DNS record being queried (IN, CH, HS)", "CLASS");
  41. // Sending options
  42. opts.optopt ("", "edns", "Whether to OPT in to EDNS (disable, hide, show)", "SETTING");
  43. opts.optopt ("", "txid", "Set the transaction ID to a specific value", "NUMBER");
  44. opts.optmulti("Z", "", "Set uncommon protocol tweaks", "TWEAKS");
  45. // Protocol options
  46. opts.optflag ("U", "udp", "Use the DNS protocol over UDP");
  47. opts.optflag ("T", "tcp", "Use the DNS protocol over TCP");
  48. opts.optflag ("S", "tls", "Use the DNS-over-TLS protocol");
  49. opts.optflag ("H", "https", "Use the DNS-over-HTTPS protocol");
  50. // Output options
  51. opts.optopt ("", "color", "When to use terminal colors", "WHEN");
  52. opts.optopt ("", "colour", "When to use terminal colours", "WHEN");
  53. opts.optflag ("J", "json", "Display the output as JSON");
  54. opts.optflag ("", "seconds", "Do not format durations, display them as seconds");
  55. opts.optflag ("1", "short", "Short mode: display nothing but the first result");
  56. opts.optflag ("", "time", "Print how long the response took to arrive");
  57. // Meta options
  58. opts.optflag ("v", "version", "Print version information");
  59. opts.optflag ("?", "help", "Print list of command-line options");
  60. let matches = match opts.parse(args) {
  61. Ok(m) => m,
  62. Err(e) => return OptionsResult::InvalidOptionsFormat(e),
  63. };
  64. let uc = UseColours::deduce(&matches);
  65. if matches.opt_present("version") {
  66. OptionsResult::Version(uc)
  67. }
  68. else if matches.opt_present("help") {
  69. OptionsResult::Help(HelpReason::Flag, uc)
  70. }
  71. else {
  72. match Self::deduce(matches) {
  73. Ok(opts) => {
  74. if opts.requests.inputs.domains.is_empty() {
  75. OptionsResult::Help(HelpReason::NoDomains, uc)
  76. }
  77. else {
  78. OptionsResult::Ok(opts)
  79. }
  80. }
  81. Err(e) => {
  82. OptionsResult::InvalidOptions(e)
  83. }
  84. }
  85. }
  86. }
  87. fn deduce(matches: getopts::Matches) -> Result<Self, OptionsError> {
  88. let measure_time = matches.opt_present("time");
  89. let format = OutputFormat::deduce(&matches);
  90. let requests = RequestGenerator::deduce(matches)?;
  91. Ok(Self { requests, measure_time, format })
  92. }
  93. }
  94. impl RequestGenerator {
  95. fn deduce(matches: getopts::Matches) -> Result<Self, OptionsError> {
  96. let edns = UseEDNS::deduce(&matches)?;
  97. let txid_generator = TxidGenerator::deduce(&matches)?;
  98. let protocol_tweaks = ProtocolTweaks::deduce(&matches)?;
  99. let inputs = Inputs::deduce(matches)?;
  100. Ok(Self { inputs, txid_generator, edns, protocol_tweaks })
  101. }
  102. }
  103. impl Inputs {
  104. fn deduce(matches: getopts::Matches) -> Result<Self, OptionsError> {
  105. let mut inputs = Self::default();
  106. inputs.load_transport_types(&matches);
  107. inputs.load_named_args(&matches)?;
  108. inputs.load_free_args(matches)?;
  109. inputs.check_for_missing_nameserver()?;
  110. inputs.load_fallbacks();
  111. Ok(inputs)
  112. }
  113. fn load_transport_types(&mut self, matches: &getopts::Matches) {
  114. if matches.opt_present("https") {
  115. self.transport_types.push(TransportType::HTTPS);
  116. }
  117. if matches.opt_present("tls") {
  118. self.transport_types.push(TransportType::TLS);
  119. }
  120. if matches.opt_present("tcp") {
  121. self.transport_types.push(TransportType::TCP);
  122. }
  123. if matches.opt_present("udp") {
  124. self.transport_types.push(TransportType::UDP);
  125. }
  126. }
  127. fn load_named_args(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> {
  128. for domain in matches.opt_strs("query") {
  129. match Labels::encode(&domain) {
  130. Ok(d) => self.domains.push(d),
  131. Err(e) => return Err(OptionsError::InvalidDomain(e.into())),
  132. }
  133. }
  134. for qtype in matches.opt_strs("type") {
  135. self.add_type(&qtype)?;
  136. }
  137. for ns in matches.opt_strs("nameserver") {
  138. self.add_nameserver(&ns)?;
  139. }
  140. for qclass in matches.opt_strs("class") {
  141. self.add_class(&qclass)?;
  142. }
  143. Ok(())
  144. }
  145. fn add_type(&mut self, input: &str) -> Result<(), OptionsError> {
  146. if input.eq_ignore_ascii_case("OPT") {
  147. return Err(OptionsError::QueryTypeOPT);
  148. }
  149. let input_uppercase = input.to_ascii_uppercase();
  150. if let Some(rt) = RecordType::from_type_name(&input_uppercase) {
  151. self.types.push(rt);
  152. Ok(())
  153. }
  154. else if let Ok(type_number) = input.parse::<u16>() {
  155. self.types.push(RecordType::from(type_number));
  156. Ok(())
  157. }
  158. else {
  159. Err(OptionsError::InvalidQueryType(input.into()))
  160. }
  161. }
  162. fn add_nameserver(&mut self, input: &str) -> Result<(), OptionsError> {
  163. self.resolvers.push(Resolver::specified(input.into()));
  164. Ok(())
  165. }
  166. fn parse_class_name(&self, input: &str) -> Option<QClass> {
  167. if input.eq_ignore_ascii_case("IN") {
  168. Some(QClass::IN)
  169. }
  170. else if input.eq_ignore_ascii_case("CH") {
  171. Some(QClass::CH)
  172. }
  173. else if input.eq_ignore_ascii_case("HS") {
  174. Some(QClass::HS)
  175. }
  176. else {
  177. None
  178. }
  179. }
  180. fn add_class(&mut self, input: &str) -> Result<(), OptionsError> {
  181. let qclass = self.parse_class_name(input)
  182. .or_else(|| input.parse().ok().map(QClass::Other));
  183. match qclass {
  184. Some(c) => Ok(self.classes.push(c)),
  185. None => Err(OptionsError::InvalidQueryClass(input.into())),
  186. }
  187. }
  188. fn load_free_args(&mut self, matches: getopts::Matches) -> Result<(), OptionsError> {
  189. for argument in matches.free {
  190. if let Some(nameserver) = argument.strip_prefix('@') {
  191. trace!("Got nameserver -> {:?}", nameserver);
  192. self.add_nameserver(nameserver)?;
  193. }
  194. else if Self::is_constant_name(&argument) {
  195. if let Some(class) = self.parse_class_name(&argument) {
  196. trace!("Got qclass -> {:?}", &argument);
  197. self.classes.push(class);
  198. }
  199. else {
  200. trace!("Got qtype -> {:?}", &argument);
  201. self.add_type(&argument)?;
  202. }
  203. }
  204. else {
  205. trace!("Got domain -> {:?}", &argument);
  206. match Labels::encode(&argument) {
  207. Ok(d) => self.domains.push(d),
  208. Err(e) => return Err(OptionsError::InvalidDomain(e.into())),
  209. }
  210. }
  211. }
  212. Ok(())
  213. }
  214. fn is_constant_name(argument: &str) -> bool {
  215. let first_char = match argument.chars().next() {
  216. Some(c) => c,
  217. None => return false,
  218. };
  219. if ! first_char.is_ascii_alphabetic() {
  220. return false;
  221. }
  222. argument.chars().all(|c| c.is_ascii_alphanumeric())
  223. }
  224. fn load_fallbacks(&mut self) {
  225. if self.types.is_empty() {
  226. self.types.push(RecordType::A);
  227. }
  228. if self.classes.is_empty() {
  229. self.classes.push(QClass::IN);
  230. }
  231. if self.resolvers.is_empty() {
  232. self.resolvers.push(Resolver::system_default());
  233. }
  234. if self.transport_types.is_empty() {
  235. self.transport_types.push(TransportType::Automatic);
  236. }
  237. }
  238. fn check_for_missing_nameserver(&self) -> Result<(), OptionsError> {
  239. if self.resolvers.is_empty() && self.transport_types == [TransportType::HTTPS] {
  240. Err(OptionsError::MissingHttpsUrl)
  241. }
  242. else {
  243. Ok(())
  244. }
  245. }
  246. }
  247. impl TxidGenerator {
  248. fn deduce(matches: &getopts::Matches) -> Result<Self, OptionsError> {
  249. if let Some(starting_txid) = matches.opt_str("txid") {
  250. if let Some(start) = parse_dec_or_hex(&starting_txid) {
  251. Ok(Self::Sequence(start))
  252. }
  253. else {
  254. Err(OptionsError::InvalidTxid(starting_txid))
  255. }
  256. }
  257. else {
  258. Ok(Self::Random)
  259. }
  260. }
  261. }
  262. fn parse_dec_or_hex(input: &str) -> Option<u16> {
  263. if let Some(hex_str) = input.strip_prefix("0x") {
  264. match u16::from_str_radix(hex_str, 16) {
  265. Ok(num) => {
  266. Some(num)
  267. }
  268. Err(e) => {
  269. warn!("Error parsing hex number: {}", e);
  270. None
  271. }
  272. }
  273. }
  274. else {
  275. match input.parse() {
  276. Ok(num) => {
  277. Some(num)
  278. }
  279. Err(e) => {
  280. warn!("Error parsing number: {}", e);
  281. None
  282. }
  283. }
  284. }
  285. }
  286. impl OutputFormat {
  287. fn deduce(matches: &getopts::Matches) -> Self {
  288. if matches.opt_present("short") {
  289. let summary_format = TextFormat::deduce(matches);
  290. Self::Short(summary_format)
  291. }
  292. else if matches.opt_present("json") {
  293. Self::JSON
  294. }
  295. else {
  296. let use_colours = UseColours::deduce(matches);
  297. let summary_format = TextFormat::deduce(matches);
  298. Self::Text(use_colours, summary_format)
  299. }
  300. }
  301. }
  302. impl UseColours {
  303. fn deduce(matches: &getopts::Matches) -> Self {
  304. match matches.opt_str("color").or_else(|| matches.opt_str("colour")).unwrap_or_default().as_str() {
  305. "automatic" | "auto" | "" => Self::Automatic,
  306. "always" | "yes" => Self::Always,
  307. "never" | "no" => Self::Never,
  308. otherwise => {
  309. warn!("Unknown colour setting {:?}", otherwise);
  310. Self::Automatic
  311. },
  312. }
  313. }
  314. }
  315. impl TextFormat {
  316. fn deduce(matches: &getopts::Matches) -> Self {
  317. let format_durations = ! matches.opt_present("seconds");
  318. Self { format_durations }
  319. }
  320. }
  321. impl UseEDNS {
  322. fn deduce(matches: &getopts::Matches) -> Result<Self, OptionsError> {
  323. if let Some(edns) = matches.opt_str("edns") {
  324. match edns.as_str() {
  325. "disable" | "off" => Ok(Self::Disable),
  326. "hide" => Ok(Self::SendAndHide),
  327. "show" => Ok(Self::SendAndShow),
  328. oh => Err(OptionsError::InvalidEDNS(oh.into())),
  329. }
  330. }
  331. else {
  332. Ok(Self::SendAndHide)
  333. }
  334. }
  335. }
  336. impl ProtocolTweaks {
  337. fn deduce(matches: &getopts::Matches) -> Result<Self, OptionsError> {
  338. let mut tweaks = Self::default();
  339. for tweak_str in matches.opt_strs("Z") {
  340. match &*tweak_str {
  341. "aa" | "authoritative" => {
  342. tweaks.set_authoritative_flag = true;
  343. }
  344. "ad" | "authentic" => {
  345. tweaks.set_authentic_flag = true;
  346. }
  347. "cd" | "checking-disabled" => {
  348. tweaks.set_checking_disabled_flag = true;
  349. }
  350. otherwise => {
  351. if let Some(remaining_num) = tweak_str.strip_prefix("bufsize=") {
  352. match remaining_num.parse() {
  353. Ok(parsed_bufsize) => {
  354. tweaks.udp_payload_size = Some(parsed_bufsize);
  355. continue;
  356. }
  357. Err(e) => {
  358. warn!("Failed to parse buffer size: {}", e);
  359. }
  360. }
  361. }
  362. return Err(OptionsError::InvalidTweak(otherwise.into()));
  363. }
  364. }
  365. }
  366. Ok(tweaks)
  367. }
  368. }
  369. /// The result of the `Options::getopts` function.
  370. #[derive(PartialEq, Debug)]
  371. pub enum OptionsResult {
  372. /// The options were parsed successfully.
  373. Ok(Options),
  374. /// There was an error (from `getopts`) parsing the arguments.
  375. InvalidOptionsFormat(getopts::Fail),
  376. /// There was an error with the combination of options the user selected.
  377. InvalidOptions(OptionsError),
  378. /// Can’t run any checks because there’s help to display!
  379. Help(HelpReason, UseColours),
  380. /// One of the arguments was `--version`, to display the version number.
  381. Version(UseColours),
  382. }
  383. /// The reason that help is being displayed. If it’s for the `--help` flag,
  384. /// then we shouldn’t return an error exit status.
  385. #[derive(PartialEq, Debug, Copy, Clone)]
  386. pub enum HelpReason {
  387. /// Help was requested with the `--help` flag.
  388. Flag,
  389. /// There were no domains being queried, so display help instead.
  390. /// Unlike `dig`, we don’t implicitly search for the root domain.
  391. NoDomains,
  392. }
  393. /// Something wrong with the combination of options the user has picked.
  394. #[derive(PartialEq, Debug)]
  395. pub enum OptionsError {
  396. InvalidDomain(String),
  397. InvalidEDNS(String),
  398. InvalidQueryType(String),
  399. InvalidQueryClass(String),
  400. InvalidTxid(String),
  401. InvalidTweak(String),
  402. QueryTypeOPT,
  403. MissingHttpsUrl,
  404. }
  405. impl fmt::Display for OptionsError {
  406. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  407. match self {
  408. Self::InvalidDomain(domain) => write!(f, "Invalid domain {:?}", domain),
  409. Self::InvalidEDNS(edns) => write!(f, "Invalid EDNS setting {:?}", edns),
  410. Self::InvalidQueryType(qt) => write!(f, "Invalid query type {:?}", qt),
  411. Self::InvalidQueryClass(qc) => write!(f, "Invalid query class {:?}", qc),
  412. Self::InvalidTxid(txid) => write!(f, "Invalid transaction ID {:?}", txid),
  413. Self::InvalidTweak(tweak) => write!(f, "Invalid protocol tweak {:?}", tweak),
  414. Self::QueryTypeOPT => write!(f, "OPT request is sent by default (see -Z flag)"),
  415. Self::MissingHttpsUrl => write!(f, "You must pass a URL as a nameserver when using --https"),
  416. }
  417. }
  418. }
  419. #[cfg(test)]
  420. mod test {
  421. use super::*;
  422. use pretty_assertions::assert_eq;
  423. impl Inputs {
  424. fn fallbacks() -> Self {
  425. Inputs {
  426. domains: vec![ /* No domains by default */ ],
  427. types: vec![ RecordType::A ],
  428. classes: vec![ QClass::IN ],
  429. resolvers: vec![ Resolver::system_default() ],
  430. transport_types: vec![ TransportType::Automatic ],
  431. }
  432. }
  433. }
  434. impl OptionsResult {
  435. fn unwrap(self) -> Options {
  436. match self {
  437. Self::Ok(o) => o,
  438. _ => panic!("{:?}", self),
  439. }
  440. }
  441. }
  442. // help tests
  443. #[test]
  444. fn help() {
  445. assert_eq!(Options::getopts(&[ "--help" ]),
  446. OptionsResult::Help(HelpReason::Flag, UseColours::Automatic));
  447. }
  448. #[test]
  449. fn help_no_colour() {
  450. assert_eq!(Options::getopts(&[ "--help", "--colour=never" ]),
  451. OptionsResult::Help(HelpReason::Flag, UseColours::Never));
  452. }
  453. #[test]
  454. fn version() {
  455. assert_eq!(Options::getopts(&[ "--version" ]),
  456. OptionsResult::Version(UseColours::Automatic));
  457. }
  458. #[test]
  459. fn version_yes_color() {
  460. assert_eq!(Options::getopts(&[ "--version", "--color", "always" ]),
  461. OptionsResult::Version(UseColours::Always));
  462. }
  463. #[test]
  464. fn fail() {
  465. assert_eq!(Options::getopts(&[ "--pear" ]),
  466. OptionsResult::InvalidOptionsFormat(getopts::Fail::UnrecognizedOption("pear".into())));
  467. }
  468. #[test]
  469. fn empty() {
  470. let nothing: Vec<&str> = vec![];
  471. assert_eq!(Options::getopts(nothing),
  472. OptionsResult::Help(HelpReason::NoDomains, UseColours::Automatic));
  473. }
  474. #[test]
  475. fn an_unrelated_argument() {
  476. assert_eq!(Options::getopts(&[ "--time" ]),
  477. OptionsResult::Help(HelpReason::NoDomains, UseColours::Automatic));
  478. }
  479. // query tests
  480. #[test]
  481. fn just_domain() {
  482. let options = Options::getopts(&[ "lookup.dog" ]).unwrap();
  483. assert_eq!(options.requests.inputs, Inputs {
  484. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  485. .. Inputs::fallbacks()
  486. });
  487. }
  488. #[test]
  489. fn just_named_domain() {
  490. let options = Options::getopts(&[ "-q", "lookup.dog" ]).unwrap();
  491. assert_eq!(options.requests.inputs, Inputs {
  492. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  493. .. Inputs::fallbacks()
  494. });
  495. }
  496. #[test]
  497. fn domain_and_type() {
  498. let options = Options::getopts(&[ "lookup.dog", "SOA" ]).unwrap();
  499. assert_eq!(options.requests.inputs, Inputs {
  500. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  501. types: vec![ RecordType::SOA ],
  502. .. Inputs::fallbacks()
  503. });
  504. }
  505. #[test]
  506. fn domain_and_type_lowercase() {
  507. let options = Options::getopts(&[ "lookup.dog", "soa" ]).unwrap();
  508. assert_eq!(options.requests.inputs, Inputs {
  509. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  510. types: vec![ RecordType::SOA ],
  511. .. Inputs::fallbacks()
  512. });
  513. }
  514. #[test]
  515. fn domain_and_nameserver() {
  516. let options = Options::getopts(&[ "lookup.dog", "@1.1.1.1" ]).unwrap();
  517. assert_eq!(options.requests.inputs, Inputs {
  518. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  519. resolvers: vec![ Resolver::specified("1.1.1.1".into()) ],
  520. .. Inputs::fallbacks()
  521. });
  522. }
  523. #[test]
  524. fn domain_and_class() {
  525. let options = Options::getopts(&[ "lookup.dog", "CH" ]).unwrap();
  526. assert_eq!(options.requests.inputs, Inputs {
  527. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  528. classes: vec![ QClass::CH ],
  529. .. Inputs::fallbacks()
  530. });
  531. }
  532. #[test]
  533. fn domain_and_class_lowercase() {
  534. let options = Options::getopts(&[ "lookup.dog", "ch" ]).unwrap();
  535. assert_eq!(options.requests.inputs, Inputs {
  536. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  537. classes: vec![ QClass::CH ],
  538. .. Inputs::fallbacks()
  539. });
  540. }
  541. #[test]
  542. fn all_free() {
  543. let options = Options::getopts(&[ "lookup.dog", "CH", "NS", "@1.1.1.1" ]).unwrap();
  544. assert_eq!(options.requests.inputs, Inputs {
  545. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  546. classes: vec![ QClass::CH ],
  547. types: vec![ RecordType::NS ],
  548. resolvers: vec![ Resolver::specified("1.1.1.1".into()) ],
  549. .. Inputs::fallbacks()
  550. });
  551. }
  552. #[test]
  553. fn all_parameters() {
  554. let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "CH", "--type", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
  555. assert_eq!(options.requests.inputs, Inputs {
  556. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  557. classes: vec![ QClass::CH ],
  558. types: vec![ RecordType::SOA ],
  559. resolvers: vec![ Resolver::specified("1.1.1.1".into()) ],
  560. .. Inputs::fallbacks()
  561. });
  562. }
  563. #[test]
  564. fn all_parameters_lowercase() {
  565. let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "ch", "--type", "soa", "--nameserver", "1.1.1.1" ]).unwrap();
  566. assert_eq!(options.requests.inputs, Inputs {
  567. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  568. classes: vec![ QClass::CH ],
  569. types: vec![ RecordType::SOA ],
  570. resolvers: vec![ Resolver::specified("1.1.1.1".into()) ],
  571. .. Inputs::fallbacks()
  572. });
  573. }
  574. #[test]
  575. fn two_types() {
  576. let options = Options::getopts(&[ "-q", "lookup.dog", "--type", "SRV", "--type", "AAAA" ]).unwrap();
  577. assert_eq!(options.requests.inputs, Inputs {
  578. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  579. types: vec![ RecordType::SRV, RecordType::AAAA ],
  580. .. Inputs::fallbacks()
  581. });
  582. }
  583. #[test]
  584. fn two_classes() {
  585. let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "IN", "--class", "CH" ]).unwrap();
  586. assert_eq!(options.requests.inputs, Inputs {
  587. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  588. classes: vec![ QClass::IN, QClass::CH ],
  589. .. Inputs::fallbacks()
  590. });
  591. }
  592. #[test]
  593. fn all_mixed_1() {
  594. let options = Options::getopts(&[ "lookup.dog", "--class", "CH", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
  595. assert_eq!(options.requests.inputs, Inputs {
  596. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  597. classes: vec![ QClass::CH ],
  598. types: vec![ RecordType::SOA ],
  599. resolvers: vec![ Resolver::specified("1.1.1.1".into()) ],
  600. .. Inputs::fallbacks()
  601. });
  602. }
  603. #[test]
  604. fn all_mixed_2() {
  605. let options = Options::getopts(&[ "CH", "SOA", "MX", "IN", "-q", "lookup.dog", "--class", "HS" ]).unwrap();
  606. assert_eq!(options.requests.inputs, Inputs {
  607. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  608. classes: vec![ QClass::HS, QClass::CH, QClass::IN ],
  609. types: vec![ RecordType::SOA, RecordType::MX ],
  610. .. Inputs::fallbacks()
  611. });
  612. }
  613. #[test]
  614. fn all_mixed_3() {
  615. let options = Options::getopts(&[ "lookup.dog", "--nameserver", "1.1.1.1", "--nameserver", "1.0.0.1" ]).unwrap();
  616. assert_eq!(options.requests.inputs, Inputs {
  617. domains: vec![ Labels::encode("lookup.dog").unwrap() ],
  618. resolvers: vec![ Resolver::specified("1.1.1.1".into()),
  619. Resolver::specified("1.0.0.1".into()), ],
  620. .. Inputs::fallbacks()
  621. });
  622. }
  623. #[test]
  624. fn explicit_numerics() {
  625. let options = Options::getopts(&[ "11", "--class", "22", "--type", "33" ]).unwrap();
  626. assert_eq!(options.requests.inputs, Inputs {
  627. domains: vec![ Labels::encode("11").unwrap() ],
  628. classes: vec![ QClass::Other(22) ],
  629. types: vec![ RecordType::from(33) ],
  630. .. Inputs::fallbacks()
  631. });
  632. }
  633. #[test]
  634. fn edns_and_tweaks() {
  635. let options = Options::getopts(&[ "dom.ain", "--edns", "show", "-Z", "authentic" ]).unwrap();
  636. assert_eq!(options.requests.edns, UseEDNS::SendAndShow);
  637. assert_eq!(options.requests.protocol_tweaks.set_authentic_flag, true);
  638. }
  639. #[test]
  640. fn two_more_tweaks() {
  641. let options = Options::getopts(&[ "dom.ain", "-Z", "aa", "-Z", "cd" ]).unwrap();
  642. assert_eq!(options.requests.protocol_tweaks.set_authoritative_flag, true);
  643. assert_eq!(options.requests.protocol_tweaks.set_checking_disabled_flag, true);
  644. }
  645. #[test]
  646. fn udp_size() {
  647. let options = Options::getopts(&[ "dom.ain", "-Z", "bufsize=4096" ]).unwrap();
  648. assert_eq!(options.requests.protocol_tweaks.udp_payload_size, Some(4096));
  649. }
  650. #[test]
  651. fn udp_size_invalid() {
  652. assert_eq!(Options::getopts(&[ "-Z", "bufsize=null" ]),
  653. OptionsResult::InvalidOptions(OptionsError::InvalidTweak("bufsize=null".into())));
  654. }
  655. #[test]
  656. fn udp_size_too_big() {
  657. assert_eq!(Options::getopts(&[ "-Z", "bufsize=999999999" ]),
  658. OptionsResult::InvalidOptions(OptionsError::InvalidTweak("bufsize=999999999".into())));
  659. }
  660. #[test]
  661. fn short_mode() {
  662. let tf = TextFormat { format_durations: true };
  663. let options = Options::getopts(&[ "dom.ain", "--short" ]).unwrap();
  664. assert_eq!(options.format, OutputFormat::Short(tf));
  665. let tf = TextFormat { format_durations: false };
  666. let options = Options::getopts(&[ "dom.ain", "--short", "--seconds" ]).unwrap();
  667. assert_eq!(options.format, OutputFormat::Short(tf));
  668. }
  669. #[test]
  670. fn json_output() {
  671. let options = Options::getopts(&[ "dom.ain", "--json" ]).unwrap();
  672. assert_eq!(options.format, OutputFormat::JSON);
  673. }
  674. #[test]
  675. fn specific_txid() {
  676. let options = Options::getopts(&[ "dom.ain", "--txid", "1234" ]).unwrap();
  677. assert_eq!(options.requests.txid_generator,
  678. TxidGenerator::Sequence(1234));
  679. }
  680. #[test]
  681. fn all_transport_types() {
  682. use crate::connect::TransportType::*;
  683. let options = Options::getopts(&[ "dom.ain", "--https", "--tls", "--tcp", "--udp" ]).unwrap();
  684. assert_eq!(options.requests.inputs.transport_types,
  685. vec![ HTTPS, TLS, TCP, UDP ]);
  686. }
  687. // invalid options tests
  688. #[test]
  689. fn invalid_named_class() {
  690. assert_eq!(Options::getopts(&[ "lookup.dog", "--class", "tubes" ]),
  691. OptionsResult::InvalidOptions(OptionsError::InvalidQueryClass("tubes".into())));
  692. }
  693. #[test]
  694. fn invalid_named_class_too_big() {
  695. assert_eq!(Options::getopts(&[ "lookup.dog", "--class", "999999" ]),
  696. OptionsResult::InvalidOptions(OptionsError::InvalidQueryClass("999999".into())));
  697. }
  698. #[test]
  699. fn invalid_named_type() {
  700. assert_eq!(Options::getopts(&[ "lookup.dog", "--type", "tubes" ]),
  701. OptionsResult::InvalidOptions(OptionsError::InvalidQueryType("tubes".into())));
  702. }
  703. #[test]
  704. fn invalid_named_type_too_big() {
  705. assert_eq!(Options::getopts(&[ "lookup.dog", "--type", "999999" ]),
  706. OptionsResult::InvalidOptions(OptionsError::InvalidQueryType("999999".into())));
  707. }
  708. #[test]
  709. fn invalid_capsword() {
  710. assert_eq!(Options::getopts(&[ "SMH", "lookup.dog" ]),
  711. OptionsResult::InvalidOptions(OptionsError::InvalidQueryType("SMH".into())));
  712. }
  713. #[test]
  714. fn invalid_txid() {
  715. assert_eq!(Options::getopts(&[ "lookup.dog", "--txid=0x10000" ]),
  716. OptionsResult::InvalidOptions(OptionsError::InvalidTxid("0x10000".into())));
  717. }
  718. #[test]
  719. fn invalid_edns() {
  720. assert_eq!(Options::getopts(&[ "--edns=yep" ]),
  721. OptionsResult::InvalidOptions(OptionsError::InvalidEDNS("yep".into())));
  722. }
  723. #[test]
  724. fn invalid_tweaks() {
  725. assert_eq!(Options::getopts(&[ "-Zsleep" ]),
  726. OptionsResult::InvalidOptions(OptionsError::InvalidTweak("sleep".into())));
  727. }
  728. #[test]
  729. fn opt() {
  730. assert_eq!(Options::getopts(&[ "OPT", "lookup.dog" ]),
  731. OptionsResult::InvalidOptions(OptionsError::QueryTypeOPT));
  732. }
  733. #[test]
  734. fn opt_lowercase() {
  735. assert_eq!(Options::getopts(&[ "opt", "lookup.dog" ]),
  736. OptionsResult::InvalidOptions(OptionsError::QueryTypeOPT));
  737. }
  738. #[test]
  739. fn missing_https_url() {
  740. assert_eq!(Options::getopts(&[ "--https", "lookup.dog" ]),
  741. OptionsResult::InvalidOptions(OptionsError::MissingHttpsUrl));
  742. }
  743. // txid tests
  744. #[test]
  745. fn number_parsing() {
  746. assert_eq!(parse_dec_or_hex("1234"), Some(1234));
  747. assert_eq!(parse_dec_or_hex("0x1234"), Some(0x1234));
  748. assert_eq!(parse_dec_or_hex("0xABcd"), Some(0xABcd));
  749. assert_eq!(parse_dec_or_hex("65536"), None);
  750. assert_eq!(parse_dec_or_hex("0x65536"), None);
  751. assert_eq!(parse_dec_or_hex(""), None);
  752. assert_eq!(parse_dec_or_hex("0x"), None);
  753. }
  754. }