use std::io::{self, Write};
use crossterm::{
terminal,
event::{self, Event, KeyCode, KeyEvent, KeyEventKind}
};
use sha256;
struct PromptMask {
token: char,
content: String
}
pub struct CliPass {
label: String, no_label: bool, no_visibility: bool, prompt_mask_token: char, prompt_input: String
}
impl CliPass {
pub fn new() -> Self {
Self {
label: String::from("Password:"), no_label: false,
no_visibility: false,
prompt_mask_token: '*',
prompt_input: String::new()
}
}
fn token_set(&self) -> bool {
self.prompt_mask_token != ' '
}
pub fn set_prompt_label(&mut self, custom_label: &str) {
self.label = custom_label.to_string();
}
pub fn set_no_label(&mut self) {
self.no_label = true;
}
pub fn set_no_visibility(&mut self) {
self.no_visibility = true;
}
pub fn set_prompt_mask_token(&mut self, custom_token: char) {
self.prompt_mask_token = custom_token;
}
fn update_prompt_state(&self, mask: &str) {
if self.token_set() {
print!("\r{} {}", self.label, " ".to_string().repeat(mask.len() + 1));
print!("\r{} {}", self.label, mask);
}
}
fn set_content_internal(&mut self, input: &str) {
self.prompt_input = input.to_string();
}
fn get_content_internal(&self) -> String {
self.prompt_input.clone()
}
#[must_use]
pub fn launch_prompt(&mut self) -> io::Result<String> {
terminal::enable_raw_mode()?;
if !self.no_label {
print!("{} ", self.label);
std::io::stdout().flush()?;
}
let mut input = String::new();
let mut mask = PromptMask {
token: self.prompt_mask_token,
content: String::new()
};
loop {
std::io::stdout().flush()?;
if let Event::Key(KeyEvent {
code,
kind: KeyEventKind::Press,
..
}) = event::read()? {
match code {
KeyCode::Char(c) => {
input.push(c);
mask.content.push(mask.token);
if self.token_set() {
print!("{}", mask.token);
}
}
KeyCode::Enter => {
if !self.no_label {
self.update_prompt_state(&mask.content);
}
println!("\r");
break;
}
KeyCode::Backspace => {
input.pop();
mask.content.pop();
self.update_prompt_state(&mask.content);
}
KeyCode::Down => {
if !self.no_visibility {
print!("\r{} {}", self.label, input);
}
}
KeyCode::Up => {
self.update_prompt_state(&mask.content);
}
_ => {}
}
}
}
terminal::disable_raw_mode()?;
self.set_content_internal(&input);
Ok(input)
}
pub fn hash_sha256_internal(&self) -> String {
sha256::digest(self.get_content_internal())
}
pub fn hash_sha256_external(&self, input: &str) -> String {
sha256::digest(input)
}
pub fn hash_md5_internal(&self) -> String {
format!("{:x}", md5::compute(self.get_content_internal()))
}
pub fn hash_md5_external(&self, input: &str) -> String {
format!("{:x}", md5::compute(input))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sha256_compare_hashes() {
let session = CliPass::new();
assert_eq!(
session.hash_sha256_external("supersecretpassword"),
"5ac152b6f8bdb8bb12959548d542cb237c4a730064bf88bbb8dd6e204912baad"
);
}
#[test]
fn md5_compare_hashes() {
let session = CliPass::new();
assert_eq!(
session.hash_md5_external("supersecretpassword"),
"bbb2c5e63d2ef893106fdd0d797aa97a"
)
}
}