mod tty;
pub use crate::tty::Stream;
use std::error::Error;
#[cfg(target_family = "windows")]
pub use crate::windows::read_password;
#[cfg(target_family = "windows")]
pub use crate::tty::isatty;
#[cfg(target_family = "unix")]
pub use crate::unix::read_password;
#[cfg(target_family = "unix")]
pub use crate::tty::isatty;
#[derive(Debug)]
pub enum PromptError {
EnableFailed(std::io::Error),
IOError(std::io::Error),
}
impl std::fmt::Display for PromptError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
PromptError::EnableFailed(e) => write!(f, "Could not re-enable echo: {}", e),
PromptError::IOError(e) => e.fmt(f),
}
}
}
impl From<std::io::Error> for PromptError {
fn from(e: std::io::Error) -> PromptError {
PromptError::IOError(e)
}
}
impl Error for PromptError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
PromptError::EnableFailed(e) => Some(e),
PromptError::IOError(e) => Some(e),
}
}
}
#[cfg(target_family = "windows")]
mod windows {
use crate::PromptError;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Storage::FileSystem::GetFileType;
use windows::Win32::System::Console::{
GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE, ENABLE_ECHO_INPUT,
STD_INPUT_HANDLE,
};
fn set_stdin_echo(echo: bool, handle: HANDLE) -> Result<(), PromptError> {
let mut mode: CONSOLE_MODE = CONSOLE_MODE(0);
unsafe {
if GetConsoleMode(handle, &mut mode as *mut CONSOLE_MODE) == false {
return Err(PromptError::IOError(std::io::Error::last_os_error()));
}
}
if !echo {
mode &= !ENABLE_ECHO_INPUT;
} else {
mode |= ENABLE_ECHO_INPUT;
}
unsafe {
if SetConsoleMode(handle, mode) == false {
let err = std::io::Error::last_os_error();
if echo {
return Err(PromptError::EnableFailed(err));
} else {
return Err(PromptError::IOError(err));
}
}
}
Ok(())
}
pub fn read_password() -> Result<String, PromptError> {
let mut pass = String::new();
let handle =
unsafe { GetStdHandle(STD_INPUT_HANDLE).map_err(|e| PromptError::IOError(e.into()))? };
let console = unsafe {
GetFileType(handle) == windows::Win32::Storage::FileSystem::FILE_TYPE_CHAR
};
if console {
set_stdin_echo(false, handle)?;
}
let stdin = std::io::stdin();
match stdin.read_line(&mut pass) {
Ok(_) => {}
Err(e) => {
if console {
set_stdin_echo(true, handle)?;
}
return Err(PromptError::IOError(e));
}
};
pass = pass.trim().to_string();
if console {
set_stdin_echo(true, handle)?;
}
Ok(pass)
}
}
#[cfg(target_family = "unix")]
mod unix {
use libc::{tcgetattr, tcsetattr, termios, ECHO, STDIN_FILENO, TCSANOW};
use std::mem::MaybeUninit;
use crate::PromptError;
fn set_stdin_echo(echo: bool) -> Result<(), PromptError> {
let mut tty = MaybeUninit::<termios>::uninit();
unsafe {
if tcgetattr(STDIN_FILENO, tty.as_mut_ptr()) != 0 {
return Err(PromptError::IOError(std::io::Error::last_os_error()));
}
}
let mut tty = unsafe { tty.assume_init() };
if !echo {
tty.c_lflag &= !ECHO;
} else {
tty.c_lflag |= ECHO;
}
unsafe {
let tty_ptr: *const termios = &tty;
if tcsetattr(STDIN_FILENO, TCSANOW, tty_ptr) != 0 {
let err = std::io::Error::last_os_error();
if echo {
return Err(PromptError::EnableFailed(err));
} else {
return Err(PromptError::IOError(err));
}
}
}
Ok(())
}
pub fn read_password() -> Result<String, PromptError> {
let mut pass = String::new();
let is_tty = unsafe { libc::isatty(STDIN_FILENO) == 1 };
if is_tty {
set_stdin_echo(false)?;
}
let stdin = std::io::stdin();
match stdin.read_line(&mut pass) {
Ok(_) => {}
Err(e) => {
if is_tty {
set_stdin_echo(true)?;
}
return Err(PromptError::IOError(e));
}
};
if is_tty {
set_stdin_echo(true)?;
}
pass = pass.trim().to_string();
Ok(pass)
}
}