diff --git a/Cargo.toml b/Cargo.toml index 48896d5..8731eb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,6 @@ libc = "0.2" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["std", "winnt", "fileapi", "processenv", "winbase", "handleapi", "consoleapi", "minwindef", "wincon"] } + +[features] +enhanced_mock = [] diff --git a/README.md b/README.md index 1161af3..c1644b5 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,22 @@ fn main() { The full API documentation is available at [https://docs.rs/rpassword](https://docs.rs/rpassword). +## Optional feature + +The optional feature **enhanced_mock** can be enabled to change `read_password_with_reader` signature from +``` +pub fn read_password_with_reader(source: Option) -> ::std::io::Result + where + T: ::std::io::BufRead +``` +to +``` +pub fn read_password_with_reader(source: Option<&mut T>) -> ::std::io::Result + where + T: ::std::io::BufRead +``` +allowing to call `read_password_with_reader` multiple times with the same reader. + ## Contributors We welcome contribution from everyone. Feel free to open an issue or a pull request at any time. diff --git a/src/lib.rs b/src/lib.rs index ec5f066..59662e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,11 +39,6 @@ fn fixes_newline(password: &mut ZeroOnDrop) { } } -/// Reads a password from STDIN -pub fn read_password() -> ::std::io::Result { - read_password_with_reader(None::<::std::io::Empty>) -} - #[cfg(unix)] mod unix { use libc::{c_int, termios, isatty, tcsetattr, TCSANOW, ECHO, ECHONL, STDIN_FILENO}; @@ -242,19 +237,116 @@ use unix::{read_password_from_stdin, display_on_tty}; use windows::{read_password_from_stdin, display_on_tty}; /// Reads a password from anything that implements BufRead -pub fn read_password_with_reader(source: Option) -> ::std::io::Result - where T: ::std::io::BufRead { - match source { - Some(mut reader) => { - let mut password = ZeroOnDrop::new(); - reader.read_line(&mut password)?; - fixes_newline(&mut password); - Ok(password.into_inner()) - }, - None => read_password_from_stdin(false), +#[cfg(not(feature = "enhanced_mock"))] +mod legacy_mock { + use super::*; + + /// Reads a password from STDIN + pub fn read_password() -> ::std::io::Result { + read_password_with_reader(None::<::std::io::Empty>) + } + + /// Reads a password from anything that implements BufRead + pub fn read_password_with_reader(source: Option) -> ::std::io::Result + where + T: ::std::io::BufRead, + { + match source { + Some(mut reader) => { + let mut password = ZeroOnDrop::new(); + reader.read_line(&mut password)?; + fixes_newline(&mut password); + Ok(password.into_inner()) + }, + None => read_password_from_stdin(false), + } + } + + #[cfg(test)] + mod tests { + use std::io::Cursor; + + fn mock_input_crlf() -> Cursor<&'static [u8]> { + Cursor::new(&b"A mocked response.\r\n\r\n"[..]) + } + + fn mock_input_lf() -> Cursor<&'static [u8]> { + Cursor::new(&b"A mocked response.\n"[..]) + } + + #[test] + fn can_read_from_redirected_input() { + let response = ::read_password_with_reader(Some(mock_input_crlf())).unwrap(); + assert_eq!(response, "A mocked response."); + let response = ::read_password_with_reader(Some(mock_input_lf())).unwrap(); + assert_eq!(response, "A mocked response."); + } } } +#[cfg(feature = "enhanced_mock")] +mod enhanced_mock { + use super::*; + + /// Reads a password from STDIN + pub fn read_password() -> ::std::io::Result { + read_password_with_reader(None::<&mut ::std::io::Empty>) + } + + /// Reads a password from anything that implements BufRead + pub fn read_password_with_reader(source: Option<&mut T>) -> ::std::io::Result + where + T: ::std::io::BufRead, + { + match source { + Some(reader) => { + let mut password = ZeroOnDrop::new(); + if let Err(err) = reader.read_line(&mut password) { + Err(err) + } else { + fixes_newline(&mut password); + Ok(password.into_inner()) + } + } + None => read_password_from_stdin(false), + } + } + + #[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 = ::read_password_with_reader(Some(&mut reader_crlf)).unwrap(); + assert_eq!(response, "A mocked response."); + let response = ::read_password_with_reader(Some(&mut reader_crlf)).unwrap(); + assert_eq!(response, "Another mocked response."); + + let mut reader_lf = mock_input_lf(); + let response = ::read_password_with_reader(Some(&mut reader_lf)).unwrap(); + assert_eq!(response, "A mocked response."); + let response = ::read_password_with_reader(Some(&mut reader_lf)).unwrap(); + assert_eq!(response, "Another mocked response."); + } + } +} + +#[cfg(feature = "enhanced_mock")] +pub use enhanced_mock::{read_password, read_password_with_reader}; +#[cfg(not(feature = "enhanced_mock"))] +pub use legacy_mock::{read_password, read_password_with_reader}; + /// Reads a password from the terminal pub fn read_password_from_tty(prompt: Option<&str>) -> ::std::io::Result { @@ -281,24 +373,3 @@ pub fn prompt_password_stderr(prompt: &str) -> std::io::Result { stderr.flush()?; read_password() } - -#[cfg(test)] -mod tests { - use std::io::Cursor; - - fn mock_input_crlf() -> Cursor<&'static [u8]> { - Cursor::new(&b"A mocked response.\r\n"[..]) - } - - fn mock_input_lf() -> Cursor<&'static [u8]> { - Cursor::new(&b"A mocked response.\n"[..]) - } - - #[test] - fn can_read_from_redirected_input() { - let response = ::read_password_with_reader(Some(mock_input_crlf())).unwrap(); - assert_eq!(response, "A mocked response."); - let response = ::read_password_with_reader(Some(mock_input_lf())).unwrap(); - assert_eq!(response, "A mocked response."); - } -} diff --git a/tests/no-terminal.rs b/tests/no-terminal.rs index 485265b..0084161 100644 --- a/tests/no-terminal.rs +++ b/tests/no-terminal.rs @@ -38,13 +38,14 @@ fn close_stdin() { } fn mock_input_crlf() -> Cursor<&'static [u8]> { - Cursor::new(&b"A mocked response.\r\n"[..]) + 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.\n"[..]) + Cursor::new(&b"A mocked response.\nAnother mocked response.\n"[..]) } +#[cfg(not(feature = "enhanced_mock"))] #[test] fn can_read_from_redirected_input() { close_stdin(); @@ -54,3 +55,22 @@ fn can_read_from_redirected_input() { let response = ::read_password_with_reader(Some(mock_input_lf())).unwrap(); assert_eq!(response, "A mocked response."); } + +#[cfg(feature = "enhanced_mock")] +#[test] +fn can_read_from_redirected_input_many_times() { + close_stdin(); + + let mut reader_crlf = mock_input_crlf(); + + let response = ::read_password_with_reader(Some(&mut reader_crlf)).unwrap(); + assert_eq!(response, "A mocked response."); + let response = ::read_password_with_reader(Some(&mut reader_crlf)).unwrap(); + assert_eq!(response, "Another mocked response."); + + let mut reader_lf = mock_input_lf(); + let response = ::read_password_with_reader(Some(&mut reader_lf)).unwrap(); + assert_eq!(response, "A mocked response."); + let response = ::read_password_with_reader(Some(&mut reader_lf)).unwrap(); + assert_eq!(response, "Another mocked response."); +}