diff --git a/Cargo.toml b/Cargo.toml index 94586ea..3c5132e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,6 @@ keywords = ["read", "password", "security", "pass", "getpass"] edition = "2018" rust-version = "1.60" -[features] -serde = ["dep:serde", "dep:serde_json"] - [target.'cfg(unix)'.dependencies] libc = "0.2" @@ -22,5 +19,4 @@ libc = "0.2" winapi = { version = "0.3", features = ["std", "winnt", "fileapi", "processenv", "winbase", "handleapi", "consoleapi", "minwindef", "wincon"] } [dependencies] -serde = { version = "1.0", features = ["derive"], optional = true } -serde_json = { version = "1.0", optional = true } +rutil = { path = "../rutil" } diff --git a/src/lib.rs b/src/lib.rs index f98e557..2cb6956 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,5 @@ #[allow(unused)] mod rpassword; -#[allow(unused)] -mod rutil; pub use crate::rpassword::*; diff --git a/src/rpassword/all.rs b/src/rpassword/all.rs index 66ad056..6e2efea 100644 --- a/src/rpassword/all.rs +++ b/src/rpassword/all.rs @@ -1,6 +1,6 @@ -use crate::rutil::fix_line_issues::fix_line_issues; -use crate::rutil::print_tty::{print_tty, print_writer}; -use crate::rutil::safe_string::SafeString; +use rutil::rutil::fix_line_issues::fix_line_issues; +use rutil::rutil::print_tty::{print_tty, print_writer}; +use rutil::rutil::safe_string::SafeString; use std::io::{BufRead, Write}; #[cfg(target_family = "wasm")] diff --git a/src/rutil.rs b/src/rutil.rs deleted file mode 100644 index 8afead9..0000000 --- a/src/rutil.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod atty; -pub mod fix_line_issues; -pub mod print_tty; -pub mod safe_string; -#[cfg(feature = "serde")] -pub mod safe_string_serde; -pub mod safe_vec; diff --git a/src/rutil/atty.rs b/src/rutil/atty.rs deleted file mode 100644 index da49853..0000000 --- a/src/rutil/atty.rs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2015-2019 Doug Tangren -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. - -#![cfg_attr(unix, no_std)] - -#[cfg(windows)] -use winapi::shared::minwindef::DWORD; -#[cfg(windows)] -use winapi::shared::ntdef::WCHAR; - -/// possible stream sources -#[derive(Clone, Copy, Debug)] -pub enum Stream { - Stdout, - Stderr, - Stdin, -} - -/// returns true if this is a tty -#[cfg(target_family = "unix")] -pub fn is(stream: Stream) -> bool { - let fd = match stream { - Stream::Stdout => libc::STDOUT_FILENO, - Stream::Stderr => libc::STDERR_FILENO, - Stream::Stdin => libc::STDIN_FILENO, - }; - unsafe { libc::isatty(fd) != 0 } -} - -/// returns true if this is a tty -#[cfg(target_os = "hermit")] -pub fn is(stream: Stream) -> bool { - let fd = match stream { - Stream::Stdout => hermit_abi::STDOUT_FILENO, - Stream::Stderr => hermit_abi::STDERR_FILENO, - Stream::Stdin => hermit_abi::STDIN_FILENO, - }; - hermit_abi::isatty(fd) -} - -/// returns true if this is a tty -#[cfg(windows)] -pub fn is(stream: Stream) -> bool { - use winapi::um::winbase::{ - STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT, - STD_OUTPUT_HANDLE as STD_OUTPUT, - }; - - let (fd, others) = match stream { - Stream::Stdin => (STD_INPUT, [STD_ERROR, STD_OUTPUT]), - Stream::Stderr => (STD_ERROR, [STD_INPUT, STD_OUTPUT]), - Stream::Stdout => (STD_OUTPUT, [STD_INPUT, STD_ERROR]), - }; - if unsafe { console_on_any(&[fd]) } { - // False positives aren't possible. If we got a console then - // we definitely have a tty on stdin. - return true; - } - - // At this point, we *could* have a false negative. We can determine that - // this is true negative if we can detect the presence of a console on - // any of the other streams. If another stream has a console, then we know - // we're in a Windows console and can therefore trust the negative. - if unsafe { console_on_any(&others) } { - return false; - } - - // Otherwise, we fall back to a very strange msys hack to see if we can - // sneakily detect the presence of a tty. - unsafe { msys_tty_on(fd) } -} - -/// returns true if this is _not_ a tty -pub fn isnt(stream: Stream) -> bool { - !is(stream) -} - -/// Returns true if any of the given fds are on a console. -#[cfg(windows)] -unsafe fn console_on_any(fds: &[DWORD]) -> bool { - use winapi::um::{consoleapi::GetConsoleMode, processenv::GetStdHandle}; - - for &fd in fds { - let mut out = 0; - let handle = GetStdHandle(fd); - if GetConsoleMode(handle, &mut out) != 0 { - return true; - } - } - false -} - -/// Returns true if there is an MSYS tty on the given handle. -#[cfg(windows)] -unsafe fn msys_tty_on(fd: DWORD) -> bool { - use std::{mem, slice}; - - use winapi::{ - ctypes::c_void, - shared::minwindef::MAX_PATH, - um::{ - fileapi::FILE_NAME_INFO, minwinbase::FileNameInfo, processenv::GetStdHandle, - winbase::GetFileInformationByHandleEx, - }, - }; - - let size = mem::size_of::(); - let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::()]; - let res = GetFileInformationByHandleEx( - GetStdHandle(fd), - FileNameInfo, - &mut *name_info_bytes as *mut _ as *mut c_void, - name_info_bytes.len() as u32, - ); - if res == 0 { - return false; - } - let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO); - let s = slice::from_raw_parts( - name_info.FileName.as_ptr(), - name_info.FileNameLength as usize / 2, - ); - let name = String::from_utf16_lossy(s); - // This checks whether 'pty' exists in the file name, which indicates that - // a pseudo-terminal is attached. To mitigate against false positives - // (e.g., an actual file name that contains 'pty'), we also require that - // either the strings 'msys-' or 'cygwin-' are in the file name as well.) - let is_msys = name.contains("msys-") || name.contains("cygwin-"); - let is_pty = name.contains("-pty"); - is_msys && is_pty -} - -/// returns true if this is a tty -#[cfg(target_family = "wasm")] -pub fn is(_stream: Stream) -> bool { - false -} diff --git a/src/rutil/fix_line_issues.rs b/src/rutil/fix_line_issues.rs deleted file mode 100644 index 9f7a695..0000000 --- a/src/rutil/fix_line_issues.rs +++ /dev/null @@ -1,27 +0,0 @@ -/// Normalizes the return of `read_line()` in the context of a CLI application -pub fn fix_line_issues(mut line: String) -> std::io::Result { - if !line.ends_with('\n') { - return Err(std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "unexpected end of file", - )); - } - - // Remove the \n from the line. - line.pop(); - - // Remove the \r from the line if present - if line.ends_with('\r') { - line.pop(); - } - - // Ctrl-U should remove the line in terminals - if line.contains('') { - line = match line.rfind('') { - Some(last_ctrl_u_index) => line[last_ctrl_u_index + 1..].to_string(), - None => line, - }; - } - - Ok(line) -} diff --git a/src/rutil/print_tty.rs b/src/rutil/print_tty.rs deleted file mode 100644 index a69392a..0000000 --- a/src/rutil/print_tty.rs +++ /dev/null @@ -1,73 +0,0 @@ -#[cfg(target_family = "wasm")] -mod wasm { - use std::io::Write; - - /// Displays a message on the STDOUT - pub fn print_tty(prompt: impl ToString) -> std::io::Result<()> { - let mut stdout = std::io::stdout(); - write!(stdout, "{}", prompt.to_string().as_str())?; - stdout.flush()?; - Ok(()) - } -} - -#[cfg(target_family = "unix")] -mod unix { - use std::io::Write; - - /// Displays a message on the TTY - pub fn print_tty(prompt: impl ToString) -> std::io::Result<()> { - let mut stream = std::fs::OpenOptions::new().write(true).open("/dev/tty")?; - stream - .write_all(prompt.to_string().as_str().as_bytes()) - .and_then(|_| stream.flush()) - } -} - -#[cfg(target_family = "windows")] -mod windows { - use std::io::Write; - use std::os::windows::io::FromRawHandle; - use winapi::um::fileapi::{CreateFileA, OPEN_EXISTING}; - use winapi::um::handleapi::INVALID_HANDLE_VALUE; - use winapi::um::winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE}; - - /// Displays a message on the TTY - pub fn print_tty(prompt: impl ToString) -> std::io::Result<()> { - let handle = unsafe { - CreateFileA( - b"CONOUT$\x00".as_ptr() as *const i8, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - std::ptr::null_mut(), - OPEN_EXISTING, - 0, - std::ptr::null_mut(), - ) - }; - if handle == INVALID_HANDLE_VALUE { - return Err(std::io::Error::last_os_error()); - } - - let mut stream = unsafe { std::fs::File::from_raw_handle(handle) }; - - stream - .write_all(prompt.to_string().as_str().as_bytes()) - .and_then(|_| stream.flush()) - } -} - -/// Prints a message to a writer -pub fn print_writer(stream: &mut impl Write, prompt: impl ToString) -> std::io::Result<()> { - stream - .write_all(prompt.to_string().as_str().as_bytes()) - .and_then(|_| stream.flush()) -} - -use std::io::Write; -#[cfg(target_family = "unix")] -pub use unix::print_tty; -#[cfg(target_family = "wasm")] -pub use wasm::print_tty; -#[cfg(target_family = "windows")] -pub use windows::print_tty; diff --git a/src/rutil/safe_string.rs b/src/rutil/safe_string.rs deleted file mode 100644 index 00ef819..0000000 --- a/src/rutil/safe_string.rs +++ /dev/null @@ -1,70 +0,0 @@ -#[cfg(feature = "serde")] -use serde::de::{Deserialize, Deserializer, Visitor}; -#[cfg(feature = "serde")] -use serde::ser::{Serialize, Serializer}; -use std::convert::Into; -#[cfg(feature = "serde")] -use std::fmt; -use std::ops::{Deref, DerefMut, Drop}; -use std::{ptr, sync::atomic}; - -/// String that is zeroed when dropped -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SafeString { - inner: String, -} - -impl SafeString { - pub fn new() -> SafeString { - SafeString { - inner: String::new(), - } - } - - pub fn from_string(inner: String) -> SafeString { - SafeString { inner } - } - - pub fn into_inner(mut self) -> String { - std::mem::replace(&mut self.inner, String::new()) - } -} - -impl Drop for SafeString { - fn drop(&mut self) { - let default = u8::default(); - - for c in unsafe { self.inner.as_bytes_mut() } { - unsafe { ptr::write_volatile(c, default) }; - } - - atomic::fence(atomic::Ordering::SeqCst); - atomic::compiler_fence(atomic::Ordering::SeqCst); - } -} - -impl Deref for SafeString { - type Target = String; - - fn deref(&self) -> &String { - &self.inner - } -} - -impl DerefMut for SafeString { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -impl Into for String { - fn into(self) -> SafeString { - SafeString::from_string(self) - } -} - -impl<'a> Into for &'a str { - fn into(self) -> SafeString { - self.to_string().into() - } -} diff --git a/src/rutil/safe_string_serde.rs b/src/rutil/safe_string_serde.rs deleted file mode 100644 index 78c07e5..0000000 --- a/src/rutil/safe_string_serde.rs +++ /dev/null @@ -1,96 +0,0 @@ -use super::safe_string::SafeString; -use serde::de::{Deserialize, Deserializer, Visitor}; -use serde::ser::{Serialize, Serializer}; -use std::fmt; -use std::ops::Deref; - -struct StringVisitor; - -impl<'de> Visitor<'de> for StringVisitor { - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string") - } - fn visit_str(self, v: &str) -> Result { - Ok(String::from(v)) - } - type Value = String; -} - -impl Serialize for SafeString { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(self.deref()) - } -} - -impl<'de> Deserialize<'de> for SafeString { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer - .deserialize_string(StringVisitor) - .map(|parsed_value| SafeString::from_string(parsed_value)) - } -} - -mod test { - use super::SafeString; - use serde::{Deserialize, Serialize}; - - #[test] - fn safe_string_serialization() { - let s = SafeString::from_string(String::from("blabla")); - - match serde_json::to_string(&s) { - Ok(json) => assert_eq!("\"blabla\"", json), - Err(_) => panic!("Serialization failed, somehow"), - } - } - - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - pub struct TestStruct { - password: SafeString, - } - - #[test] - fn safe_string_within_struct_serialization() { - let ts = TestStruct { - password: SafeString::from_string(String::from("blabla")), - }; - - match serde_json::to_string(&ts) { - Ok(json) => assert_eq!("{\"password\":\"blabla\"}", json), - Err(_) => panic!("Serialization failed, somehow"), - } - } - - #[test] - fn safe_string_deserialization() { - let s = "\"blabla\""; - - let res: Result = serde_json::from_str(s); - - match res { - Ok(ss) => assert_eq!(ss, SafeString::from_string(String::from("blabla"))), - Err(_) => panic!("Deserialization failed"), - } - } - - #[test] - fn safe_string_within_struct_deserialization() { - let json = "{\"password\":\"blabla\"}"; - let res: Result = serde_json::from_str(json); - match res { - Ok(ts) => assert_eq!( - ts, - TestStruct { - password: SafeString::from_string(String::from("blabla")) - } - ), - Err(_) => panic!("Deserialization failed"), - } - } -} diff --git a/src/rutil/safe_vec.rs b/src/rutil/safe_vec.rs deleted file mode 100644 index aeba337..0000000 --- a/src/rutil/safe_vec.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::ops::Deref; -use std::ops::DerefMut; -use std::ops::Drop; -use std::{ptr, sync::atomic}; - -/// Vec that is zeroed when dropped -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SafeVec { - pub inner: Vec, -} - -impl SafeVec { - pub fn new(inner: Vec) -> SafeVec { - SafeVec { inner: inner } - } - - pub fn inner_mut(&mut self) -> &mut Vec { - &mut self.inner - } -} - -impl Drop for SafeVec { - fn drop(&mut self) { - let default = u8::default(); - - for c in self.inner.as_mut_slice() { - unsafe { ptr::write_volatile(c, default) }; - } - - atomic::fence(atomic::Ordering::SeqCst); - atomic::compiler_fence(atomic::Ordering::SeqCst); - } -} - -impl Deref for SafeVec { - type Target = [u8]; - - fn deref(&self) -> &[u8] { - self.inner.deref() - } -} - -impl DerefMut for SafeVec { - fn deref_mut(&mut self) -> &mut [u8] { - self.inner.deref_mut() - } -}