use crate::lk::LKRef; use crate::parser::command_parser; use crate::structs::{Command, LKErr, LKOut, HISTORY_FILE}; use crate::utils::editor::{password, Editor, EditorRef}; #[derive(Debug)] pub struct LKRead { pub rl: EditorRef, pub prompt: String, pub state: LKRef, pub cmd: String, pub input: Option, pub read_password: fn(String) -> std::io::Result, } #[derive(Debug)] pub struct LKEval<'a> { pub rl: EditorRef, pub cmd: Command<'a>, pub state: LKRef, pub read_password: fn(String) -> std::io::Result, } #[derive(Debug)] pub struct LKPrint { pub out: LKOut, pub quit: bool, pub state: LKRef, } impl LKRead { pub fn new(rl: EditorRef, prompt: String, state: LKRef) -> Self { Self { rl, prompt, state, cmd: "".to_string(), input: None, read_password: password, } } pub fn read(&mut self) -> LKEval { let history_file = HISTORY_FILE.to_str().unwrap(); self.cmd = match &self.input { Some(cmd) => cmd.to_string(), None => match self.rl.lock().readline(&*self.prompt) { Ok(str) => str, Err(LKErr::EOF) => "quit".to_string(), Err(LKErr::Error(_)) => "quit".to_string(), Err(err) => { return LKEval::new( self.rl.clone(), Command::Error(LKErr::ReadError(err.to_string())), self.state.clone(), self.read_password, ) } } }; match command_parser::cmd(&self.cmd) { Ok(cmd) => LKEval::new(self.rl.clone(), cmd, self.state.clone(), self.read_password), Err(err) => { self.rl.lock().add_history_entry(&self.cmd); self.rl.lock().save_history(&history_file).ok(); LKEval::new(self.rl.clone(), Command::Error(LKErr::ParseError(err)), self.state.clone(), self.read_password) }, } } pub fn refresh(&mut self) {} pub fn quit(&mut self) {} } impl<'a> LKEval<'a> { pub fn new(rl: EditorRef, cmd: Command<'a>, state: LKRef, read_password: fn(String) -> std::io::Result) -> Self { Self { rl, cmd, state, read_password, } } pub fn news(cmd: Command<'a>, state: LKRef) -> Self { LKEval::new(Editor::new(), cmd, state, |_| { Err(std::io::Error::new(std::io::ErrorKind::NotConnected, "could not read password")) }) } pub fn newd(cmd: Command<'a>, state: LKRef, read_password: fn(String) -> std::io::Result) -> Self { LKEval::new(Editor::new(), cmd, state, read_password) } pub fn eval(&self) -> LKPrint { let out = LKOut::new(); let mut quit: bool = false; let history_file = HISTORY_FILE.to_str().unwrap(); let mut to_history = true; self.rl.lock().clear_history(); self.rl.lock().load_history(&history_file).ok(); match &self.cmd { Command::Quit => { out.e("Bye!".to_string()); quit = true; } Command::Ls(filter) => { self.cmd_ls(&out, filter.to_string(), |a, b| a.lock().borrow().name.cmp(&b.lock().borrow().name)) } Command::Ld(filter) => { self.cmd_ls(&out, filter.to_string(), |a, b| a.lock().borrow().date.cmp(&b.lock().borrow().date)) } Command::Add(name) => self.cmd_add(&out, &name), Command::Keep(name) => self.cmd_keep(&out, &name), Command::Comment(name, comment) => self.cmd_comment(&out, &name, &comment), Command::Rm(name) => match self.get_password(name) { Some(pwd) => { self.state.lock().borrow_mut().db.remove(&pwd.lock().borrow().name); out.o(format!("removed {}", pwd.lock().borrow().name)); } None => out.e(format!("error: password {} not found", name)), }, Command::Enc(name) => { self.cmd_enc(&out, name); } Command::Gen(num, name) => self.cmd_gen(&out, &num, &name), Command::PasteBuffer(command) => self.cmd_pb(&out, command), Command::Source(script) => { quit = self.cmd_source(&out, script); } Command::Dump(script) => self.cmd_dump(&out, script), Command::Set(key, value) => { to_history = false; self.cmd_set(&out, key, value); } Command::Pass(name, None) => self.cmd_pass(&out, &name, &None), Command::Pass(name, pass) => { to_history = false; self.cmd_pass(&out, &name, &pass); }, Command::UnPass(Some(name)) => match self.state.lock().borrow_mut().secrets.remove(name) { Some(_) => out.o(format!("Removed saved password for {}", name)), None => out.e(format!("error: saved password for {} not found", name)), }, Command::UnPass(None) => { self.state.lock().borrow_mut().secrets.clear(); out.o("forgot all cached masters".to_string()); }, Command::Correct(name) => self.cmd_correct(&out, name, true, None), Command::Uncorrect(name) => self.cmd_correct(&out, name, false, None), Command::Noop => { to_history = false; }, Command::Help => { out.o(concat!( "hel - S/KEY (RFC 2289) deterministic passwords from your master + the entry\n", "name; nothing secret is stored. The default mode is six short, memorable words\n", "(the \"correct horse battery staple\" idea from xkcd 936).\n", "\n", "entries\n", " add [len][mode] [seq] [date] [text] [^parent] define an entry\n", " ls [regex] list by name ld [regex] list by date\n", " mv rename / move rm remove\n", " comment [text] set or clear the comment\n", "\n", "passwords\n", " enc show the generated password\n", " gen[N] N numbered variants; name ends in G.. (all) or X.. (random)\n", " pass [pw] cache a master / override for an entry's subtree\n", " unpass [name] forget a cached password (unpass / = root; unpass = all)\n", " correct trust this password's hash uncorrect untrust it\n", "\n", "catalog\n", " dump print the catalog ls list it\n", " save [target] write it (file / localStorage key / |command)\n", " source load it set config\n", "\n", "modes R words C camel N hyphenated H hex B base64 D decimal (U.. = UPPER)\n", " R is the six-word S/KEY form: memorable, pronounceable, easy to type anywhere." ).to_string()); } Command::Mv(name, folder) => self.cmd_mv(&out, &name, &folder), Command::Error(error) => { to_history = false; match error { LKErr::ParseError(e) => out.e(e.to_string()), LKErr::ReadError(e) => out.e(e.to_string()), LKErr::EOF => out.e("error: end of file".to_string()), LKErr::Error(e) => out.e(format!("error: {}", e.to_string())), }; }, } if to_history { self.rl.lock().add_history_entry(self.cmd.to_string().as_str()); self.rl.lock().save_history(&history_file).ok(); } LKPrint::new(out, quit, self.state.clone()) } } impl LKPrint { pub fn new(out: LKOut, quit: bool, state: LKRef) -> Self { Self { out, quit, state } } pub fn print(&mut self) -> bool { self.out.print_err(); self.out.print_out(); return !self.quit; } } impl PartialEq for LKPrint { fn eq(&self, other: &Self) -> bool { self.out == other.out && self.quit == other.quit && *self.state.lock() == *other.state.lock() } } #[cfg(test)] mod tests { use super::*; use std::collections::HashSet; use crate::lk::LK; use crate::password::Password; use crate::structs::Mode; use crate::utils::date::Date; use parking_lot::ReentrantMutex; use std::cell::RefCell; use std::collections::HashMap; use std::sync::Arc; #[test] fn exec_cmds_basic() { let lk = Arc::new(ReentrantMutex::new(RefCell::new(LK::new()))); assert_eq!( LKEval::news(Command::Ls(".".to_string()), lk.clone()).eval(), LKPrint::new(LKOut::from_vecs(vec![], vec![]), false, lk.clone()) ); let pwd1 = Password::from_password(Password { name: "t1".to_string(), prefix: None, length: None, mode: Mode::Regular, seq: 99, date: Date::new(2022, 12, 30), comment: Some("comment".to_string()), parent: None, }); assert_eq!( LKEval::news(Command::Add(pwd1.clone()), lk.clone()) .eval() .state .lock() .borrow() .db .iter() .map(|x| (x.0.to_string(), x.1.lock().borrow().to_string())) .collect::>(), { let mut db = HashMap::new(); db.insert(pwd1.lock().borrow().name.to_string(), pwd1.clone()); db.into_iter() .map(|x| (x.0.to_string(), x.1.lock().borrow().to_string())) .collect::>() } ); assert_eq!( LKEval::news(Command::Ls(".".to_string()), lk.clone()).eval(), LKPrint::new( LKOut::from_vecs(vec![" 1 t1 R 99 2022-12-30 comment".to_string()], vec![]), false, lk.clone() ) ); assert_eq!( LKEval::news(Command::Quit, lk.clone()).eval(), LKPrint::new(LKOut::from_vecs(vec![], vec!["Bye!".to_string()]), true, lk.clone()) ); let pwd2 = Password::from_password(Password { name: "t2".to_string(), prefix: None, length: None, mode: Mode::Regular, seq: 99, date: Date::new(2022, 12, 31), comment: Some("bli blup".to_string()), parent: None, }); assert_eq!( LKEval::news(Command::Add(pwd2.clone()), lk.clone()) .eval() .state .lock() .borrow() .db .iter() .map(|x| (x.0.to_string(), x.1.lock().borrow().to_string())) .collect::>(), { let mut db = HashMap::new(); db.insert(pwd1.lock().borrow().name.to_string(), pwd1.clone()); db.insert(pwd2.lock().borrow().name.to_string(), pwd2.clone()); db.into_iter().map(|x| (x.0, x.1.lock().borrow().to_string())).collect::>() } ); assert_eq!( LKEval::news(Command::Ls(".".to_string()), lk.clone()).eval(), LKPrint::new( LKOut::from_vecs( vec![ " 1 t1 R 99 2022-12-30 comment".to_string(), " 2 t2 R 99 2022-12-31 bli blup".to_string() ], vec![] ), false, lk.clone() ) ); assert_eq!( LKEval::news(Command::Rm("2".to_string()), lk.clone()).eval(), LKPrint::new(LKOut::from_vecs(vec!["removed t2".to_string()], vec![]), false, lk.clone()) ); assert_eq!( LKEval::news(Command::Ls(".".to_string()), lk.clone()).eval(), LKPrint::new( LKOut::from_vecs(vec![" 1 t1 R 99 2022-12-30 comment".to_string()], vec![]), false, lk.clone() ) ); } #[test] fn read_pwd_test() { let lk = Arc::new(ReentrantMutex::new(RefCell::new(LK::new()))); let t1 = Password::from_password(Password::new( None, "t1".to_string(), None, Mode::Regular, 99, Date::new(2022, 12, 30), None, )); let t2 = Password::from_password(Password::new( None, "t2".to_string(), None, Mode::Regular, 99, Date::new(2022, 12, 30), None, )); let t3 = Password::from_password(Password::new( None, "t3".to_string(), None, Mode::Regular, 99, Date::new(2022, 12, 30), None, )); assert_eq!( LKEval::news(Command::Add(t1.clone()), lk.clone()).eval(), LKPrint::new(LKOut::from_vecs(vec![], vec![]), false, lk.clone()) ); assert_eq!( LKEval::news(Command::Add(t2.clone()), lk.clone()).eval(), LKPrint::new(LKOut::from_vecs(vec![], vec![]), false, lk.clone()) ); assert_eq!( LKEval::news(Command::Add(t3.clone()), lk.clone()).eval(), LKPrint::new(LKOut::from_vecs(vec![], vec![]), false, lk.clone()) ); assert_eq!( LKEval::news(Command::Mv("t3".to_string(), "t2".to_string()), lk.clone()).eval(), LKPrint::new(LKOut::from_vecs(vec![], vec![]), false, lk.clone()) ); assert_eq!( LKEval::news(Command::Mv("t2".to_string(), "t1".to_string()), lk.clone()).eval(), LKPrint::new(LKOut::from_vecs(vec![], vec![]), false, lk.clone()) ); assert_eq!( LKEval::newd(Command::Enc("t3".to_string()), lk.clone(), |p| if p == "NULL" { Ok("a".to_string()) } else { Err(std::io::Error::new(std::io::ErrorKind::NotFound, "test")) }) .eval(), LKPrint::new( LKOut::from_vecs(vec![], vec!["error: master for t3 not found".to_string()]), false, lk.clone() ) ); assert_eq!( LKEval::newd(Command::Enc("t3".to_string()), lk.clone(), |p| if p == "/" { Ok("a".to_string()) } else { Err(std::io::Error::new(std::io::ErrorKind::NotFound, "test")) }) .eval(), LKPrint::new( LKOut::from_vecs( vec!["san bud most noon jaw cash".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(), ] ), false, lk.clone() ) ); assert_eq!( LKEval::newd(Command::Enc("t2".to_string()), lk.clone(), |p| if p == "NULL" { Ok("a".to_string()) } else { Err(std::io::Error::new(std::io::ErrorKind::NotFound, "test")) }) .eval(), LKPrint::new( LKOut::from_vecs( vec!["alga barn wise tim skin mock".to_string()], vec!["warning: password t2 is not marked as correct".to_string()] ), false, lk.clone() ) ); assert_eq!( LKEval::newd(Command::Enc("t1".to_string()), lk.clone(), |p| if p == "NULL" { Ok("a".to_string()) } else { Err(std::io::Error::new(std::io::ErrorKind::NotFound, "test")) }) .eval(), LKPrint::new( LKOut::from_vecs( vec!["lime rudy jay my kong tack".to_string()], vec!["warning: password t1 is not marked as correct".to_string()] ), false, lk.clone() ) ); } #[test] fn exec_cmd_pass() { let lk = Arc::new(ReentrantMutex::new(RefCell::new(LK::new()))); let t1 = Password::from_password(Password::new( None, "t1".to_string(), None, Mode::Regular, 99, Date::new(2022, 12, 30), None, )); LKEval::news(Command::Add(t1.clone()), lk.clone()).eval(); LKEval::newd(Command::Pass("t1".to_string(), None), lk.clone(), |_| { Ok("test pwd1".to_string()) }).eval(); assert_eq!(lk.lock().borrow().secrets[&"t1".to_string()], "test pwd1"); LKEval::news(Command::Pass("t1".to_string(), Some("other pw".to_string())), lk.clone()).eval(); assert_eq!(lk.lock().borrow().secrets[&"t1".to_string()], "other pw"); } }