1#[derive(thiserror::Error, Debug)]
3pub enum Error {
4 #[error("IO error: {0}")]
5 Io(#[from] std::io::Error),
6}
7
8pub fn read_password() -> Result<Vec<u8>, Error> {
12 platform::read_password()
13}
14
15#[cfg(unix)]
18mod platform {
19 use std::io::Read;
20
21 use libc::{ECHO, ICANON, ICRNL, ISIG, STDIN_FILENO, TCSANOW, tcgetattr, tcsetattr, termios};
22
23 struct TermiosGuard {
25 saved: termios,
26 }
27
28 impl Drop for TermiosGuard {
29 fn drop(&mut self) {
30 unsafe { tcsetattr(STDIN_FILENO, TCSANOW, &self.saved) };
32 }
33 }
34
35 pub(super) fn read_password() -> Result<Vec<u8>, super::Error> {
36 let mut saved: termios = unsafe { std::mem::zeroed() };
38 let is_tty = unsafe { tcgetattr(STDIN_FILENO, &mut saved) } == 0;
39
40 let _guard = if is_tty {
42 let mut raw = saved;
43 raw.c_lflag &= !(ECHO as libc::tcflag_t);
47 raw.c_lflag |= (ICANON | ISIG) as libc::tcflag_t;
48 raw.c_iflag |= ICRNL as libc::tcflag_t;
49 unsafe { tcsetattr(STDIN_FILENO, TCSANOW, &raw) };
51 Some(TermiosGuard {
52 saved,
53 })
54 } else {
55 None
56 };
57
58 read_line()
59 }
60
61 fn read_line() -> Result<Vec<u8>, super::Error> {
62 let mut buf = Vec::new();
63 let stdin = std::io::stdin();
64 for byte in stdin.lock().bytes() {
65 let b = byte?;
66 if b == b'\n' || b == b'\r' {
67 break;
68 }
69 buf.push(b);
70 }
71 Ok(buf)
72 }
73}
74
75#[cfg(not(unix))]
78mod platform {
79 use std::io::BufRead;
80
81 pub(super) fn read_password() -> Result<Vec<u8>, super::Error> {
82 let mut line = String::new();
83 std::io::stdin().lock().read_line(&mut line)?;
84 if line.ends_with('\n') {
85 line.pop();
86 if line.ends_with('\r') {
87 line.pop();
88 }
89 }
90 Ok(line.into_bytes())
91 }
92}
93
94#[cfg(test)]
97mod tests {
98 #[test]
105 fn password_bytes_strip_newline() {
106 let mut raw = b"secret\n".to_vec();
107 if raw.ends_with(b"\n") {
108 raw.pop();
109 if raw.ends_with(b"\r") {
110 raw.pop();
111 }
112 }
113 assert_eq!(raw, b"secret");
114 }
115
116 #[test]
117 fn password_bytes_strip_crlf() {
118 let mut raw = b"secret\r\n".to_vec();
119 if raw.ends_with(b"\n") {
120 raw.pop();
121 if raw.ends_with(b"\r") {
122 raw.pop();
123 }
124 }
125 assert_eq!(raw, b"secret");
126 }
127
128 #[test]
129 fn password_bytes_no_newline() {
130 let raw = b"secret".to_vec();
131 assert_eq!(raw, b"secret");
132 }
133}