use std::path::{Path, PathBuf}; use anyhow::{bail, Error, Context}; use fehler::throws; use hyper::Uri; use hyper::body::HttpBody; use hyper::client::{Client, HttpConnector}; use hyper::header::{CONTENT_TYPE, LOCATION}; use hyper_rustls::HttpsConnector; use tokio::fs; use tokio::io::AsyncWriteExt; use super::Args; type HttpsClient = Client>; #[throws] pub(crate) async fn download(url: Uri, args: &Args) { let output_file_path = if let Some(output) = &args.output_document { Some(output.clone()) } else { let url_path = Path::new(url.path()); match (url_path.file_name(), url.path().ends_with('/')) { (Some(file_name), false) => Some(PathBuf::from(file_name)), _ => None, } }; let client: HttpsClient = Client::builder().build(HttpsConnector::with_native_roots()); let mut response = client.get(url.clone()).await?; while response.status().is_redirection() { let location = response.headers() .get(LOCATION).context("no Location header in redirect")? .to_str().context("malformed Location header in redirect")? .parse().context("non-URL Location header in redirect")?; response = client.get(location).await?; } if !response.status().is_success() { bail!("non-success response code {} in URL {}", response.status(), url); } let output_file_path = if let Some(path) = output_file_path { path } else { let content_type = response.headers().get(CONTENT_TYPE); let extension = content_type .and_then(|mime_type| mime_type.to_str().ok()) .and_then(|mime_type| mime2ext::mime2ext(mime_type)) .map(|x| format!(".{}", x)) .unwrap_or_default(); PathBuf::from(format!("index{}", extension)) }; let mut output_file = fs::File::create(output_file_path).await?; while let Some(data) = response.body_mut().data().await { output_file.write_all(&data?).await?; } }