1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
extern crate libc;
extern crate rand;
extern crate seckey;
extern crate termion;
extern crate sha3;

pub mod readtty;
pub mod colorhash;

use std::io::{ self, Read, Write };
use std::iter::repeat;
use rand::random;
use seckey::{ SecKey, TempKey };
use termion::clear;
use termion::get_tty;
use termion::event::Key;
use termion::color::{ Fg, Reset, AnsiValue };
use colorhash::{ hash_as_ansi, hash_chars_as_ansi };
use readtty::{ RawTTY, read_from_tty };


/// Askpass
///
/// ### Fail When:
///
/// - IO Error
/// - User Interrupted
/// - `RawTTY` create fail
/// - `SecKey` malloc fail
#[inline]
pub fn askpass<F, T>(prompt: &str, f: F)
    -> io::Result<T>
    where F: FnOnce(&str) -> io::Result<T>
{
    raw_askpass(&mut RawTTY::new()?, &mut get_tty()?, prompt, '*')
        .and_then(|(buf, pos)| {
            let mut buf = buf.read().iter().take(pos).collect::<String>();
            let buf = TempKey::from(&mut buf as &mut str);
            f(&buf)
        })
}

pub fn raw_askpass(input: &mut Read, output: &mut Write, prompt: &str, star: char)
    -> io::Result<(SecKey<[char; 256]>, usize)>
{
    let mut pos = 0;
    let mut buf = SecKey::new([char::default(); 256])
        .map_err(|_| io::Error::new(io::ErrorKind::Other, "SecKey malloc fail"))?;

    read_from_tty(input, |key| {
        let mut buf = buf.write();
        match key {
            Key::Char('\n') => return Ok(true),
            Key::Char(c) => if pos < buf.len() {
                buf[pos] = c;
                pos += 1;
            },
            Key::Backspace | Key::Delete if pos >= 1 => pos -= 1,
            Key::Ctrl('c') => return Err(io::Error::new(io::ErrorKind::Interrupted, "Ctrl-c")),
            Key::Null => (),
            _ => return Ok(false)
        }

        let colors = match pos {
            0 => [AnsiValue(30); 8],
            1...7 => hash_as_ansi(&[random(), random(), random(), random()]),
            p => hash_chars_as_ansi(&buf[..p])
        };

        write!(
            output,
            "\r{} {}{}",
            prompt,
            repeat(star)
                .take(4)
                .zip(&colors[..4])
                .map(|(star, &color)| format!("{}{}{}", Fg(color), star, star))
                .collect::<String>(),
            Fg(Reset)
        )?;

        Ok(false)
    })?;

    write!(output, "{}\r", clear::CurrentLine)?;

    Ok((buf, pos))
}