use std::error; use std::fmt; use std::io; use std::thread; use std::time::Duration; use std::{env, sync::Arc}; use rustls::{ Certificate, ClientConfig, RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError, }; use ureq; use webpki::DNSNameRef; #[derive(Debug)] struct StringError(String); impl error::Error for StringError {} impl fmt::Display for StringError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl From for StringError { fn from(source: String) -> Self { Self(source) } } #[derive(Debug)] struct Error { source: Box, } impl error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.source) } } impl From for Error { fn from(source: StringError) -> Self { Error { source: source.into(), } } } impl From for Error { fn from(source: ureq::Error) -> Self { Error { source: source.into(), } } } impl From for Error { fn from(source: io::Error) -> Self { Error { source: source.into(), } } } fn perform( agent: &ureq::Agent, method: &str, url: &str, data: &[u8], print_headers: bool, ) -> Result<(), Error> { let req = agent.request(method, url); let response = if method == "GET" && data.len() == 0 { req.call()? } else { req.send_bytes(data)? }; if print_headers { println!( "{} {} {}", response.http_version(), response.status(), response.status_text() ); for h in response.headers_names() { println!("{}: {}", h, response.header(&h).unwrap_or_default()); } println!(); } let mut reader = response.into_reader(); io::copy(&mut reader, &mut io::stdout())?; Ok(()) } struct AcceptAll {} impl ServerCertVerifier for AcceptAll { fn verify_server_cert( &self, _roots: &RootCertStore, _presented_certs: &[Certificate], _dns_name: DNSNameRef<'_>, _ocsp_response: &[u8], ) -> Result { Ok(ServerCertVerified::assertion()) } } fn main() { match main2() { Ok(()) => {} Err(e) => { eprintln!("{}", e); std::process::exit(1); } } } fn main2() -> Result<(), Error> { let mut args = env::args(); if args.next().is_none() { println!( r##"Usage: {:#?} url [url ...] -i Include headers when printing response -X Use the given request method (GET, POST, etc) -d Use the given data as the request body (useful for POST) --wait Wait n seconds between requests -k Ignore certificate errors Fetch url and copy it to stdout. "##, env::current_exe()? ); return Ok(()); } env_logger::init(); let mut builder = ureq::builder() .timeout_connect(Duration::from_secs(30)) .timeout(Duration::from_secs(300)); let mut nonflags: Vec = vec![]; let mut print_headers: bool = false; let mut method: String = "GET".into(); let mut data: Vec = vec![]; let mut wait = Duration::new(0, 0); while let Some(arg) = args.next() { match arg.as_ref() { "-i" => print_headers = true, "-X" => method = args.next().expect("flag -X requires a value"), "-d" => data = args.next().expect("flag -d requires a value").into(), "--wait" => { let wait_string: String = args.next().expect("flag --wait requires a value").into(); let wait_seconds: u64 = wait_string.parse().expect("invalid --wait flag"); wait = Duration::from_secs(wait_seconds); } "-k" => { let mut client_config = ClientConfig::new(); client_config .dangerous() .set_certificate_verifier(Arc::new(AcceptAll {})); builder = builder.tls_config(Arc::new(client_config)); } arg => { if arg.starts_with("-") { Err(StringError(format!("unrecognized flag '{}'", arg)))?; } nonflags.push(arg.to_owned()); } } } let agent = builder.build(); for (i, url) in nonflags.iter().enumerate() { perform(&agent, &method, &url, &data, print_headers)?; if i != nonflags.len() - 1 { thread::sleep(wait); } } Ok(()) }