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}