use crate::password::{Comment, Name, PasswordRef}; use home::home_dir; use rpassword::prompt_password; use rustyline::Editor; use std::fmt; use std::path::Path; use std::{cell::RefCell, rc::Rc}; use crate::lk::LK; use crate::parser::command_parser; use crate::repl::{LKEval, LKRead}; lazy_static! { pub static ref HISTORY_FILE: Box = { match std::env::var("HEL_HISTORY") { Ok(v) => Path::new(shellexpand::full(&v).unwrap().into_owned().as_str()).to_path_buf().into_boxed_path(), _ => home_dir().unwrap().join(".hel_history").into_boxed_path(), } }; pub static ref PROMPT_SETTING: String = { match std::env::var("HEL_PROMPT") { Ok(v) => v, _ => "> ".to_string(), } }; pub static ref INIT_FILE: Box = { match std::env::var("HEL_INIT") { Ok(v) => Path::new(shellexpand::full(&v).unwrap().into_owned().as_str()).to_path_buf().into_boxed_path(), _ => home_dir().unwrap().join(".helrc").into_boxed_path(), } }; pub static ref CORRECT_FILE: Box = { match std::env::var("HEL_CORRECT") { Ok(v) => Path::new(shellexpand::full(&v).unwrap().into_owned().as_str()).to_path_buf().into_boxed_path(), _ => home_dir().unwrap().join(".hel_correct").into_boxed_path(), } }; pub static ref DUMP_FILE: Box = { match std::env::var("HEL_DUMP") { Ok(v) => Path::new(shellexpand::full(&v).unwrap().into_owned().as_str()).to_path_buf().into_boxed_path(), _ => home_dir().unwrap().join(".hel_dump").into_boxed_path(), } }; } #[derive(thiserror::Error, Debug, PartialEq)] pub enum LKErr<'a> { #[error("Error: {0}")] Error(&'a str), #[error("Failed to read the line: {0}")] ReadError(String), #[error("Failed to parse: {0}")] ParseError(peg::error::ParseError), } #[derive(PartialEq, Debug)] pub enum Command<'a> { Add(PasswordRef), Leave(Name), Ls(String), Ld(String), Mv(Name, Name), Rm(Name), Enc(Name), Gen(u32, PasswordRef), Pass(Name), UnPass(Name), Correct(Name), Uncorrect(Name), PasteBuffer(String), Source(String), Dump(Option), Comment(Name, Comment), Error(LKErr<'a>), Noop, Help, Quit, } #[derive(PartialEq, Debug, Clone)] pub enum Mode { Regular, RegularUpcase, NoSpace, NoSpaceUpcase, NoSpaceCamel, Hex, HexUpcase, Base64, Base64Upcase, Decimal, } impl std::fmt::Display for Mode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { Mode::Regular => "R", Mode::RegularUpcase => "UR", Mode::NoSpace => "N", Mode::NoSpaceUpcase => "UN", Mode::NoSpaceCamel => "C", Mode::Hex => "H", Mode::HexUpcase => "UH", Mode::Base64 => "B", Mode::Base64Upcase => "UB", Mode::Decimal => "D", } .to_string() ) } } #[derive(PartialEq, Debug)] pub struct LKOut { pub out: Option>>>, pub err: Option>>>, } impl LKOut { pub fn new() -> Self { Self { out: Some(Rc::new(RefCell::new(vec![]))), err: Some(Rc::new(RefCell::new(vec![]))), } } pub fn from_lkout(out: Option>>>, err: Option>>>) -> Self { let o = match out { Some(v) => Some(v.clone()), None => None, }; let e = match err { Some(v) => Some(v.clone()), None => None, }; Self { out: o, err: e } } #[allow(dead_code)] pub fn from_vecs(out: Vec, err: Vec) -> Self { Self { out: Some(Rc::new(RefCell::new(out))), err: Some(Rc::new(RefCell::new(err))), } } pub fn copy_out(&self, out: &LKOut) { if !self.out.is_some() { return; } for line in self.out.as_ref().unwrap().borrow().iter() { out.o(line.to_string()) } } pub fn copy_err(&self, out: &LKOut) { if !self.err.is_some() { return; } for line in self.err.as_ref().unwrap().borrow().iter() { out.e(line.to_string()) } } pub fn print_out(&self) { if !self.out.is_some() { return; } for line in self.out.as_ref().unwrap().borrow().iter() { println!("{}", line); } } pub fn print_err(&self) { if !self.err.is_some() { return; } for line in self.err.as_ref().unwrap().borrow().iter() { eprintln!("{}", line); } } pub fn copy(&self, out: &LKOut) { self.copy_err(&out); self.copy_out(&out); } pub fn data(&self) -> String { if self.out.is_some() { self.out.as_ref().unwrap().borrow().join("\n") } else { "".to_string() } } pub fn active(&self) -> bool { self.out.is_some() } pub fn o(&self, line: String) { if self.out.is_some() { self.out.as_ref().unwrap().borrow_mut().push(line); } } pub fn e(&self, line: String) { if self.err.is_some() { self.err.as_ref().unwrap().borrow_mut().push(line); } } } pub struct Radix { x: i32, radix: u32, } impl Radix { pub fn new(x: i32, radix: u32) -> Result { if radix < 2 || radix > 36 { Err("Unnsupported radix") } else { Ok(Self { x, radix }) } } } impl fmt::Display for Radix { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut x = self.x; // Good for binary formatting of `u128`s let mut result = ['\0'; 128]; let mut used = 0; let negative = x < 0; if negative { x *= -1; } let mut x = x as u32; loop { let m = x % self.radix; x /= self.radix; result[used] = std::char::from_digit(m, self.radix).unwrap(); used += 1; if x == 0 { break; } } if negative { write!(f, "-")?; } for c in result[..used].iter().rev() { write!(f, "{}", c)?; } Ok(()) } } pub fn init() -> Option { let lk = Rc::new(RefCell::new(LK::new())); match std::fs::read_to_string(INIT_FILE.to_str().unwrap()) { Ok(script) => match command_parser::script(&script) { Ok(cmd_list) => { for cmd in cmd_list { if !LKEval::new(cmd, lk.clone(), prompt_password).eval().print() { return None; } } } Err(err) => { LKEval::new(Command::Error(LKErr::ParseError(err)), lk.clone(), prompt_password).eval().print(); } }, Err(err) if err.kind() == std::io::ErrorKind::NotFound => (), Err(err) => { LKEval::new( Command::Error(LKErr::Error( format!("Failed to read init file {:?}: {}", INIT_FILE.to_str(), err).as_str(), )), lk.clone(), prompt_password, ) .eval() .print(); } } Some(LKRead::new(Editor::<()>::new().unwrap(), PROMPT_SETTING.to_string(), lk.clone())) } #[cfg(test)] mod tests { use super::*; use crate::password::Password; use chrono::naive::NaiveDate; use std::io::{BufWriter, Write}; use std::os::unix::fs::PermissionsExt; #[test] fn test_env() { std::env::set_var("HEL_HISTORY", "./test_history"); std::env::set_var("HEL_INIT", "./test_init"); std::env::set_var("HEL_DUMP", "./test_dump"); std::env::set_var("HEL_CORRECT", "./test_correct"); std::env::set_var("HEL_PB", "./test_pb"); std::env::set_var("HEL_PROMPT", "test> "); fn create_init() { let file = std::fs::File::create("test_init").unwrap(); let mut writer = BufWriter::new(file); writeln!(writer, "add t1 r 99 2022-10-10").expect("write"); writeln!(writer, "add t2 r 99 2022-10-10 test ^t1").expect("write"); writeln!(writer, "add t3 r 99 2022-10-10 ^t2 aoeu").expect("write"); } fn create_pb() { let file = std::fs::File::create("test_pb").unwrap(); let mut writer = BufWriter::new(file); let metadata = std::fs::metadata("test_pb").expect("unable to get file metadata"); let mut permissions = metadata.permissions(); permissions.set_mode(0o755); // set executable flag std::fs::set_permissions("test_pb", permissions).expect("unable to set file permissions"); writeln!(writer, "#!/bin/sh\ncat >test_pb_out").expect("write"); } fn clear_test_files() { #[allow(unused_must_use)] { std::fs::remove_file("test_history"); std::fs::remove_file("test_init"); std::fs::remove_file("test_dump"); std::fs::remove_file("test_correct"); std::fs::remove_file("test_pb"); std::fs::remove_file("test_pb_out"); } } defer! { clear_test_files(); } clear_test_files(); create_init(); create_pb(); let lkread = init().unwrap(); assert_eq!(lkread.prompt, "test> "); assert_eq!(lkread.state.borrow().db.contains_key("t1"), true); let t1 = Rc::new(RefCell::new(Password::new( None, "t1".to_string(), None, Mode::Regular, 99, NaiveDate::from_ymd_opt(2022, 10, 10).unwrap(), None, ))); let t2 = Rc::new(RefCell::new(Password::new( None, "t2".to_string(), None, Mode::Regular, 99, NaiveDate::from_ymd_opt(2022, 10, 10).unwrap(), Some("test".to_string()), ))); t2.borrow_mut().parent = Some(t1.clone()); let t3 = Rc::new(RefCell::new(Password::new( None, "t3".to_string(), None, Mode::Regular, 99, NaiveDate::from_ymd_opt(2022, 10, 10).unwrap(), Some("aoeu".to_string()), ))); t3.borrow_mut().parent = Some(t2.clone()); assert_eq!(*lkread.state.borrow().db.get("t1").unwrap().borrow(), *t1.borrow()); assert_eq!(*lkread.state.borrow().db.get("t2").unwrap().borrow(), *t2.borrow()); assert_eq!(*lkread.state.borrow().db.get("t3").unwrap().borrow(), *t3.borrow()); LKEval::new(command_parser::cmd("dump").unwrap(), lkread.state.clone(), prompt_password) .eval() .print(); assert_eq!( std::fs::read_to_string("test_dump").expect("read"), "add t1 R 99 2022-10-10\nadd t2 R 99 2022-10-10 test ^t1\nadd t3 R 99 2022-10-10 aoeu ^t2\n".to_string() ); let pr = LKEval::new(command_parser::cmd("enc t3").unwrap(), lkread.state.clone(), |v| { if v == "/" { Ok("a".to_string()) } else { Ok("".to_string()) } }) .eval(); assert_eq!( pr.out, LKOut::from_vecs( vec!["fief gild sits can un very".to_string()], vec![ "warning: password / is not marked as correct".to_string(), "warning: password t1 is not marked as correct".to_string(), "warning: password t2 is not marked as correct".to_string(), "warning: password t3 is not marked as correct".to_string(), ] ) ); lkread.state.borrow_mut().secrets.clear(); let pr = LKEval::new(command_parser::cmd("pb enc t3").unwrap(), lkread.state.clone(), |v| { if v == "/" { Ok("a".to_string()) } else { Ok("".to_string()) } }) .eval(); assert_eq!( pr.out, LKOut::from_vecs( vec!["Copied output with command ./test_pb".to_string()], vec![ "warning: password / is not marked as correct".to_string(), "warning: password t1 is not marked as correct".to_string(), "warning: password t2 is not marked as correct".to_string(), "warning: password t3 is not marked as correct".to_string(), ] ) ); assert_eq!(std::fs::read_to_string("test_pb_out").expect("read"), "fief gild sits can un very"); } }