153 lines
5.0 KiB
Rust
153 lines
5.0 KiB
Rust
//! A headless prompt handler.
|
|
//!
|
|
//! This prompt handler uses the program's standard input and output to read inputs. It is not
|
|
//! directly intended to be machine-readable, but can be used for scriptable automation in a
|
|
//! fashion similar to a terminal handler.
|
|
|
|
use std::{
|
|
io::{IsTerminal, Write},
|
|
str::FromStr,
|
|
};
|
|
|
|
use crate::{BoxResult, Choice, Error, Message, PromptHandler, Result};
|
|
|
|
/// A headless prompt handler, usable in situations when a terminal might not be available, or for
|
|
/// scripting purposes where manual input from a terminal is not desirable.
|
|
pub struct Headless {
|
|
stdin: std::io::Stdin,
|
|
stderr: std::io::Stderr,
|
|
}
|
|
|
|
impl Headless {
|
|
/// Create a new [`Headless`] prompt handler.
|
|
#[allow(clippy::missing_errors_doc, clippy::new_without_default)]
|
|
pub fn new() -> Self {
|
|
Self {
|
|
stdin: std::io::stdin(),
|
|
stderr: std::io::stderr(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PromptHandler for Headless {
|
|
fn prompt_input(&mut self, prompt: &str) -> Result<String> {
|
|
self.stderr.write_all(prompt.as_bytes())?;
|
|
self.stderr.flush()?;
|
|
let mut line = String::new();
|
|
self.stdin.read_line(&mut line)?;
|
|
Ok(line)
|
|
}
|
|
|
|
fn prompt_wordlist(&mut self, prompt: &str, _wordlist: &[&str]) -> Result<String> {
|
|
self.stderr.write_all(prompt.as_bytes())?;
|
|
self.stderr.flush()?;
|
|
let mut line = String::new();
|
|
self.stdin.read_line(&mut line)?;
|
|
Ok(line)
|
|
}
|
|
|
|
fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
|
|
// Temporarily perform an IOCTL to disable printed output.
|
|
if self.stdin.is_terminal() {
|
|
eprintln!("WARNING: Headless terminal mode may leak passwords!");
|
|
}
|
|
self.stderr.write_all(prompt.as_bytes())?;
|
|
self.stderr.flush()?;
|
|
let mut line = String::new();
|
|
self.stdin.read_line(&mut line)?;
|
|
Ok(line)
|
|
}
|
|
|
|
fn prompt_message(&mut self, prompt: Message) -> Result<()> {
|
|
match prompt {
|
|
Message::Text(s) => {
|
|
writeln!(&mut self.stderr, "{s}")?;
|
|
self.stderr.flush()?;
|
|
}
|
|
Message::Data(s) => {
|
|
writeln!(&mut self.stderr, "{s}")?;
|
|
self.stderr.flush()?;
|
|
}
|
|
}
|
|
writeln!(&mut self.stderr, "Press enter to continue.")?;
|
|
self.stdin.read_line(&mut String::new())?;
|
|
Ok(())
|
|
}
|
|
|
|
fn prompt_choice_num(&mut self, prompt: &str, choices: &[Box<dyn Choice>]) -> Result<usize> {
|
|
writeln!(&mut self.stderr, "{prompt}")?;
|
|
for (i, choice) in choices.iter().enumerate() {
|
|
match choice.identifier() {
|
|
Some(identifier) => {
|
|
writeln!(&mut self.stderr, "{i}. ({identifier})\t{choice}")?;
|
|
}
|
|
None => {
|
|
writeln!(&mut self.stderr, "{i}.\t{choice}")?;
|
|
}
|
|
}
|
|
}
|
|
self.stderr.flush()?;
|
|
let mut line = String::new();
|
|
self.stdin.read_line(&mut line)?;
|
|
let selector_char = line.chars().next();
|
|
if let Some(selector @ ('a'..='z' | 'A'..='Z')) = selector_char {
|
|
if let Some((index, _)) = choices.iter().enumerate().find(|(_, choice)| {
|
|
choice
|
|
.identifier()
|
|
.is_some_and(|identifier| selector == identifier)
|
|
}) {
|
|
return Ok(index);
|
|
}
|
|
}
|
|
usize::from_str(line.trim()).map_err(|e| Error::Custom(e.to_string()))
|
|
}
|
|
|
|
fn prompt_validated_wordlist(
|
|
&mut self,
|
|
prompt: &str,
|
|
retries: u8,
|
|
_wordlist: &[&str],
|
|
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
|
) -> Result<()> {
|
|
let mut line = String::new();
|
|
let mut last_error = String::new();
|
|
for _ in 0..retries {
|
|
self.stderr.write_all(prompt.as_bytes())?;
|
|
self.stderr.flush()?;
|
|
self.stderr.flush()?;
|
|
self.stdin.read_line(&mut line)?;
|
|
if let Err(e) = validator_fn(std::mem::take(&mut line)) {
|
|
last_error = e.to_string();
|
|
writeln!(&mut self.stderr, "{e}")?;
|
|
self.stderr.flush()?;
|
|
} else {
|
|
return Ok(());
|
|
}
|
|
}
|
|
Err(Error::Validation(retries, last_error))
|
|
}
|
|
|
|
fn prompt_validated_passphrase(
|
|
&mut self,
|
|
prompt: &str,
|
|
retries: u8,
|
|
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
|
) -> Result<()> {
|
|
let mut line = String::new();
|
|
let mut last_error = String::new();
|
|
for _ in 0..retries {
|
|
self.stderr.write_all(prompt.as_bytes())?;
|
|
self.stderr.flush()?;
|
|
self.stdin.read_line(&mut line)?;
|
|
if let Err(e) = validator_fn(std::mem::take(&mut line)) {
|
|
last_error = e.to_string();
|
|
writeln!(&mut self.stderr, "{e}")?;
|
|
self.stderr.flush()?;
|
|
} else {
|
|
return Ok(());
|
|
}
|
|
}
|
|
Err(Error::Validation(retries, last_error))
|
|
}
|
|
}
|