use anyhow::Result;
use chrono::{DateTime, Duration, Local, Utc};
use clap::Args;
use clap_complete::engine::ArgValueCompleter;
use globset::GlobBuilder;
use yb_core::{orchestrator, parse_ec_public_key_from_cert_der, store::Store, Context};
use crate::cli::util::{check_blob_signature, quote_name, SigVerdict};
use crate::complete::complete_blob_names;
#[derive(Args, Debug)]
pub struct ListArgs {
#[arg(add = ArgValueCompleter::new(complete_blob_names))]
pub pattern: Option<String>,
#[arg(short = 'l', long = "long")]
pub long: bool,
#[arg(short = '1')]
pub one_per_line: bool,
#[arg(short = 't', long = "sort-time")]
pub sort_time: bool,
#[arg(short = 'r', long = "reverse")]
pub reverse: bool,
}
pub fn run(ctx: &Context, args: &ListArgs) -> Result<()> {
let matcher = match &args.pattern {
Some(pat) => {
let glob = GlobBuilder::new(pat)
.case_insensitive(false)
.build()?
.compile_matcher();
Some(glob)
}
None => None,
};
let store = Store::from_device(&ctx.reader, ctx.piv.as_ref())?;
let verifying_key = ctx
.piv
.read_certificate(&ctx.reader, store.store_key_slot)
.ok()
.and_then(|cert_der| parse_ec_public_key_from_cert_der(&cert_der).ok())
.map(|pk| {
use p256::ecdsa::VerifyingKey;
VerifyingKey::from(&pk)
});
let heads: Vec<_> = store.objects.iter().filter(|o| o.is_head()).collect();
let mut verdict_map: std::collections::HashMap<&str, SigVerdict> =
std::collections::HashMap::new();
let post_quantum = store.kind.is_post_quantum();
for head in &heads {
let v = if post_quantum {
quick_check(head, &store)
} else {
check_blob_signature(head, &store, verifying_key.as_ref())
};
verdict_map.insert(head.blob_name.as_str(), v);
}
if post_quantum && args.long {
println!("post-quantum store");
}
let mut blobs = orchestrator::list_blobs(&store);
if let Some(ref m) = matcher {
blobs.retain(|b| m.is_match(&b.name));
}
if args.sort_time {
blobs.sort_by_key(|b| std::cmp::Reverse(b.mtime)); }
if args.reverse {
blobs.reverse();
}
let mut any_corrupted = false;
for b in &blobs {
let verdict = verdict_map.get(b.name.as_str()).copied();
if verdict == Some(SigVerdict::Corrupted) {
any_corrupted = true;
}
let displayed_name = quote_name(&b.name);
let suffix = match verdict {
Some(SigVerdict::Corrupted) => " CORRUPTED".to_owned(),
_ => {
let mut s = String::new();
if verdict == Some(SigVerdict::Unsigned) {
s.push_str(" UNSIGNED");
}
if b.is_legacy_encryption {
s.push_str(" LEGACY");
}
s
}
};
if args.long {
let enc_flag = if b.is_encrypted { '-' } else { 'P' };
let date = format_mtime(b.mtime);
println!(
"{enc_flag} {:2} {} {:6} {displayed_name}{suffix}",
b.chunk_count, date, b.plain_size,
);
} else {
println!("{displayed_name}{suffix}");
}
}
if any_corrupted {
std::process::exit(if post_quantum { 4 } else { 1 });
}
Ok(())
}
fn format_mtime(unix: u32) -> String {
if unix == 0 {
return " ".to_owned();
}
let dt: DateTime<Local> =
DateTime::from(DateTime::<Utc>::from_timestamp(unix as i64, 0).unwrap());
let now = Local::now();
if now.signed_duration_since(dt) < Duration::days(180) {
dt.format("%b %e %H:%M").to_string()
} else {
dt.format("%b %e %Y").to_string()
}
}
#[cfg(not(windows))]
fn quick_check(head: &yb_core::store::Object, store: &Store) -> SigVerdict {
yb_core::orchestrator::post_quantum::quick_check(head, store)
}
#[cfg(windows)]
fn quick_check(_: &yb_core::store::Object, _: &Store) -> SigVerdict {
SigVerdict::Unverifiable
}