anubis_age/
cli_common.rs

1//! Common helpers for CLI binaries.
2
3use std::io;
4
5use crate::Callbacks;
6
7mod error;
8pub use error::ReadError;
9
10pub mod file_io;
11
12mod identities;
13pub use identities::read_identities;
14
15mod recipients;
16pub use recipients::read_recipients;
17
18/// A guard that helps to ensure that standard input is only used once.
19pub struct StdinGuard {
20    stdin_used: bool,
21}
22
23impl StdinGuard {
24    /// Constructs a new `StdinGuard`.
25    ///
26    /// `input_is_stdin` should be set to `true` if standard input is being used for
27    /// plaintext input during encryption, or ciphertext input during decryption.
28    pub fn new(input_is_stdin: bool) -> Self {
29        Self {
30            stdin_used: input_is_stdin,
31        }
32    }
33
34    fn open(&mut self, filename: String) -> Result<file_io::InputReader, ReadError> {
35        let input = file_io::InputReader::new(Some(filename))?;
36        if matches!(input, file_io::InputReader::Stdin(_)) {
37            if self.stdin_used {
38                return Err(ReadError::MultipleStdin);
39            }
40            self.stdin_used = true;
41        }
42        Ok(input)
43    }
44}
45
46/// Implementation of age callbacks that makes requests to the user via the UI.
47#[derive(Clone, Copy)]
48pub struct UiCallbacks;
49
50impl Callbacks for UiCallbacks {
51    fn display_message(&self, message: &str) {
52        eprintln!("{}", message);
53    }
54
55    fn confirm(&self, message: &str, yes_string: &str, no_string: Option<&str>) -> Option<bool> {
56        #[cfg(feature = "console")]
57        {
58            let term = console::Term::stderr();
59            let cancel_str = no_string.unwrap_or("no");
60            let initial = format!("{}: ({}/{}) ", message, yes_string, cancel_str);
61            loop {
62                term.write_str(&initial).ok()?;
63                let response = term.read_line().ok()?.to_lowercase();
64                if ["y", "yes"].contains(&response.as_str())
65                    || response == yes_string.to_lowercase()
66                {
67                    break Some(true);
68                } else if ["n", "no"].contains(&response.as_str())
69                    || Some(response.as_str()) == no_string.map(|s| s.to_lowercase()).as_deref()
70                {
71                    break Some(false);
72                }
73            }
74        }
75        #[cfg(not(feature = "console"))]
76        {
77            let _ = (message, yes_string, no_string);
78            None
79        }
80    }
81
82    fn request_public_string(&self, description: &str) -> Option<String> {
83        #[cfg(feature = "console")]
84        {
85            let term = console::Term::stderr();
86            term.write_str(description).ok()?;
87            term.read_line().ok().filter(|s| !s.is_empty())
88        }
89        #[cfg(not(feature = "console"))]
90        {
91            let _ = description;
92            None
93        }
94    }
95
96    fn request_passphrase(&self, _description: &str) -> Option<anubis_core::secrecy::SecretString> {
97        // Passphrase encryption is not supported in ML-KEM-only mode
98        None
99    }
100}