use std::io::{self, BufRead};
use zeroize::Zeroizing;
#[cfg(not(any(unix, windows)))]
compile_error!("only Unix-like OSes and Windows are supported!");
#[cfg_attr(unix, path = "unix.rs")]
#[cfg_attr(windows, path = "windows.rs")]
mod sys;
pub use sys::from_tty;
const CTRL_U: char = 21 as char;
fn from_bufread(reader: &mut impl BufRead) -> io::Result<Zeroizing<String>> {
let mut password = Zeroizing::new(String::new());
reader.read_line(&mut password)?;
let len = password.trim_end_matches(&['\r', '\n'][..]).len();
password.truncate(len);
password = match password.rfind(CTRL_U) {
Some(last_ctrl_u_index) => Zeroizing::new(password[last_ctrl_u_index + 1..].to_string()),
None => password,
};
Ok(password)
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
fn mock_input_crlf() -> Cursor<&'static [u8]> {
Cursor::new(&b"A mocked response.\r\nAnother mocked response.\r\n"[..])
}
fn mock_input_lf() -> Cursor<&'static [u8]> {
Cursor::new(&b"A mocked response.\nAnother mocked response.\n"[..])
}
#[test]
fn can_read_from_redirected_input_many_times() {
let mut reader_crlf = mock_input_crlf();
let response = super::from_bufread(&mut reader_crlf).unwrap();
assert_eq!(*response, "A mocked response.");
let response = super::from_bufread(&mut reader_crlf).unwrap();
assert_eq!(*response, "Another mocked response.");
let mut reader_lf = mock_input_lf();
let response = super::from_bufread(&mut reader_lf).unwrap();
assert_eq!(*response, "A mocked response.");
let response = super::from_bufread(&mut reader_lf).unwrap();
assert_eq!(*response, "Another mocked response.");
}
#[cfg(unix)]
fn close_stdin() {
unsafe {
libc::close(libc::STDIN_FILENO);
}
}
#[cfg(windows)]
fn close_stdin() {
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::System::Console::{GetStdHandle, STD_INPUT_HANDLE};
unsafe {
CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
}
}
#[cfg(not(any(unix, windows)))]
fn close_stdin() {
unimplemented!()
}
#[test]
fn can_read_from_redirected_input_many_times_nostdin() {
close_stdin();
can_read_from_redirected_input_many_times();
}
#[test]
fn can_read_from_input_ctrl_u() {
close_stdin();
let s = format!(
"A mocked response.{}Another mocked response.\n",
super::CTRL_U
);
let mut reader_ctrl_u = Cursor::new(s.as_bytes());
let response = super::from_bufread(&mut reader_ctrl_u).unwrap();
assert_eq!(*response, "Another mocked response.");
let s = format!("A mocked response.{}\n", super::CTRL_U);
let mut reader_ctrl_u_at_end = Cursor::new(s.as_bytes());
let response = super::from_bufread(&mut reader_ctrl_u_at_end).unwrap();
assert_eq!(*response, "");
}
}