use std::fs;
use std::io::{self, Read};
use std::time::Duration;
#[derive(Debug)]
pub struct Response {
pub body: String,
pub status: u16,
pub headers: Vec<(String, String)>,
}
pub fn get(url: &str, opts: &crate::Opts) -> Result<Response, String> {
if url == "-" {
return Ok(Response {
body: read_stdin()?,
status: 200,
headers: Vec::new(),
});
}
if !url.starts_with("http://") && !url.starts_with("https://") {
let body = fs::read_to_string(url).map_err(|e| format!("{url}: {e}"))?;
return Ok(Response {
body,
status: 200,
headers: Vec::new(),
});
}
let ag = ureq::AgentBuilder::new()
.timeout(Duration::from_secs(opts.max_time))
.user_agent(&opts.agent)
.redirects(8)
.build();
let mut req = ag.get(url);
for h in &opts.headers {
let (k, v) = h
.split_once(':')
.ok_or_else(|| format!("bad header: {h}"))?;
req = req.set(k.trim(), v.trim());
}
if let Some(c) = &opts.cookie {
req = req.set("Cookie", c);
}
let res = req.call().map_err(|e| format!("{url}: {e}"))?;
let status = res.status();
let headers = res
.headers_names()
.iter()
.filter_map(|k| res.header(k).map(|v| (k.clone(), v.to_string())))
.collect();
let body = res
.into_string()
.map_err(|e| format!("{url}: read body: {e}"))?;
Ok(Response {
body,
status,
headers,
})
}
pub fn read_stdin() -> Result<String, String> {
let mut buf = String::new();
io::stdin()
.read_to_string(&mut buf)
.map_err(|e| format!("stdin: {e}"))?;
Ok(buf)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Opts;
#[test]
fn reads_local_file() {
let p = std::env::temp_dir().join("flq-fetch-ok.html");
fs::write(&p, "<html><body>local</body></html>").unwrap();
let opts = Opts::default();
let res = get(p.to_str().unwrap(), &opts).unwrap();
assert_eq!(res.status, 200);
assert!(res.headers.is_empty());
assert!(res.body.contains("local"));
let _ = fs::remove_file(&p);
}
#[test]
fn missing_local_file_errors() {
let opts = Opts::default();
let err = get("this/does/not/exist.html", &opts).unwrap_err();
assert!(err.contains("this/does/not/exist.html"), "err was: {err}");
}
}