ssec_cli/
dec.rs

1use ssec_core::Decrypt;
2use futures_util::{Stream, StreamExt};
3use tokio::io::AsyncWriteExt;
4use zeroize::Zeroizing;
5use indicatif::{ProgressBar, ProgressStyle};
6use std::path::PathBuf;
7use crate::cli::{DecArgs, FetchArgs};
8use crate::file::new_async_tempfile;
9use crate::BAR_STYLE;
10
11const SPINNER_STYLE: &str = "{spinner} deriving decryption key";
12
13async fn dec_stream_to<E: std::error::Error, S: Stream<Item = Result<bytes::Bytes, E>> + Unpin>(
14	stream: S,
15	password: Zeroizing<Vec<u8>>,
16	out_path: PathBuf
17) -> Result<(), ()> {
18	let mut dec = Decrypt::new(stream, password);
19
20	let mut f_out = new_async_tempfile().await.unwrap();
21
22	let mut total = None;
23	let progress = ProgressBar::new_spinner();
24	progress.set_style(ProgressStyle::with_template(SPINNER_STYLE).unwrap());
25	progress.enable_steady_tick(std::time::Duration::from_millis(100));
26
27	while let Some(bytes) = dec.next().await {
28		if let Some(remaining) = dec.remaining_read_len() {
29			match total {
30				Some(total) => progress.set_position(total - remaining),
31				None => {
32					progress.disable_steady_tick();
33					progress.set_style(ProgressStyle::with_template(BAR_STYLE).unwrap());
34					progress.set_length(remaining);
35					progress.reset();
36					total = Some(remaining);
37				}
38			}
39		}
40
41		f_out.as_mut().write_all(&bytes.unwrap()).await.unwrap();
42	}
43
44	f_out.as_mut().shutdown().await.unwrap();
45
46	f_out.persist(out_path).await.unwrap();
47
48	Ok(())
49}
50
51async fn prompt_password() -> Result<Zeroizing<Vec<u8>>, ()> {
52	tokio::task::spawn_blocking(move || {
53		rpassword::prompt_password("password: ")
54			.map(String::into_bytes)
55			.map(Zeroizing::new)
56	}).await.unwrap().map_err(|e| {
57		eprintln!("failed to read password interactively: {e}");
58	})
59}
60
61pub async fn dec_file(args: DecArgs) -> Result<(), ()> {
62	let password = prompt_password().await?;
63
64	let f_in = tokio::fs::File::open(&args.in_file).await.map_err(|e| {
65		eprintln!("failed to open file {:?}: {e}", args.in_file);
66	})?;
67	let s = tokio_util::io::ReaderStream::new(f_in);
68
69	dec_stream_to(s, password, args.out_file).await
70}
71
72pub async fn dec_fetch(args: FetchArgs) -> Result<(), ()> {
73	let password = prompt_password().await?;
74
75	let client = reqwest::Client::new();
76
77	let s = client.get(args.url.clone()).send().await.map_err(|e| {
78		eprintln!("failed to fetch remote file {:?}: {e}", args.url);
79	})?.bytes_stream();
80
81	dec_stream_to(s, password, args.out_file).await
82}