use std::fs::File;
use std::io::{stdout, Read, Write};
use crate::block::PackedBlock;
use crate::store::b32dec;
use crate::{err_str, Block, Manifest, Sha384};
pub struct DownloadArguments<'a> {
pub project: &'a str,
pub branch: &'a str,
pub cert_opt: Option<&'a str>,
pub cache_opt: Option<&'a str>,
pub key: &'a str,
pub url: &'a str,
pub file_opt: Option<&'a str>,
}
pub struct Downloader {
key: Vec<u8>,
url: reqwest::Url,
project: String,
branch: String,
client: reqwest::blocking::Client,
}
impl Downloader {
pub fn new(
key: &str,
url: &str,
project: &str,
branch: &str,
cert_opt: Option<&[u8]>,
) -> Result<Downloader, String> {
let key = b32dec(key).ok_or_else(|| "key not in base32 format".to_string())?;
let url = reqwest::Url::parse(url).map_err(err_str)?;
let client = {
let mut builder = reqwest::blocking::Client::builder();
if let Some(cert) = cert_opt {
builder = builder
.add_root_certificate(reqwest::Certificate::from_pem(cert).map_err(err_str)?);
}
builder.build().map_err(err_str)?
};
Ok(Downloader {
key,
url,
project: project.to_string(),
branch: branch.to_string(),
client,
})
}
fn download(&self, path: &str) -> Result<Vec<u8>, String> {
let url = self.url.join(path).map_err(err_str)?;
let mut response = self.client.get(url).send().map_err(err_str)?;
if !response.status().is_success() {
return Err(format!(
"failed to download {}: {:?}",
path,
response.status()
));
}
let mut data = Vec::new();
response.read_to_end(&mut data).map_err(err_str)?;
Ok(data)
}
pub fn object(&self, digest: &str) -> Result<Vec<u8>, String> {
let path = format!("object/{}", digest);
let data = self.download(&path)?;
let sha = Sha384::new(data.as_slice()).map_err(err_str)?;
if sha.to_base32() != digest {
return Err("sha384 mismatch".to_string());
}
Ok(data)
}
pub fn tail(&self) -> Result<Block, String> {
let path = format!("tail/{}/{}", self.project, self.branch);
let data = self.download(&path)?;
let b: &PackedBlock =
plain::from_bytes(&data).map_err(|_| "response too small".to_string())?;
b.verify(&self.key)
}
}
pub fn download(args: DownloadArguments) -> Result<(), String> {
let mut cert = Vec::new();
let cert_opt = if let Some(cert_path) = args.cert_opt {
{
let mut file = File::open(cert_path).map_err(err_str)?;
file.read_to_end(&mut cert).map_err(err_str)?;
}
Some(cert.as_slice())
} else {
None
};
let dl = Downloader::new(args.key, args.url, args.project, args.branch, cert_opt)?;
let tail = dl.tail()?;
let manifest_json = dl.object(&tail.digest)?;
let manifest = serde_json::from_slice::<Manifest>(&manifest_json).map_err(err_str)?;
if let Some(file) = args.file_opt {
if let Some(digest) = manifest.files.get(file) {
let data = dl.object(digest)?;
stdout().write(&data).map_err(err_str)?;
} else {
return Err(format!("{} not found", file));
}
} else {
for (file, _digest) in manifest.files.iter() {
println!("{}", file);
}
}
Ok(())
}