use crate::download_root::download_root;
use crate::error::{self, Result};
use clap::Parser;
use snafu::{ensure, ResultExt};
use std::fs::File;
use std::num::NonZeroU64;
use std::path::{Path, PathBuf};
use tough::{ExpirationEnforcement, Prefix, Repository, RepositoryLoader, TargetName};
use url::Url;
#[derive(Debug, Parser)]
pub(crate) struct DownloadArgs {
#[clap(short = 'r', long = "root")]
root: Option<PathBuf>,
#[clap(short = 'v', long = "root-version", default_value = "1")]
root_version: NonZeroU64,
#[clap(short = 'm', long = "metadata-url")]
metadata_base_url: Url,
#[clap(short = 't', long = "targets-url")]
targets_base_url: Url,
#[clap(long)]
allow_root_download: bool,
#[clap(short = 'n', long = "target-name")]
target_names: Vec<String>,
outdir: PathBuf,
#[clap(long)]
allow_expired_repo: bool,
}
fn expired_repo_warning<P: AsRef<Path>>(path: P) {
#[rustfmt::skip]
eprintln!("\
=================================================================
Downloading repo to {}
WARNING: `--allow-expired-repo` was passed; this is unsafe and will not establish trust, use only for testing!
=================================================================",
path.as_ref().display());
}
impl DownloadArgs {
pub(crate) fn run(&self) -> Result<()> {
ensure!(
!self.outdir.exists(),
error::DownloadOutdirExistsSnafu { path: &self.outdir }
);
let root_path = if let Some(path) = &self.root {
PathBuf::from(path)
} else if self.allow_root_download {
let outdir = std::env::current_dir().context(error::CurrentDirSnafu)?;
download_root(&self.metadata_base_url, self.root_version, outdir)?
} else {
eprintln!("No root.json available");
std::process::exit(1);
};
let expiration_enforcement = if self.allow_expired_repo {
expired_repo_warning(&self.outdir);
ExpirationEnforcement::Unsafe
} else {
ExpirationEnforcement::Safe
};
let repository = RepositoryLoader::new(
File::open(&root_path).context(error::OpenRootSnafu { path: &root_path })?,
self.metadata_base_url.clone(),
self.targets_base_url.clone(),
)
.expiration_enforcement(expiration_enforcement)
.load()
.context(error::RepoLoadSnafu)?;
handle_download(&repository, &self.outdir, &self.target_names)
}
}
fn handle_download(repository: &Repository, outdir: &Path, raw_names: &[String]) -> Result<()> {
let target_names: Result<Vec<TargetName>> = raw_names
.iter()
.map(|s| TargetName::new(s).context(error::InvalidTargetNameSnafu))
.collect();
let target_names = target_names?;
let download_target = |name: &TargetName| -> Result<()> {
println!("\t-> {}", name.raw());
repository
.save_target(name, outdir, Prefix::None)
.context(error::MetadataSnafu)?;
Ok(())
};
let targets: Vec<TargetName> = if target_names.is_empty() {
repository
.targets()
.signed
.targets
.keys()
.cloned()
.collect()
} else {
target_names
};
println!("Downloading targets to {:?}", outdir);
std::fs::create_dir_all(outdir).context(error::DirCreateSnafu { path: outdir })?;
for target in targets {
download_target(&target)?;
}
Ok(())
}