Split project into three workspaces: hel (the library), helcli (the tool) and helwasm (the wasm code). Move wasm incompatible code to extra modules in utils.rs to be implementable separately for wasm.

This commit is contained in:
Oleksandr Kozachuk
2023-01-01 18:50:16 +01:00
parent cff9e3f90d
commit eced302282
18 changed files with 330 additions and 181 deletions
+31
View File
@@ -0,0 +1,31 @@
[package]
name = "hel"
version = "0.1.0"
authors = ["ok2"]
edition = "2021"
[lib]
name = "hel"
crate-type = ["lib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
peg = "0.8.1"
anyhow = "1.0.66"
lazy_static = "1.4.0"
regex = "1.6.0"
thiserror = "1.0.37"
anyerror = "0.1.7"
sha1 = "0.10.5"
base64 = "0.20.0"
rpassword = "7.2.0"
shlex = "1.1.0"
shellexpand = "3.0.0"
scopeguard = "1.1.0"
[target.'cfg(not(wasm))'.dependencies]
chrono = "0.4.23"
rand = "0.8.5"
home = "0.5.4"
rustyline = "10.0.0"
+501
View File
@@ -0,0 +1,501 @@
use regex::Regex;
use rpassword::prompt_password;
use sha1::{Digest, Sha1};
use std::cmp::min;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::io::{BufRead, BufReader};
use std::io::{BufWriter, Write};
use crate::parser::command_parser;
use crate::password::fix_password_recursion;
use crate::password::{Name, Password, PasswordRef};
use crate::repl::LKEval;
use crate::structs::{LKOut, Radix, CORRECT_FILE, DUMP_FILE};
use crate::utils::{call_cmd_with_input, get_cmd_args_from_command, get_copy_command_from_env, rnd};
impl<'a> LKEval<'a> {
pub fn get_password(&self, name: &String) -> Option<PasswordRef> {
match self.state.borrow().ls.get(name) {
Some(pwd) => Some(pwd.clone()),
None => match self.state.borrow().db.get(name) {
Some(pwd) => Some(pwd.clone()),
None => None,
},
}
}
pub fn read_master(&self, out: &LKOut, pwd: PasswordRef, read: bool) -> Option<String> {
if read {
match self.read_master(&out, pwd.clone(), false) {
Some(p) => {
return Some(p);
}
None => (),
}
}
let parent = match &pwd.borrow().parent {
Some(p) => p.borrow().name.to_string(),
None => "/".to_string(),
};
let secret = match self.state.borrow().secrets.get(&parent) {
Some(p) => Some(p.clone()),
None => None,
};
match (pwd.borrow().parent.clone(), secret) {
(_, Some(s)) => Some(s.to_string()),
(None, None) => {
if read {
match (self.read_password)("Master: ".to_string()) {
Ok(password) => {
let name = "/".to_string();
self.cmd_correct(&out, &name, true, Some(password.clone()));
self.state.borrow_mut().secrets.insert(name, password.clone());
Some(password)
}
Err(_) => None,
}
} else {
None
}
}
(Some(pn), None) => {
let password = if read {
(self.read_password)(format!("Password for {}: ", pn.borrow().name)).ok()
} else {
None
};
if password.is_some() && password.as_ref().unwrap().len() > 0 {
let name = pn.borrow().name.to_string();
self.cmd_correct(&out, &name, true, Some(password.as_ref().unwrap().clone()));
self.state.borrow_mut().secrets.insert(name, password.as_ref().unwrap().clone());
password
} else {
match self.read_master(&out, pn.clone(), read) {
Some(master) => {
let password = pn.borrow().encode(master.as_str());
let name = pn.borrow().name.to_string();
self.cmd_correct(&out, &name, true, Some(master));
self.state.borrow_mut().secrets.insert(name, password.clone());
Some(password)
}
None => None,
}
}
}
}
}
pub fn cmd_add(&self, out: &LKOut, name: &PasswordRef) {
let state = &mut self.state.borrow_mut();
let mut fix = false;
{
let db = &mut state.db;
let pwname = &name.borrow().name.to_string();
if db.get(pwname).is_some() {
out.e(format!("error: password {} already exist", pwname));
} else {
db.insert(pwname.to_string(), name.clone());
fix = true;
}
}
if fix {
state.fix_hierarchy();
}
}
pub fn cmd_leave(&self, out: &LKOut, name: &Name) {
let pwd = match self.state.borrow().ls.get(name) {
Some(pwd) => pwd.clone(),
None => { out.e(format!("error: {} not found", name)); return; }
};
self.cmd_add(&out, &pwd);
}
pub fn cmd_mv(&self, out: &LKOut, name: &String, folder: &String) {
match self.get_password(name) {
Some(pwd) => {
if folder == "/" {
pwd.borrow_mut().parent = None
} else {
match self.get_password(folder) {
Some(fld) => {
pwd.borrow_mut().parent = Some(fld.clone());
fix_password_recursion(pwd.clone());
}
None => out.e(format!("error: folder {} not found", folder)),
}
}
}
None => out.e(format!("error: password with name {} not found", name)),
}
}
pub fn cmd_pass(&self, out: &LKOut, name: &String) {
match self.get_password(name) {
Some(p) => {
let pwd = (self.read_password)(format!("Password for {}: ", p.borrow().name)).unwrap();
self.cmd_correct(&out, &p.borrow().name, true, Some(pwd.clone()));
self.state.borrow_mut().secrets.insert(p.borrow().name.to_string(), pwd);
}
None => {
if name == "/" {
let pwd = (self.read_password)("Master: ".to_string()).unwrap();
self.cmd_correct(&out, &"/".to_string(), true, Some(pwd.clone()));
self.state
.borrow_mut()
.secrets
.insert("/".to_string(), (self.read_password)("Master: ".to_string()).unwrap());
} else {
out.e(format!("error: password with name {} not found", name));
}
}
}
}
pub fn cmd_comment(&self, out: &LKOut, name: &String, comment: &Option<String>) {
match self.get_password(name) {
Some(pwd) => {
pwd.borrow_mut().comment = match comment {
Some(c) => Some(c.to_string()),
None => None,
}
}
None => out.e("error: password not found".to_string()),
}
}
pub fn cmd_enc(&self, out: &LKOut, name: &String) -> Option<(String, String)> {
let root_folder = "/".to_string();
let (name, pass) = if name == "/" && self.state.borrow().secrets.contains_key(&root_folder) {
(root_folder.to_string(), self.state.borrow().secrets.get(&root_folder).unwrap().to_string())
} else {
let pwd = match self.get_password(name) {
Some(p) => p.clone(),
None => {
out.e(format!("error: name {} not found", name));
return None;
}
};
let name = pwd.borrow().name.to_string();
if self.state.borrow().secrets.contains_key(&name) {
(name.clone(), self.state.borrow().secrets.get(&name).unwrap().to_string())
} else {
match self.read_master(&out, pwd.clone(), true) {
Some(sec) => (name.clone(), pwd.borrow().encode(sec.as_str())),
None => {
out.e(format!("error: master for {} not found", name));
return None;
}
}
}
};
if out.active() {
out.o(pass.clone());
self.cmd_correct(&out, &name, true, Some(pass.clone()));
}
Some((name, pass))
}
pub fn cmd_pb(&self, out: &LKOut, command: &String) {
match command_parser::cmd(command) {
Ok(cmd) => {
let print = LKEval::new(cmd, self.state.clone(), self.read_password).eval();
let data = print.out.data();
print.out.copy_err(&out);
if data.len() > 0 {
let (copy_command, copy_cmd_args) = get_copy_command_from_env();
match call_cmd_with_input(&copy_command, &copy_cmd_args, &data) {
Ok(s) if s.len() > 0 => {
out.o(format!(
"Copied output with the command {}, and got following output:",
copy_command
));
out.o(s.trim().to_string());
}
Ok(_) => out.o(format!("Copied output with command {}", copy_command)),
Err(e) => out.e(format!("error: failed to copy: {}", e.to_string())),
};
}
}
Err(e) => out.e(format!("error: faild to parse command {}: {}", command, e.to_string())),
};
}
pub fn cmd_source(&self, out: &LKOut, source: &String) -> bool {
let script = if source.trim().ends_with("|") {
let (cmd, args) = match get_cmd_args_from_command(source.trim().trim_end_matches('|')) {
Ok(c) => c,
Err(e) => {
out.e(format!("error: failed to parse command {:?}: {}", source, e.to_string()));
return false;
}
};
match call_cmd_with_input(&cmd, &args, "") {
Ok(o) => o,
Err(e) => {
out.e(format!("error: failed to execute command {}: {}", cmd, e.to_string()));
return false;
}
}
} else {
let script = shellexpand::full(source).unwrap().into_owned();
match std::fs::read_to_string(script) {
Ok(script) => script,
Err(e) => {
out.e(format!("error: failed to read file {}: {}", source, e.to_string()));
return false;
}
}
};
match command_parser::script(&script) {
Ok(cmd_list) => {
for cmd in cmd_list {
let print = LKEval::new(cmd, self.state.clone(), prompt_password).eval();
print.out.copy(&out);
if print.quit { return true; }
}
}
Err(e) => {
out.e(format!("error: {}", e.to_string()));
}
};
false
}
pub fn cmd_dump(&self, out: &LKOut, script: &Option<String>) {
let script = match script {
Some(p) => p,
None => DUMP_FILE.to_str().unwrap(),
};
let script = shellexpand::full(script).unwrap().into_owned();
fn save_dump(data: &HashMap<Name, PasswordRef>, script: &String) -> std::io::Result<()> {
let file = fs::File::create(script)?;
let mut writer = BufWriter::new(file);
let mut vals = data.values().map(|v| v.clone()).collect::<Vec<PasswordRef>>();
vals.sort_by(|a, b| a.borrow().name.cmp(&b.borrow().name));
for pwd in vals {
writeln!(writer, "add {}", pwd.borrow().to_string())?
}
Ok(())
}
if script.trim().starts_with("|") {
let (cmd, args) = match get_cmd_args_from_command(script.trim().trim_start_matches('|')) {
Ok(c) => c,
Err(e) => {
out.e(format!("error: failed to parse command {:?}: {}", script, e.to_string()));
return;
}
};
let data = self
.state
.borrow()
.db
.values()
.map(|v| format!("add {}", v.borrow().to_string()))
.collect::<Vec<String>>()
.join("\n");
let output = match call_cmd_with_input(&cmd, &args, data.as_str()) {
Ok(o) => o,
Err(e) => {
out.e(format!("error: failed to execute command {}: {}", cmd, e.to_string()));
return;
}
};
if output.len() > 0 {
out.e(format!("Passwords dumped to command {} and got following output:", cmd));
out.o(output);
} else {
out.o(format!("Passwords dumped to command {}", cmd));
}
} else {
match save_dump(&self.state.borrow().db, &script) {
Ok(()) => out.o(format!("Passwords dumped to {}", script)),
Err(e) => out.e(format!("error: failed to dump passswords to {}: {}", script, e.to_string())),
};
}
}
pub fn cmd_ls<F>(&self, out: &LKOut, filter: String, sort_by: F)
where
F: Fn(&PasswordRef, &PasswordRef) -> std::cmp::Ordering,
{
let re = match Regex::new(&filter) {
Ok(re) => re,
Err(e) => {
out.e(format!("error: failed to parse re: {:?}", e));
return;
}
};
let mut tmp: Vec<PasswordRef> = vec![];
for (_, name) in &self.state.borrow().db {
if re.find(&name.borrow().to_string()).is_some() {
tmp.push(name.clone());
} else if re.find(&name.borrow().name).is_some() {
tmp.push(name.clone());
} else if name.borrow().comment.is_some() && re.find(&name.borrow().comment.as_ref().unwrap()).is_some() {
tmp.push(name.clone());
}
}
tmp.sort_by(|a,b| a.borrow().name.cmp(&b.borrow().name));
tmp.sort_by(sort_by);
self.state.borrow_mut().ls.clear();
let mut counter = 1;
for pwd in tmp {
let key = Radix::new(counter, 36).unwrap().to_string();
counter += 1;
self.state.borrow_mut().ls.insert(key.clone(), pwd.clone());
out.o(format!("{:>3} {}", key, pwd.borrow().to_string()));
}
}
pub fn cmd_correct(&self, out: &LKOut, name: &String, correct: bool, check: Option<String>) {
let (check, pwd) = match check {
Some(p) => (true, Some((name.to_string(), p))),
None => (
false,
self.cmd_enc(
&LKOut::from_lkout(
None,
match &out.err {
Some(e) => Some(e.clone()),
None => None,
},
),
&name,
),
),
};
let (name, pwd) = match pwd {
Some(v) => v,
None => return,
};
fn load_lines() -> std::io::Result<HashSet<String>> {
let file = fs::File::open(CORRECT_FILE.to_str().unwrap())?;
let reader = BufReader::new(file);
let mut lines = HashSet::new();
for line in reader.lines() {
lines.insert(line?.trim().to_owned());
}
Ok(lines)
}
let mut data = match load_lines() {
Ok(d) => d,
Err(_) => HashSet::new(),
};
let mut sha1 = Sha1::new();
sha1.update(name.to_string());
sha1.update(pwd);
let encpwd = format!("{:x}", sha1.finalize());
if check {
if data.contains(&encpwd) {
return;
}
out.e(format!("warning: password {} is not marked as correct", name));
return;
}
if correct {
if data.contains(&encpwd) {
return;
}
data.insert(encpwd);
} else {
if !data.contains(&encpwd) {
return;
}
data.remove(&encpwd);
}
fn save_lines(data: &HashSet<String>) -> std::io::Result<()> {
let file = fs::File::create(CORRECT_FILE.to_str().unwrap())?;
let mut writer = BufWriter::new(file);
for entry in data {
writeln!(writer, "{}", entry)?;
}
Ok(())
}
match save_lines(&data) {
Ok(()) => out.o(format!(
"Hash of the password {} {} {}",
name,
if correct { "remembered to" } else { "removed from" },
CORRECT_FILE.to_str().unwrap()
)),
Err(e) => out.e(format!("error: failed to write: {}", e.to_string())),
};
}
pub fn cmd_gen(&self, out: &LKOut, num: &u32, name: &PasswordRef) {
lazy_static! {
static ref RE: Regex = Regex::new(r"^.+?(G+|X+)$").unwrap();
}
let num: usize = (*num).try_into().unwrap();
let pwd = name.borrow();
let mut genpwds: Vec<PasswordRef> = Vec::new();
match RE.captures(pwd.name.as_ref()) {
Some(caps) => {
let gen = &caps[1];
if gen.starts_with("G") {
let name = pwd.name.trim_end_matches('G');
for num in 1..10_u32.pow(gen.len().try_into().unwrap()) {
let npwd = Password::from_password(&pwd);
npwd.borrow_mut().name = format!("{}{}", name, num).to_string();
genpwds.push(npwd);
}
} else {
let name = pwd.name.trim_end_matches('X');
let num = rnd::range(1, 10_u32.pow(gen.len().try_into().unwrap()));
let npwd = Password::from_password(&pwd);
npwd.borrow_mut().name = format!("{}{}", name, num).to_string();
genpwds.push(npwd);
}
}
None => {
let npwd = Password::from_password(&pwd);
genpwds.push(npwd);
}
}
self.state.borrow_mut().ls.clear();
let mut counter = 1;
let mut lspwds: Vec<(PasswordRef, String)> = Vec::new();
for num in 0..genpwds.len() {
let pwd = genpwds[num].clone();
let key = Radix::new(counter, 36).unwrap().to_string();
counter += 1;
self.state.borrow_mut().ls.insert(key.to_string(), pwd.clone());
lspwds.push((pwd, key));
}
self.state.borrow().fix_hierarchy();
let mut err = match &out.err {
Some(e) => Some(e.clone()),
None => None,
};
let mut encpwds: Vec<(PasswordRef, String)> = Vec::new();
for (pwd, key) in lspwds {
let pass = match self.cmd_enc(&LKOut::from_lkout(None, err), &key) {
Some((name, pass)) => {
if name != pwd.borrow().name {
panic!("INTERNAL_ERROR: wrong name found: {} != {}", name, pwd.borrow().name);
};
pass
}
None => {
out.e(format!("error: failed to encrypt password"));
return;
}
};
err = None;
encpwds.push((pwd.clone(), pass));
}
encpwds.sort_by(|a, b| b.1.len().cmp(&a.1.len()));
self.state.borrow_mut().ls.clear();
let mut counter = 1;
out.o(format!("{:>3} {:>36} {:>4} {}", "", "Password", "Len", "Name"));
for num in (encpwds.len() - min(genpwds.len(), num))..encpwds.len() {
let (pwd, pass) = (encpwds[num].0.clone(), encpwds[num].1.to_string());
let key = Radix::new(counter, 36).unwrap().to_string();
counter += 1;
self.state.borrow_mut().ls.insert(key.clone(), pwd.clone());
out.o(format!("{:>3} {:>36} {:>4} {}", key, pass, pass.len(), pwd.borrow().to_string()));
}
}
}
+15
View File
@@ -0,0 +1,15 @@
#[macro_use]
extern crate lazy_static;
#[allow(unused_imports)]
#[macro_use(defer)]
extern crate scopeguard;
pub mod commands;
pub mod lk;
pub mod parser;
pub mod password;
pub mod repl;
pub mod skey;
pub mod structs;
pub mod utils;
+55
View File
@@ -0,0 +1,55 @@
use crate::password::{fix_password_recursion, Name, PasswordRef};
use regex::{Captures, Regex};
use std::collections::HashMap;
#[derive(PartialEq, Debug)]
pub struct LK {
pub db: HashMap<Name, PasswordRef>,
pub ls: HashMap<String, PasswordRef>,
pub secrets: HashMap<Name, String>,
}
impl LK {
pub fn new() -> Self {
Self {
db: HashMap::new(),
ls: HashMap::new(),
secrets: HashMap::new(),
}
}
pub fn fix_hierarchy(&self) {
lazy_static! {
static ref RE: Regex = Regex::new(r"\s*\^([!-~]+)").unwrap();
}
for db in vec![&self.db, &self.ls] {
for (_, name) in db {
let comment = name.borrow().comment.clone();
match comment {
Some(comment) => {
let mut changed = false;
let new = RE
.replace(comment.as_str(), |c: &Captures| {
let folder = c[1].to_string();
match self.db.get(&folder) {
Some(entry) => {
name.borrow_mut().parent = Some(entry.clone());
changed = true;
}
None => (),
}
""
})
.trim()
.to_string();
if changed && new != comment {
name.borrow_mut().comment = if new.len() > 0 { Some(new) } else { None }
}
}
None => (),
}
fix_password_recursion(name.clone());
}
}
}
}
+424
View File
@@ -0,0 +1,424 @@
extern crate peg;
use crate::password::Password;
use crate::structs::{Command, LKErr, Mode};
use crate::utils::date::Date;
use std::{cell::RefCell, rc::Rc};
peg::parser! {
pub grammar command_parser() for str {
pub rule cmd() -> Command<'input> = c:(info_cmd_list() / mod_cmd_list() / enc_cmd_list() / asides_cmd_list()) { c }
pub rule info_cmd_list() -> Command<'input> = space()* c:(ls_cmd() / ld_cmd() / pb_cmd() / dump_cmd() / dump_def_cmd()) { c }
pub rule mod_cmd_list() -> Command<'input> = space()* c:(add_cmd() / leave_cmd() / mv_cmd() / rm_cmd() / comment_cmd ()) { c }
pub rule asides_cmd_list() -> Command<'input> = space()* c:(help_cmd() / source_cmd() / quit_cmd() / noop_cmd() / error_cmd()) { c }
pub rule enc_cmd_list() -> Command<'input> = space()* c:(enc_cmd() / gen_cmd() / pass_cmd() / unpass_cmd() / correct_cmd() / uncorrect_cmd()) { c }
pub rule script() -> Vec<Command<'input>> = c:(info_cmd_list() / mod_cmd_list() / enc_cmd_list() / asides_cmd_list()) ++ "\n" { c }
rule space() -> &'input str = s:$(
" " // Space (U+0020)
/ "\u{00A0}" // Non-breaking space (U+00A0)
/ "\u{2009}" // Thin space (U+2009)
/ "\u{2003}" // Em space (U+2003)
/ "\u{2002}" // En space (U+2002)
/ "\t" // Tab (U+0009)
// / "\n" // Line feed (U+000A)
/ "\r" // Carriage return (U+000D)
/ "\u{000C}" // Form feed (U+000C)
/ "\u{200B}" // Zero-width space (U+200B)
/ "\u{3000}" // Ideographic space (U+3000)
) { s }
rule _() -> &'input str = s:$(space()+) { s }
rule comment() -> String = _ c:$([' '..='~']+) { c.to_string() }
rule word() -> String = n:$(['!'..='~']+) { n.to_string() }
rule num() -> u32 = n:$(['0'..='9']+) {? n.parse().or(Err("not a number")) }
rule pname() -> Password = &(word() _ word() _ num()? mode() _ num() _ date()) pr:word() _ pn:word() _ pl:num()? pm:mode() _ ps:num() _ pd:date() pc:comment()?
{ Password::new(Some(pr), pn, pl, pm, ps, pd, pc) }
rule jname() -> Password = &(word() _ num()? mode() _ num() _ date()) pn:word() _ pl:num()? pm:mode() _ ps:num() _ pd:date() pc:comment()?
{ Password::new(None, pn, pl, pm, ps, pd, pc) }
rule mname() -> Password = &(word() _ word() _ num()? mode() _ date()) pr:word() _ pn:word() _ pl:num()? pm:mode() _ pd:date() pc:comment()?
{ Password::new(Some(pr), pn, pl, pm, 99, pd, pc) }
rule sname() -> Password = &(word() _ num()? mode() _ date()) pn:word() _ pl:num()? pm:mode() _ pd:date() pc:comment()?
{ Password::new(None, pn, pl, pm, 99, pd, pc) }
rule nname() -> Password = &(word() _ num()? mode()) pn:word() _ pl:num()? pm:mode()
{ Password::new(None, pn, pl, pm, 99, Date::now(), None) }
rule qname() -> Password = &(word()) pn:word()
{ Password::new(None, pn, None, Mode::NoSpaceCamel, 99, Date::now(), None) }
pub rule name() -> Password = name:(jname() / pname() / mname() / sname() / nname() / qname())? {?
match name { Some(n) => Ok(n), None => Err("failed to parse password description") }
}
rule ndate() -> Date = y:$("-"? ['0'..='9']*<1,4>) "-" m:$(['0'..='9']*<1,2>) "-" d:$(['0'..='9']*<1,2>) {?
let year: i32 = match y.parse() { Ok(n) => n, Err(_) => return Err("year") };
let month: u32 = match m.parse() { Ok(n) => n, Err(_) => return Err("month") };
let day: u32 = match d.parse() { Ok(n) => n, Err(_) => return Err("day") };
Date::try_new(year, month, day)
}
rule cdate() -> Date = "now" { Date::now() }
rule date() -> Date = d:(ndate() / cdate()) { d }
rule umode() -> Mode = ("U" / "u") m:$("R" / "r" / "N" / "n" / "H" / "h" / "B" / "b") {?
match m.to_uppercase().as_str() {
"R" => Ok(Mode::RegularUpcase),
"N" => Ok(Mode::NoSpaceUpcase),
"H" => Ok(Mode::HexUpcase),
"B" => Ok(Mode::Base64Upcase),
_ => Err("unknown mode"),
}
}
rule rmode() -> Mode = m:$("R" / "r" / "U" / "u" / "N" / "n" / "C" / "c" / "H" / "h" / "B" / "b" / "D" / "d") {?
match m.to_uppercase().as_str() {
"R" => Ok(Mode::Regular),
"N" => Ok(Mode::NoSpace),
"C" => Ok(Mode::NoSpaceCamel),
"U" => Ok(Mode::RegularUpcase),
"H" => Ok(Mode::Hex),
"B" => Ok(Mode::Base64),
"D" => Ok(Mode::Decimal),
_ => Err("unknown mode"),
}
}
rule mode() -> Mode = m:(umode() / rmode()) { m }
rule noop_cmd() -> Command<'input> = ("#" [' '..='~']*)? { Command::Noop }
rule help_cmd() -> Command<'input> = "help" { Command::Help }
rule quit_cmd() -> Command<'input> = "quit" { Command::Quit }
rule pb_cmd() -> Command<'input> = "pb" _ e:$(([' '..='~'])+) { Command::PasteBuffer(e.to_string()) }
rule dump_cmd() -> Command<'input> = "dump" _ s:$(([' '..='~'])+) { Command::Dump(Some(s.to_string())) }
rule dump_def_cmd() -> Command<'input> = "dump" { Command::Dump(None) }
rule source_cmd() -> Command<'input> = "source" _ s:$(([' '..='~'])+) { Command::Source(s.to_string()) }
rule ls_cmd() -> Command<'input> = "ls" f:comment()? { Command::Ls(f.unwrap_or(".".to_string())) }
rule ld_cmd() -> Command<'input> = "ld" f:comment()? { Command::Ld(f.unwrap_or(".".to_string())) }
rule add_cmd() -> Command<'input> = "add" _ name:name() { Command::Add(Rc::new(RefCell::new(name))) }
rule leave_cmd() -> Command<'input> = "leave" _ name:word() { Command::Leave(name.to_string()) }
rule gen_cmd() -> Command<'input> = "gen" n:num()? _ name:name() {
Command::Gen(match n { Some(n) => n, None => 10_u32 }, Rc::new(RefCell::new(name)))
}
rule error_cmd() -> Command<'input> = "error" _ e:$(([' '..='~'])+) { Command::Error(LKErr::Error(e)) }
rule mv_cmd() -> Command<'input> = "mv" _ name:word() _ folder:word() { Command::Mv(name, folder) }
rule pass_cmd() -> Command<'input> = "pass" _ name:word() { Command::Pass(name) }
rule correct_cmd() -> Command<'input> = "correct" _ name:word() { Command::Correct(name) }
rule uncorrect_cmd() -> Command<'input> = "uncorrect" _ name:word() { Command::Uncorrect(name) }
rule unpass_cmd() -> Command<'input> = "unpass" _ name:word() { Command::UnPass(name) }
rule enc_cmd() -> Command<'input> = "enc" _ name:word() { Command::Enc(name) }
rule rm_cmd() -> Command<'input> = "rm" _ name:word() { Command::Rm(name) }
rule comment_cmd() -> Command<'input> = "comment" _ name:word() c:comment()? { Command::Comment(name, c) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_script_test() {
assert_eq!(
command_parser::script(
r###"add t1 C 99 2022-12-14
add t2 C 99 2022-12-14
add t3 C 99 2022-12-14"###
),
Ok(vec![
Command::Add(Rc::new(RefCell::new(Password {
parent: None,
prefix: None,
name: "t1".to_string(),
length: None,
mode: Mode::NoSpaceCamel,
seq: 99,
date: Date::new(2022, 12, 14),
comment: None
}))),
Command::Add(Rc::new(RefCell::new(Password {
parent: None,
prefix: None,
name: "t2".to_string(),
length: None,
mode: Mode::NoSpaceCamel,
seq: 99,
date: Date::new(2022, 12, 14),
comment: None
}))),
Command::Add(Rc::new(RefCell::new(Password {
parent: None,
prefix: None,
name: "t3".to_string(),
length: None,
mode: Mode::NoSpaceCamel,
seq: 99,
date: Date::new(2022, 12, 14),
comment: None
})))
])
);
assert_eq!(
command_parser::script(
r###"add t1 C 99 2022-12-14
add t2 C 99 2022-12-14
add t3 C 99 2022-12-14
"###
),
Ok(vec![
Command::Add(Rc::new(RefCell::new(Password {
parent: None,
prefix: None,
name: "t1".to_string(),
length: None,
mode: Mode::NoSpaceCamel,
seq: 99,
date: Date::new(2022, 12, 14),
comment: None
}))),
Command::Add(Rc::new(RefCell::new(Password {
parent: None,
prefix: None,
name: "t2".to_string(),
length: None,
mode: Mode::NoSpaceCamel,
seq: 99,
date: Date::new(2022, 12, 14),
comment: None
}))),
Command::Add(Rc::new(RefCell::new(Password {
parent: None,
prefix: None,
name: "t3".to_string(),
length: None,
mode: Mode::NoSpaceCamel,
seq: 99,
date: Date::new(2022, 12, 14),
comment: None
}))),
Command::Noop
])
);
assert_eq!(
command_parser::script(
r###"add t1 C 99 2022-12-14
add t2 C 99 2022-12-14
add t3 C 99 2022-12-14
# some comment
"###
),
Ok(vec![
Command::Add(Rc::new(RefCell::new(Password {
parent: None,
prefix: None,
name: "t1".to_string(),
length: None,
mode: Mode::NoSpaceCamel,
seq: 99,
date: Date::new(2022, 12, 14),
comment: None
}))),
Command::Add(Rc::new(RefCell::new(Password {
parent: None,
prefix: None,
name: "t2".to_string(),
length: None,
mode: Mode::NoSpaceCamel,
seq: 99,
date: Date::new(2022, 12, 14),
comment: None
}))),
Command::Add(Rc::new(RefCell::new(Password {
parent: None,
prefix: None,
name: "t3".to_string(),
length: None,
mode: Mode::NoSpaceCamel,
seq: 99,
date: Date::new(2022, 12, 14),
comment: None
}))),
Command::Noop,
Command::Noop
])
);
}
#[test]
fn parse_password_test() {
assert_eq!(
command_parser::name("ableton89 R 99 2020-12-09 xx.ableton@domain.info https://www.ableton.com"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: None,
mode: Mode::Regular,
length: None,
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("xx.ableton@domain.info https://www.ableton.com".to_string())
})
);
assert_eq!(
command_parser::name("ableton89 U 99 2020-12-09 xx.ableton@domain.info https://www.ableton.com"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: None,
mode: Mode::RegularUpcase,
length: None,
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("xx.ableton@domain.info https://www.ableton.com".to_string())
})
);
assert_eq!(
command_parser::name("ableton89 U 2020-12-09"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: None,
mode: Mode::RegularUpcase,
length: None,
seq: 99,
date: Date::new(2020, 12, 09),
comment: None
})
);
assert_eq!(
command_parser::name("#W9 ableton89 R 99 2020-12-09 xx.ableton@domain.info https://www.ableton.com"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: Some("#W9".to_string()),
mode: Mode::Regular,
length: None,
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("xx.ableton@domain.info https://www.ableton.com".to_string())
})
);
assert_eq!(
command_parser::name("#W9 ableton89 N 99 2020-12-09 xx.ableton@domain.info https://www.ableton.com"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: Some("#W9".to_string()),
mode: Mode::NoSpace,
length: None,
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("xx.ableton@domain.info https://www.ableton.com".to_string())
})
);
assert_eq!(
command_parser::name("#W9 ableton89 UN 99 2020-12-09 xx.ableton@domain.info https://www.ableton.com"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: Some("#W9".to_string()),
mode: Mode::NoSpaceUpcase,
length: None,
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("xx.ableton@domain.info https://www.ableton.com".to_string())
})
);
assert_eq!(
command_parser::name("#W9 ableton89 20R 99 2020-12-09 a b c"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: Some("#W9".to_string()),
mode: Mode::Regular,
length: Some(20),
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("a b c".to_string())
})
);
assert_eq!(
command_parser::name("#W9 ableton89 20UR 99 2020-12-09 a b c"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: Some("#W9".to_string()),
mode: Mode::RegularUpcase,
length: Some(20),
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("a b c".to_string())
})
);
assert_eq!(
command_parser::name("#W9 ableton89 20UH 99 2020-12-09 a b c"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: Some("#W9".to_string()),
mode: Mode::HexUpcase,
length: Some(20),
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("a b c".to_string())
})
);
assert_eq!(
command_parser::name("#W9 ableton89 20UB 99 2020-12-09 a b c"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: Some("#W9".to_string()),
mode: Mode::Base64Upcase,
length: Some(20),
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("a b c".to_string())
})
);
assert_eq!(
command_parser::name("#W9 ableton89 20D 99 2020-12-09 a b c"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: Some("#W9".to_string()),
mode: Mode::Decimal,
length: Some(20),
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("a b c".to_string())
})
);
assert_eq!(
command_parser::name("ableton89 20D 98 2020-12-09 a b c"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: None,
mode: Mode::Decimal,
length: Some(20),
seq: 98,
date: Date::new(2020, 12, 09),
comment: Some("a b c".to_string())
})
);
assert_eq!(
command_parser::name("ableton89 20C 98 2020-12-09 a b c"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: None,
mode: Mode::NoSpaceCamel,
length: Some(20),
seq: 98,
date: Date::new(2020, 12, 09),
comment: Some("a b c".to_string())
})
);
assert_eq!(
command_parser::name("ableton89 20D 2020-12-09 a b c"),
Ok(Password {
name: "ableton89".to_string(),
parent: None,
prefix: None,
mode: Mode::Decimal,
length: Some(20),
seq: 99,
date: Date::new(2020, 12, 09),
comment: Some("a b c".to_string())
})
);
}
}
+282
View File
@@ -0,0 +1,282 @@
use crate::skey::SKey;
use crate::structs::Mode;
use crate::utils::date::Date;
use std::{cell::RefCell, rc::Rc};
pub type Name = String;
pub type Prefix = Option<String>;
pub type Comment = Option<String>;
pub type PasswordRef = Rc<RefCell<Password>>;
pub type Parent = Option<PasswordRef>;
pub type Length = Option<u32>;
pub type Seq = u32;
#[derive(PartialEq, Debug)]
pub struct Password {
pub parent: Parent,
pub prefix: Prefix,
pub name: Name,
pub length: Length,
pub mode: Mode,
pub seq: Seq,
pub date: Date,
pub comment: Comment,
}
impl Password {
pub fn new(
prefix: Prefix,
name: Name,
length: Length,
mode: Mode,
seq: Seq,
date: Date,
comment: Comment,
) -> Password {
Password {
prefix,
name: name,
length,
mode,
date,
comment,
parent: None,
seq,
}
}
pub fn from_password(password: &Password) -> PasswordRef {
Rc::new(RefCell::new(Self {
parent: password.parent.clone(),
prefix: password.prefix.clone(),
name: password.name.clone(),
length: password.length.clone(),
mode: password.mode.clone(),
seq: password.seq,
date: password.date.clone(),
comment: password.comment.clone(),
}))
}
pub fn encode(&self, secret: &str) -> String {
let skey = SKey::new(&self.name, self.seq, secret);
let (sep, len) = match (&self.length, &self.mode) {
(Some(n), Mode::NoSpace | Mode::NoSpaceUpcase) => ("", n),
(Some(n), Mode::Base64 | Mode::Base64Upcase | Mode::Hex | Mode::HexUpcase) => ("", n),
(Some(n), _) => ("", n),
(None, Mode::NoSpace | Mode::NoSpaceUpcase) => ("-", &0_u32),
(None, Mode::Base64 | Mode::Base64Upcase | Mode::Hex | Mode::HexUpcase | Mode::NoSpaceCamel) => {
("", &0_u32)
}
(None, _) => (" ", &0_u32),
};
let result = match self.mode {
Mode::Regular => skey.to_words().join(sep),
Mode::RegularUpcase => skey.to_words().join(sep).to_uppercase(),
Mode::NoSpace => skey.to_words().join(sep),
Mode::NoSpaceUpcase => skey.to_words().join(sep).to_uppercase(),
Mode::NoSpaceCamel => camel_case(skey.to_words()),
Mode::Hex => skey.to_hex(),
Mode::HexUpcase => skey.to_hex().to_uppercase(),
Mode::Base64 => skey.to_b64(),
Mode::Base64Upcase => skey.to_b64().to_uppercase(),
Mode::Decimal => skey.to_dec().map(|v| v.to_string()).join(sep),
};
let result = match &self.prefix {
Some(p) => (p.to_owned() + sep + &result).to_string(),
None => result,
};
if len > &0_u32 {
result.chars().take(*len as usize).collect()
} else {
result
}
}
}
impl std::string::ToString for Password {
fn to_string(&self) -> String {
let prefix = match self.prefix.as_ref() {
Some(s) => format!("{} ", s),
None => "".to_string(),
};
let length = match self.length {
Some(l) => format!("{}", l),
None => "".to_string(),
};
let comment = match self.comment.as_ref() {
Some(s) => format!(" {}", s),
None => "".to_string(),
};
let parent = match &self.parent {
Some(s) => format!(" ^{}", s.borrow().name),
None => "".to_string(),
};
format!("{:>6}{} {}{} {} {}{}{}", prefix, self.name, length, self.mode, self.seq, self.date, comment, parent)
}
}
fn camel_case(words: [&str; 6]) -> String {
let mut camel_case_string = String::new();
for word in words.iter() {
let mut chars = word.chars();
camel_case_string.push(chars.next().unwrap().to_uppercase().next().unwrap());
camel_case_string.extend(chars);
}
camel_case_string
}
pub fn fix_password_recursion(entry: Rc<RefCell<Password>>) {
let mut t1 = entry.clone();
let mut t2 = entry;
let mut t3: Option<Rc<RefCell<Password>>> = None;
loop {
t2 = match &t2.clone().borrow().parent {
Some(o) => o.clone(),
None => break,
};
if std::ptr::eq(&*t1.borrow(), &*t2.borrow()) {
t3 = Some(t2.clone());
break;
}
t1 = match &t1.clone().borrow().parent {
Some(o) => o.clone(),
None => break,
};
t2 = match &t2.clone().borrow().parent {
Some(o) => o.clone(),
None => break,
};
if std::ptr::eq(&*t1.borrow(), &*t2.borrow()) {
t3 = Some(t2.clone());
break;
}
}
match t3 {
Some(o) => o.borrow_mut().parent = None,
None => (),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exec_recursion_test() {
let p1 = Rc::new(RefCell::new(Password::new(
None,
"p1".to_string(),
None,
Mode::Regular,
99,
Date::new(2022, 12, 3),
None,
)));
p1.borrow_mut().parent = Some(p1.clone());
fix_password_recursion(p1.clone());
assert_eq!(p1.borrow().parent, None);
let p2 = Rc::new(RefCell::new(Password::new(
None,
"p2".to_string(),
None,
Mode::Regular,
99,
Date::new(2022, 12, 3),
None,
)));
p2.borrow_mut().parent = Some(p1.clone());
let p3 = Rc::new(RefCell::new(Password::new(
None,
"p3".to_string(),
None,
Mode::Regular,
99,
Date::new(2022, 12, 3),
None,
)));
p3.borrow_mut().parent = Some(p2.clone());
let p4 = Rc::new(RefCell::new(Password::new(
None,
"p4".to_string(),
None,
Mode::Regular,
99,
Date::new(2022, 12, 3),
None,
)));
p4.borrow_mut().parent = Some(p3.clone());
let p5 = Rc::new(RefCell::new(Password::new(
None,
"p5".to_string(),
None,
Mode::Regular,
99,
Date::new(2022, 12, 3),
None,
)));
p5.borrow_mut().parent = Some(p4.clone());
p1.borrow_mut().parent = Some(p3.clone());
fix_password_recursion(p5.clone());
assert_eq!(p3.borrow().parent, None);
}
#[test]
fn exec_encode_test() {
let sec = "my secret";
let dat = Date::new(2022, 12, 3);
let mut pwd = Password::new(None, "test1".to_string(), None, Mode::Regular, 99, dat, None);
assert_eq!(pwd.encode(sec), "ross beau week held yoga anti");
pwd.mode = Mode::NoSpaceCamel;
assert_eq!(pwd.encode(sec), "RossBeauWeekHeldYogaAnti");
pwd.mode = Mode::Decimal;
assert_eq!(pwd.encode(sec), "1684 680 1995 1203 2046 619");
pwd.mode = Mode::RegularUpcase;
assert_eq!(pwd.encode(sec), "ROSS BEAU WEEK HELD YOGA ANTI");
pwd.mode = Mode::Regular;
pwd.prefix = Some("#Q3a".to_string());
assert_eq!(pwd.encode(sec), "#Q3a ross beau week held yoga anti");
pwd.mode = Mode::NoSpaceCamel;
assert_eq!(pwd.encode(sec), "#Q3aRossBeauWeekHeldYogaAnti");
pwd.mode = Mode::NoSpace;
assert_eq!(pwd.encode(sec), "#Q3a-ross-beau-week-held-yoga-anti");
pwd.mode = Mode::Base64;
assert_eq!(pwd.encode(sec), "#Q3a0oqj5cs//Jo");
pwd.mode = Mode::Base64Upcase;
assert_eq!(pwd.encode(sec), "#Q3a0OQJ5CS//JO");
pwd.mode = Mode::Hex;
assert_eq!(pwd.encode(sec), "#Q3ae5a38ad29afc3fcb");
pwd.mode = Mode::HexUpcase;
assert_eq!(pwd.encode(sec), "#Q3aE5A38AD29AFC3FCB");
pwd.mode = Mode::Decimal;
assert_eq!(pwd.encode(sec), "#Q3a 1684 680 1995 1203 2046 619");
let mut pwd = Password::new(None, "test1".to_string(), Some(6), Mode::Regular, 99, dat, None);
assert_eq!(pwd.encode(sec), "rossbe");
pwd.mode = Mode::NoSpaceCamel;
assert_eq!(pwd.encode(sec), "RossBe");
pwd.mode = Mode::Decimal;
assert_eq!(pwd.encode(sec), "168468");
pwd.mode = Mode::Regular;
pwd.prefix = Some("#Q3a".to_string());
assert_eq!(pwd.encode(sec), "#Q3aro");
pwd.mode = Mode::NoSpace;
assert_eq!(pwd.encode(sec), "#Q3aro");
pwd.mode = Mode::Base64;
assert_eq!(pwd.encode(sec), "#Q3a0o");
pwd.mode = Mode::Hex;
assert_eq!(pwd.encode(sec), "#Q3ae5");
pwd.mode = Mode::Decimal;
assert_eq!(pwd.encode(sec), "#Q3a16");
pwd.length = Some(10);
assert_eq!(pwd.encode(sec), "#Q3a168468");
pwd.mode = Mode::NoSpaceCamel;
assert_eq!(pwd.encode(sec), "#Q3aRossBe");
}
}
+363
View File
@@ -0,0 +1,363 @@
use rpassword::prompt_password;
use std::{cell::RefCell, rc::Rc};
use crate::lk::LK;
use crate::parser::command_parser;
use crate::structs::{Command, LKErr, LKOut, HISTORY_FILE};
use crate::utils::editor::Editor;
#[derive(Debug)]
pub struct LKRead {
pub rl: Editor,
pub prompt: String,
pub state: Rc<RefCell<LK>>,
pub cmd: String,
pub read_password: fn(String) -> std::io::Result<String>,
}
#[derive(Debug)]
pub struct LKEval<'a> {
pub cmd: Command<'a>,
pub state: Rc<RefCell<LK>>,
pub read_password: fn(String) -> std::io::Result<String>,
}
#[derive(Debug, PartialEq)]
pub struct LKPrint {
pub out: LKOut,
pub quit: bool,
pub state: Rc<RefCell<LK>>,
}
impl LKRead {
pub fn new(rl: Editor, prompt: String, state: Rc<RefCell<LK>>) -> Self {
Self {
rl,
prompt,
state,
cmd: "".to_string(),
read_password: prompt_password,
}
}
pub fn read(&mut self) -> LKEval {
let history_file = HISTORY_FILE.to_str().unwrap();
self.rl.clear_history();
match self.rl.load_history(&history_file) {
Ok(_) => (),
Err(_) => {
self.rl.add_history_entry("ls");
()
}
}
self.cmd = match self.rl.readline(&*self.prompt) {
Ok(str) => str,
Err(LKErr::EOF) => "quit".to_string(),
Err(err) => {
return LKEval::new(
Command::Error(LKErr::ReadError(err.to_string())),
self.state.clone(),
self.read_password,
)
}
};
self.rl.add_history_entry(self.cmd.as_str());
match self.rl.save_history(&history_file) {
Ok(_) => (),
Err(_) => (),
}
match command_parser::cmd(self.cmd.as_str()) {
Ok(cmd) => LKEval::new(cmd, self.state.clone(), self.read_password),
Err(err) => LKEval::new(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(cmd: Command<'a>, state: Rc<RefCell<LK>>, read_password: fn(String) -> std::io::Result<String>) -> Self {
Self {
cmd,
state,
read_password,
}
}
pub fn eval(&self) -> LKPrint {
let out = LKOut::new();
let mut quit: bool = false;
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.borrow().name.cmp(&b.borrow().name)),
Command::Ld(filter) => self.cmd_ls(&out, filter.to_string(), |a,b| a.borrow().date.cmp(&b.borrow().date)),
Command::Add(name) => self.cmd_add(&out, &name),
Command::Leave(name) => self.cmd_leave(&out, &name),
Command::Comment(name, comment) => self.cmd_comment(&out, &name, &comment),
Command::Rm(name) => match self.get_password(name) {
Some(pwd) => {
self.state.borrow_mut().db.remove(&pwd.borrow().name);
out.o(format!("removed {}", pwd.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::Pass(name) => self.cmd_pass(&out, &name),
Command::UnPass(name) => match self.state.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::Correct(name) => self.cmd_correct(&out, name, true, None),
Command::Uncorrect(name) => self.cmd_correct(&out, name, false, None),
Command::Noop => (),
Command::Help => {
out.o("HELP".to_string());
}
Command::Mv(name, folder) => self.cmd_mv(&out, &name, &folder),
Command::Error(error) => 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())),
},
}
LKPrint::new(out, quit, self.state.clone())
}
}
impl LKPrint {
pub fn new(out: LKOut, quit: bool, state: Rc<RefCell<LK>>) -> Self {
Self { out, quit, state }
}
pub fn print(&mut self) -> bool {
self.out.print_err();
self.out.print_out();
return !self.quit;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::password::Password;
use crate::structs::Mode;
use chrono::naive::NaiveDate;
use std::collections::HashMap;
impl<'a> LKEval<'a> {
pub fn news(cmd: Command<'a>, state: Rc<RefCell<LK>>) -> Self {
Self {
cmd,
state,
read_password: |_| {
Err(std::io::Error::new(std::io::ErrorKind::NotConnected, "could not read password"))
},
}
}
}
#[test]
fn exec_cmds_basic() {
let lk = Rc::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 = Rc::new(RefCell::new(Password {
name: "t1".to_string(),
prefix: None,
length: None,
mode: Mode::Regular,
seq: 99,
date: NaiveDate::from_ymd_opt(2022, 12, 30).unwrap(),
comment: Some("comment".to_string()),
parent: None,
}));
assert_eq!(LKEval::news(Command::Add(pwd1.clone()), lk.clone()).eval().state.borrow().db, {
let mut db = HashMap::new();
db.insert(pwd1.borrow().name.to_string(), pwd1.clone());
db
});
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 = Rc::new(RefCell::new(Password {
name: "t2".to_string(),
prefix: None,
length: None,
mode: Mode::Regular,
seq: 99,
date: NaiveDate::from_ymd_opt(2022, 12, 31).unwrap(),
comment: Some("bli blup".to_string()),
parent: None,
}));
assert_eq!(LKEval::news(Command::Add(pwd2.clone()), lk.clone()).eval().state.borrow().db, {
let mut db = HashMap::new();
db.insert(pwd1.borrow().name.to_string(), pwd1.clone());
db.insert(pwd2.borrow().name.to_string(), pwd2.clone());
db
});
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 = Rc::new(RefCell::new(LK::new()));
let t1 = Rc::new(RefCell::new(Password::new(
None,
"t1".to_string(),
None,
Mode::Regular,
99,
NaiveDate::from_ymd_opt(2022, 12, 30).unwrap(),
None,
)));
let t2 = Rc::new(RefCell::new(Password::new(
None,
"t2".to_string(),
None,
Mode::Regular,
99,
NaiveDate::from_ymd_opt(2022, 12, 30).unwrap(),
None,
)));
let t3 = Rc::new(RefCell::new(Password::new(
None,
"t3".to_string(),
None,
Mode::Regular,
99,
NaiveDate::from_ymd_opt(2022, 12, 30).unwrap(),
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::new(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::new(Command::Enc("t3".to_string()), lk.clone(), |p| if p == "Master: " {
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::new(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::new(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()
)
);
}
}
+272
View File
@@ -0,0 +1,272 @@
use base64;
use sha1::{Digest, Sha1};
use std::fmt::Write;
use std::vec::Vec;
type SKeyOTP = Vec<Vec<u8>>;
pub struct SKey {
otp: SKeyOTP,
}
impl SKey {
pub fn new(password: &str, seq: u32, secret: &str) -> Self {
Self {
otp: Self::otp_sha1(password, seq, secret),
}
}
fn sha1(otp: &SKeyOTP) -> SKeyOTP {
let mut hasher = Sha1::new();
for elem in otp {
hasher.update(elem);
}
let result = hasher.finalize();
let mut ints = [0; 5];
for (i, chunk) in result.chunks(4).enumerate() {
ints[i] = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
}
let x: u32 = ints[0] ^ ints[2] ^ ints[4];
let y: u32 = ints[1] ^ ints[3];
let z: Vec<Vec<u8>> = vec![x.to_be_bytes().into_iter().collect(), y.to_be_bytes().into_iter().collect()];
z
}
fn otp_sha1(password: &str, seq: u32, secret: &str) -> SKeyOTP {
let data = format!("{}{}", password, secret).to_owned().into_bytes();
let mut otp = vec![data];
for _ in 0..seq + 1 {
otp = Self::sha1(&otp);
}
let x = u32::from_le_bytes(otp[0].as_slice().try_into().unwrap());
let y = u32::from_le_bytes(otp[1].as_slice().try_into().unwrap());
vec![x.to_be_bytes().into_iter().collect(), y.to_be_bytes().into_iter().collect()]
}
pub fn to_dec(&self) -> [u32; 6] {
let mut h: Vec<u32> = vec![0, 0];
let mut parity = 0;
for (i, e) in self.otp.iter().enumerate() {
h[i] = u32::from_be_bytes(e.as_slice().try_into().unwrap());
for j in (0..=31).step_by(2) {
parity += (h[i] >> j) & 0x3;
}
}
[
(h[0] & 0xff) << 3 | (h[0] >> 13) & 0x7,
((h[0] >> 8) & 0x1f) << 6 | (h[0] >> 18) & 0x3f,
((h[0] >> 16) & 0x3) << 9 | ((h[0] >> 24) & 0xff) << 1 | (h[1] >> 7) & 0x1,
(h[1] & 0x7f) << 4 | (h[1] >> 12) & 0xf,
((h[1] >> 8) & 0xf) << 7 | (h[1] >> 17) & 0x7f,
((h[1] >> 16) & 0x1) << 10 | ((h[1] >> 24) & 0xff) << 2 | (parity & 0x03),
]
}
pub fn to_words(&self) -> [&str; 6] {
self.to_dec().map(|x| WORDS[x as usize])
}
pub fn to_hex(&self) -> String {
let mut hex_string = String::new();
for inner_vec in &self.otp {
for byte in inner_vec {
write!(hex_string, "{:0>2x}", byte).expect("Failed to write to string");
}
}
hex_string
}
pub fn to_b64(&self) -> String {
let flat_vec: Vec<u8> = self
.otp
.iter()
.map(|v| {
let mut v: Vec<u8> = v.clone();
v.reverse();
v
})
.flatten()
.collect();
base64::encode(flat_vec).trim_end_matches('=').to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encoding_test() {
assert_eq!(
SKey::sha1(&vec![vec![116, 101, 115, 116, 49, 109, 121, 32, 115, 101, 99, 114, 101, 116]]),
[[141, 231, 26, 167], [106, 250, 4, 49]]
);
assert_eq!(
SKey::sha1(&vec![vec![141, 231, 26, 167], vec![106, 250, 4, 49]]),
[[217, 8, 211, 18], [165, 49, 161, 222]]
);
let pwd = "test1";
let sec = "my secret";
assert_eq!(SKey::otp_sha1(&pwd, 99, &sec), [[229, 163, 138, 210], [154, 252, 63, 203]]);
let skey = SKey::new(&pwd, 99, &sec);
assert_eq!(skey.to_dec(), [1684, 680, 1995, 1203, 2046, 619]);
assert_eq!(skey.to_words(), ["ross", "beau", "week", "held", "yoga", "anti"]);
assert_eq!(skey.to_hex(), "e5a38ad29afc3fcb");
assert_eq!(SKey::new("test3", 99, &sec).to_hex(), "0330ce0b90086467");
assert_eq!(skey.to_b64(), "0oqj5cs//Jo");
assert_eq!(SKey::new("test3", 99, &sec).to_b64(), "C84wA2dkCJA");
assert_eq!(SKey::new("test5", 99, &sec).to_b64(), "FeXiWc7HWEY");
let skey = SKey::new(&pwd, 30, &sec);
assert_eq!(skey.to_dec(), [949, 415, 1008, 1225, 809, 165]);
let skey = SKey::new(&pwd, 300, &sec);
assert_eq!(skey.to_dec(), [1375, 1256, 2010, 333, 33, 893]);
}
}
const WORDS: [&str; 2048] = [
"a", "abe", "ace", "act", "ad", "ada", "add", "ago", "aid", "aim", "air", "all", "alp", "am", "amy", "an", "ana",
"and", "ann", "ant", "any", "ape", "aps", "apt", "arc", "are", "ark", "arm", "art", "as", "ash", "ask", "at",
"ate", "aug", "auk", "ave", "awe", "awk", "awl", "awn", "ax", "aye", "bad", "bag", "bah", "bam", "ban", "bar",
"bat", "bay", "be", "bed", "bee", "beg", "ben", "bet", "bey", "bib", "bid", "big", "bin", "bit", "bob", "bog",
"bon", "boo", "bop", "bow", "boy", "bub", "bud", "bug", "bum", "bun", "bus", "but", "buy", "by", "bye", "cab",
"cal", "cam", "can", "cap", "car", "cat", "caw", "cod", "cog", "col", "con", "coo", "cop", "cot", "cow", "coy",
"cry", "cub", "cue", "cup", "cur", "cut", "dab", "dad", "dam", "dan", "dar", "day", "dee", "del", "den", "des",
"dew", "did", "die", "dig", "din", "dip", "do", "doe", "dog", "don", "dot", "dow", "dry", "dub", "dud", "due",
"dug", "dun", "ear", "eat", "ed", "eel", "egg", "ego", "eli", "elk", "elm", "ely", "em", "end", "est", "etc",
"eva", "eve", "ewe", "eye", "fad", "fan", "far", "fat", "fay", "fed", "fee", "few", "fib", "fig", "fin", "fir",
"fit", "flo", "fly", "foe", "fog", "for", "fry", "fum", "fun", "fur", "gab", "gad", "gag", "gal", "gam", "gap",
"gas", "gay", "gee", "gel", "gem", "get", "gig", "gil", "gin", "go", "got", "gum", "gun", "gus", "gut", "guy",
"gym", "gyp", "ha", "had", "hal", "ham", "han", "hap", "has", "hat", "haw", "hay", "he", "hem", "hen", "her",
"hew", "hey", "hi", "hid", "him", "hip", "his", "hit", "ho", "hob", "hoc", "hoe", "hog", "hop", "hot", "how",
"hub", "hue", "hug", "huh", "hum", "hut", "i", "icy", "ida", "if", "ike", "ill", "ink", "inn", "io", "ion", "iq",
"ira", "ire", "irk", "is", "it", "its", "ivy", "jab", "jag", "jam", "jan", "jar", "jaw", "jay", "jet", "jig",
"jim", "jo", "job", "joe", "jog", "jot", "joy", "jug", "jut", "kay", "keg", "ken", "key", "kid", "kim", "kin",
"kit", "la", "lab", "lac", "lad", "lag", "lam", "lap", "law", "lay", "lea", "led", "lee", "leg", "len", "leo",
"let", "lew", "lid", "lie", "lin", "lip", "lit", "lo", "lob", "log", "lop", "los", "lot", "lou", "low", "loy",
"lug", "lye", "ma", "mac", "mad", "mae", "man", "mao", "map", "mat", "maw", "may", "me", "meg", "mel", "men",
"met", "mew", "mid", "min", "mit", "mob", "mod", "moe", "moo", "mop", "mos", "mot", "mow", "mud", "mug", "mum",
"my", "nab", "nag", "nan", "nap", "nat", "nay", "ne", "ned", "nee", "net", "new", "nib", "nil", "nip", "nit", "no",
"nob", "nod", "non", "nor", "not", "nov", "now", "nu", "nun", "nut", "o", "oaf", "oak", "oar", "oat", "odd", "ode",
"of", "off", "oft", "oh", "oil", "ok", "old", "on", "one", "or", "orb", "ore", "orr", "os", "ott", "our", "out",
"ova", "ow", "owe", "owl", "own", "ox", "pa", "pad", "pal", "pam", "pan", "pap", "par", "pat", "paw", "pay", "pea",
"peg", "pen", "pep", "per", "pet", "pew", "phi", "pi", "pie", "pin", "pit", "ply", "po", "pod", "poe", "pop",
"pot", "pow", "pro", "pry", "pub", "pug", "pun", "pup", "put", "quo", "rag", "ram", "ran", "rap", "rat", "raw",
"ray", "reb", "red", "rep", "ret", "rib", "rid", "rig", "rim", "rio", "rip", "rob", "rod", "roe", "ron", "rot",
"row", "roy", "rub", "rue", "rug", "rum", "run", "rye", "sac", "sad", "sag", "sal", "sam", "san", "sap", "sat",
"saw", "say", "sea", "sec", "see", "sen", "set", "sew", "she", "shy", "sin", "sip", "sir", "sis", "sit", "ski",
"sky", "sly", "so", "sob", "sod", "son", "sop", "sow", "soy", "spa", "spy", "sub", "sud", "sue", "sum", "sun",
"sup", "tab", "tad", "tag", "tan", "tap", "tar", "tea", "ted", "tee", "ten", "the", "thy", "tic", "tie", "tim",
"tin", "tip", "to", "toe", "tog", "tom", "ton", "too", "top", "tow", "toy", "try", "tub", "tug", "tum", "tun",
"two", "un", "up", "us", "use", "van", "vat", "vet", "vie", "wad", "wag", "war", "was", "way", "we", "web", "wed",
"wee", "wet", "who", "why", "win", "wit", "wok", "won", "woo", "wow", "wry", "wu", "yam", "yap", "yaw", "ye",
"yea", "yes", "yet", "you", "abed", "abel", "abet", "able", "abut", "ache", "acid", "acme", "acre", "acta", "acts",
"adam", "adds", "aden", "afar", "afro", "agee", "ahem", "ahoy", "aida", "aide", "aids", "airy", "ajar", "akin",
"alan", "alec", "alga", "alia", "ally", "alma", "aloe", "also", "alto", "alum", "alva", "amen", "ames", "amid",
"ammo", "amok", "amos", "amra", "andy", "anew", "anna", "anne", "ante", "anti", "aqua", "arab", "arch", "area",
"argo", "arid", "army", "arts", "arty", "asia", "asks", "atom", "aunt", "aura", "auto", "aver", "avid", "avis",
"avon", "avow", "away", "awry", "babe", "baby", "bach", "back", "bade", "bail", "bait", "bake", "bald", "bale",
"bali", "balk", "ball", "balm", "band", "bane", "bang", "bank", "barb", "bard", "bare", "bark", "barn", "barr",
"base", "bash", "bask", "bass", "bate", "bath", "bawd", "bawl", "bead", "beak", "beam", "bean", "bear", "beat",
"beau", "beck", "beef", "been", "beer", "beet", "bela", "bell", "belt", "bend", "bent", "berg", "bern", "bert",
"bess", "best", "beta", "beth", "bhoy", "bias", "bide", "bien", "bile", "bilk", "bill", "bind", "bing", "bird",
"bite", "bits", "blab", "blat", "bled", "blew", "blob", "bloc", "blot", "blow", "blue", "blum", "blur", "boar",
"boat", "boca", "bock", "bode", "body", "bogy", "bohr", "boil", "bold", "bolo", "bolt", "bomb", "bona", "bond",
"bone", "bong", "bonn", "bony", "book", "boom", "boon", "boot", "bore", "borg", "born", "bose", "boss", "both",
"bout", "bowl", "boyd", "brad", "brae", "brag", "bran", "bray", "bred", "brew", "brig", "brim", "brow", "buck",
"budd", "buff", "bulb", "bulk", "bull", "bunk", "bunt", "buoy", "burg", "burl", "burn", "burr", "burt", "bury",
"bush", "buss", "bust", "busy", "byte", "cady", "cafe", "cage", "cain", "cake", "calf", "call", "calm", "came",
"cane", "cant", "card", "care", "carl", "carr", "cart", "case", "cash", "cask", "cast", "cave", "ceil", "cell",
"cent", "cern", "chad", "char", "chat", "chaw", "chef", "chen", "chew", "chic", "chin", "chou", "chow", "chub",
"chug", "chum", "cite", "city", "clad", "clam", "clan", "claw", "clay", "clod", "clog", "clot", "club", "clue",
"coal", "coat", "coca", "cock", "coco", "coda", "code", "cody", "coed", "coil", "coin", "coke", "cola", "cold",
"colt", "coma", "comb", "come", "cook", "cool", "coon", "coot", "cord", "core", "cork", "corn", "cost", "cove",
"cowl", "crab", "crag", "cram", "cray", "crew", "crib", "crow", "crud", "cuba", "cube", "cuff", "cull", "cult",
"cuny", "curb", "curd", "cure", "curl", "curt", "cuts", "dade", "dale", "dame", "dana", "dane", "dang", "dank",
"dare", "dark", "darn", "dart", "dash", "data", "date", "dave", "davy", "dawn", "days", "dead", "deaf", "deal",
"dean", "dear", "debt", "deck", "deed", "deem", "deer", "deft", "defy", "dell", "dent", "deny", "desk", "dial",
"dice", "died", "diet", "dime", "dine", "ding", "dint", "dire", "dirt", "disc", "dish", "disk", "dive", "dock",
"does", "dole", "doll", "dolt", "dome", "done", "doom", "door", "dora", "dose", "dote", "doug", "dour", "dove",
"down", "drab", "drag", "dram", "draw", "drew", "drub", "drug", "drum", "dual", "duck", "duct", "duel", "duet",
"duke", "dull", "dumb", "dune", "dunk", "dusk", "dust", "duty", "each", "earl", "earn", "ease", "east", "easy",
"eben", "echo", "eddy", "eden", "edge", "edgy", "edit", "edna", "egan", "elan", "elba", "ella", "else", "emil",
"emit", "emma", "ends", "eric", "eros", "even", "ever", "evil", "eyed", "face", "fact", "fade", "fail", "fain",
"fair", "fake", "fall", "fame", "fang", "farm", "fast", "fate", "fawn", "fear", "feat", "feed", "feel", "feet",
"fell", "felt", "fend", "fern", "fest", "feud", "fief", "figs", "file", "fill", "film", "find", "fine", "fink",
"fire", "firm", "fish", "fisk", "fist", "fits", "five", "flag", "flak", "flam", "flat", "flaw", "flea", "fled",
"flew", "flit", "floc", "flog", "flow", "flub", "flue", "foal", "foam", "fogy", "foil", "fold", "folk", "fond",
"font", "food", "fool", "foot", "ford", "fore", "fork", "form", "fort", "foss", "foul", "four", "fowl", "frau",
"fray", "fred", "free", "fret", "frey", "frog", "from", "fuel", "full", "fume", "fund", "funk", "fury", "fuse",
"fuss", "gaff", "gage", "gail", "gain", "gait", "gala", "gale", "gall", "galt", "game", "gang", "garb", "gary",
"gash", "gate", "gaul", "gaur", "gave", "gawk", "gear", "geld", "gene", "gent", "germ", "gets", "gibe", "gift",
"gild", "gill", "gilt", "gina", "gird", "girl", "gist", "give", "glad", "glee", "glen", "glib", "glob", "glom",
"glow", "glue", "glum", "glut", "goad", "goal", "goat", "goer", "goes", "gold", "golf", "gone", "gong", "good",
"goof", "gore", "gory", "gosh", "gout", "gown", "grab", "grad", "gray", "greg", "grew", "grey", "grid", "grim",
"grin", "grit", "grow", "grub", "gulf", "gull", "gunk", "guru", "gush", "gust", "gwen", "gwyn", "haag", "haas",
"hack", "hail", "hair", "hale", "half", "hall", "halo", "halt", "hand", "hang", "hank", "hans", "hard", "hark",
"harm", "hart", "hash", "hast", "hate", "hath", "haul", "have", "hawk", "hays", "head", "heal", "hear", "heat",
"hebe", "heck", "heed", "heel", "heft", "held", "hell", "helm", "herb", "herd", "here", "hero", "hers", "hess",
"hewn", "hick", "hide", "high", "hike", "hill", "hilt", "hind", "hint", "hire", "hiss", "hive", "hobo", "hock",
"hoff", "hold", "hole", "holm", "holt", "home", "hone", "honk", "hood", "hoof", "hook", "hoot", "horn", "hose",
"host", "hour", "hove", "howe", "howl", "hoyt", "huck", "hued", "huff", "huge", "hugh", "hugo", "hulk", "hull",
"hunk", "hunt", "hurd", "hurl", "hurt", "hush", "hyde", "hymn", "ibis", "icon", "idea", "idle", "iffy", "inca",
"inch", "into", "ions", "iota", "iowa", "iris", "irma", "iron", "isle", "itch", "item", "ivan", "jack", "jade",
"jail", "jake", "jane", "java", "jean", "jeff", "jerk", "jess", "jest", "jibe", "jill", "jilt", "jive", "joan",
"jobs", "jock", "joel", "joey", "john", "join", "joke", "jolt", "jove", "judd", "jude", "judo", "judy", "juju",
"juke", "july", "june", "junk", "juno", "jury", "just", "jute", "kahn", "kale", "kane", "kant", "karl", "kate",
"keel", "keen", "keno", "kent", "kern", "kerr", "keys", "kick", "kill", "kind", "king", "kirk", "kiss", "kite",
"klan", "knee", "knew", "knit", "knob", "knot", "know", "koch", "kong", "kudo", "kurd", "kurt", "kyle", "lace",
"lack", "lacy", "lady", "laid", "lain", "lair", "lake", "lamb", "lame", "land", "lane", "lang", "lard", "lark",
"lass", "last", "late", "laud", "lava", "lawn", "laws", "lays", "lead", "leaf", "leak", "lean", "lear", "leek",
"leer", "left", "lend", "lens", "lent", "leon", "lesk", "less", "lest", "lets", "liar", "lice", "lick", "lied",
"lien", "lies", "lieu", "life", "lift", "like", "lila", "lilt", "lily", "lima", "limb", "lime", "lind", "line",
"link", "lint", "lion", "lisa", "list", "live", "load", "loaf", "loam", "loan", "lock", "loft", "loge", "lois",
"lola", "lone", "long", "look", "loon", "loot", "lord", "lore", "lose", "loss", "lost", "loud", "love", "lowe",
"luck", "lucy", "luge", "luke", "lulu", "lund", "lung", "lura", "lure", "lurk", "lush", "lust", "lyle", "lynn",
"lyon", "lyra", "mace", "made", "magi", "maid", "mail", "main", "make", "male", "mali", "mall", "malt", "mana",
"mann", "many", "marc", "mare", "mark", "mars", "mart", "mary", "mash", "mask", "mass", "mast", "mate", "math",
"maul", "mayo", "mead", "meal", "mean", "meat", "meek", "meet", "meld", "melt", "memo", "mend", "menu", "mert",
"mesh", "mess", "mice", "mike", "mild", "mile", "milk", "mill", "milt", "mimi", "mind", "mine", "mini", "mink",
"mint", "mire", "miss", "mist", "mite", "mitt", "moan", "moat", "mock", "mode", "mold", "mole", "moll", "molt",
"mona", "monk", "mont", "mood", "moon", "moor", "moot", "more", "morn", "mort", "moss", "most", "moth", "move",
"much", "muck", "mudd", "muff", "mule", "mull", "murk", "mush", "must", "mute", "mutt", "myra", "myth", "nagy",
"nail", "nair", "name", "nary", "nash", "nave", "navy", "neal", "near", "neat", "neck", "need", "neil", "nell",
"neon", "nero", "ness", "nest", "news", "newt", "nibs", "nice", "nick", "nile", "nina", "nine", "noah", "node",
"noel", "noll", "none", "nook", "noon", "norm", "nose", "note", "noun", "nova", "nude", "null", "numb", "oath",
"obey", "oboe", "odin", "ohio", "oily", "oint", "okay", "olaf", "oldy", "olga", "olin", "oman", "omen", "omit",
"once", "ones", "only", "onto", "onus", "oral", "orgy", "oslo", "otis", "otto", "ouch", "oust", "outs", "oval",
"oven", "over", "owly", "owns", "quad", "quit", "quod", "race", "rack", "racy", "raft", "rage", "raid", "rail",
"rain", "rake", "rank", "rant", "rare", "rash", "rate", "rave", "rays", "read", "real", "ream", "rear", "reck",
"reed", "reef", "reek", "reel", "reid", "rein", "rena", "rend", "rent", "rest", "rice", "rich", "rick", "ride",
"rift", "rill", "rime", "ring", "rink", "rise", "risk", "rite", "road", "roam", "roar", "robe", "rock", "rode",
"roil", "roll", "rome", "rood", "roof", "rook", "room", "root", "rosa", "rose", "ross", "rosy", "roth", "rout",
"rove", "rowe", "rows", "rube", "ruby", "rude", "rudy", "ruin", "rule", "rung", "runs", "runt", "ruse", "rush",
"rusk", "russ", "rust", "ruth", "sack", "safe", "sage", "said", "sail", "sale", "salk", "salt", "same", "sand",
"sane", "sang", "sank", "sara", "saul", "save", "says", "scan", "scar", "scat", "scot", "seal", "seam", "sear",
"seat", "seed", "seek", "seem", "seen", "sees", "self", "sell", "send", "sent", "sets", "sewn", "shag", "sham",
"shaw", "shay", "shed", "shim", "shin", "shod", "shoe", "shot", "show", "shun", "shut", "sick", "side", "sift",
"sigh", "sign", "silk", "sill", "silo", "silt", "sine", "sing", "sink", "sire", "site", "sits", "situ", "skat",
"skew", "skid", "skim", "skin", "skit", "slab", "slam", "slat", "slay", "sled", "slew", "slid", "slim", "slit",
"slob", "slog", "slot", "slow", "slug", "slum", "slur", "smog", "smug", "snag", "snob", "snow", "snub", "snug",
"soak", "soar", "sock", "soda", "sofa", "soft", "soil", "sold", "some", "song", "soon", "soot", "sore", "sort",
"soul", "sour", "sown", "stab", "stag", "stan", "star", "stay", "stem", "stew", "stir", "stow", "stub", "stun",
"such", "suds", "suit", "sulk", "sums", "sung", "sunk", "sure", "surf", "swab", "swag", "swam", "swan", "swat",
"sway", "swim", "swum", "tack", "tact", "tail", "take", "tale", "talk", "tall", "tank", "task", "tate", "taut",
"teal", "team", "tear", "tech", "teem", "teen", "teet", "tell", "tend", "tent", "term", "tern", "tess", "test",
"than", "that", "thee", "them", "then", "they", "thin", "this", "thud", "thug", "tick", "tide", "tidy", "tied",
"tier", "tile", "till", "tilt", "time", "tina", "tine", "tint", "tiny", "tire", "toad", "togo", "toil", "told",
"toll", "tone", "tong", "tony", "took", "tool", "toot", "tore", "torn", "tote", "tour", "tout", "town", "trag",
"tram", "tray", "tree", "trek", "trig", "trim", "trio", "trod", "trot", "troy", "true", "tuba", "tube", "tuck",
"tuft", "tuna", "tune", "tung", "turf", "turn", "tusk", "twig", "twin", "twit", "ulan", "unit", "urge", "used",
"user", "uses", "utah", "vail", "vain", "vale", "vary", "vase", "vast", "veal", "veda", "veil", "vein", "vend",
"vent", "verb", "very", "veto", "vice", "view", "vine", "vise", "void", "volt", "vote", "wack", "wade", "wage",
"wail", "wait", "wake", "wale", "walk", "wall", "walt", "wand", "wane", "wang", "want", "ward", "warm", "warn",
"wart", "wash", "wast", "wats", "watt", "wave", "wavy", "ways", "weak", "weal", "wean", "wear", "weed", "week",
"weir", "weld", "well", "welt", "went", "were", "wert", "west", "wham", "what", "whee", "when", "whet", "whoa",
"whom", "wick", "wife", "wild", "will", "wind", "wine", "wing", "wink", "wino", "wire", "wise", "wish", "with",
"wolf", "wont", "wood", "wool", "word", "wore", "work", "worm", "worn", "wove", "writ", "wynn", "yale", "yang",
"yank", "yard", "yarn", "yawl", "yawn", "yeah", "year", "yell", "yoga", "yoke",
];
+439
View File
@@ -0,0 +1,439 @@
use crate::password::{Comment, Name, PasswordRef};
use rpassword::prompt_password;
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};
use crate::utils::home;
use crate::utils::editor::Editor;
lazy_static! {
pub static ref HISTORY_FILE: Box<Path> = {
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().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<Path> = {
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().join(".helrc").into_boxed_path(),
}
};
pub static ref CORRECT_FILE: Box<Path> = {
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().join(".hel_correct").into_boxed_path(),
}
};
pub static ref DUMP_FILE: Box<Path> = {
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().join(".hel_dump").into_boxed_path(),
}
};
}
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum LKErr<'a> {
#[error("Error: {0}")]
Error(&'a str),
#[error("Error: end of file")]
EOF,
#[error("Failed to read the line: {0}")]
ReadError(String),
#[error("Failed to parse: {0}")]
ParseError(peg::error::ParseError<peg::str::LineCol>),
}
#[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<String>),
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<Rc<RefCell<Vec<String>>>>,
pub err: Option<Rc<RefCell<Vec<String>>>>,
}
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<Rc<RefCell<Vec<String>>>>, err: Option<Rc<RefCell<Vec<String>>>>) -> 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<String>, err: Vec<String>) -> 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<Self, &'static str> {
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<LKRead> {
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(), 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");
}
}
+179
View File
@@ -0,0 +1,179 @@
use shlex::split;
use std::env;
use std::ffi::OsString;
use std::io;
use std::io::{Read, Write};
use std::process::{Command, Stdio};
#[cfg(not(wasm))]
pub mod date {
use chrono::naive::NaiveDate;
use chrono::Local;
#[derive(PartialEq, Debug, Clone)]
pub struct Date {
date: NaiveDate
}
impl Date {
pub fn new(year: i32, month: u32, day: u32) -> Self {
Self { date: NaiveDate::from_ymd_opt(year, month, day).unwrap() }
}
pub fn try_new(year: i32, month: u32, day: u32) -> Result<Self, &'static str> {
match NaiveDate::from_ymd_opt(year, month, day) {
Some(d) => Ok(Self { date: d }),
None => Err("error: failed to parse the date"),
}
}
pub fn now() -> Self {
Self { date: Local::now().naive_local().date() }
}
pub fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.date.cmp(&other.date)
}
}
impl std::fmt::Display for Date {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.date.to_string())
}
}
}
#[cfg(not(wasm))]
pub mod rnd {
use rand::{thread_rng, Rng};
pub fn range(start: u32, end: u32) -> u32 {
thread_rng().gen_range(start..end)
}
}
#[cfg(unix)]
pub mod home {
use home::home_dir;
use std::path::PathBuf;
pub fn dir() -> PathBuf {
home_dir().unwrap()
}
}
#[cfg(unix)]
pub mod editor {
use crate::structs::LKErr;
use rustyline::error::ReadlineError;
#[derive(Debug)]
pub struct Editor {
editor: rustyline::Editor<()>,
}
impl Editor {
pub fn new() -> Self {
Self { editor: rustyline::Editor::<()>::new().unwrap() }
}
pub fn clear_history(&mut self) {
self.editor.clear_history();
}
pub fn add_history_entry(&mut self, entry: &str) {
self.editor.add_history_entry(entry);
}
pub fn load_history<'a>(&mut self, fname: &str) -> Result<(), LKErr<'a>> {
match self.editor.load_history(&fname) {
Ok(_) => Ok(()),
Err(_) => Err(LKErr::Error("failed to read history file")),
}
}
pub fn save_history<'a>(&mut self, fname: &str) -> Result<(), LKErr<'a>> {
match self.editor.save_history(&fname) {
Ok(_) => Ok(()),
Err(ReadlineError::Eof | ReadlineError::Interrupted) => Err(LKErr::EOF),
Err(_) => Err(LKErr::Error("failed to write history file")),
}
}
pub fn readline<'a>(&mut self, prompt: &str) -> Result<String, LKErr<'a>> {
match self.editor.readline(&prompt) {
Ok(line) => Ok(line),
Err(_) => Err(LKErr::Error("failed to read from input")),
}
}
}
}
pub fn call_cmd_with_input(cmd: &str, args: &Vec<String>, input: &str) -> io::Result<String> {
let mut cmd = Command::new(cmd).args(args).stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
let mut stdin = cmd.stdin.take().unwrap();
let stdout = cmd.stdout.as_mut().unwrap();
let in_data = input.to_string();
let write_handle = std::thread::spawn(move || stdin.write_all(in_data.as_bytes()));
let mut output = Vec::new();
stdout.read_to_end(&mut output)?;
match write_handle.join() {
Ok(_) => (),
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "Failed to run command")),
}
match String::from_utf8(output) {
Ok(x) => Ok(x),
Err(err) => Err(io::Error::new(io::ErrorKind::InvalidData, err.utf8_error())),
}
}
pub fn get_cmd_args_from_command(command: &str) -> io::Result<(String, Vec<String>)> {
let args = match split(command) {
Some(c) => c,
None => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Failed to parse the command: {:?}", command),
))
}
};
Ok((shellexpand::full(&args[0]).unwrap().into_owned(), args[1..].to_vec()))
}
pub fn get_copy_command_from_env() -> (String, Vec<String>) {
let cmd_os_str = env::var_os("HEL_PB").unwrap_or_else(|| match env::consts::OS {
_ if env::var("TMUX").is_ok() => OsString::from("tmux load-buffer -"),
"macos" => OsString::from("pbcopy"),
"linux" => OsString::from("xclip"),
_ => OsString::from("cat"),
});
get_cmd_args_from_command(&cmd_os_str.to_string_lossy()).unwrap_or_else(|_| ("cat".to_string(), vec![]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cmd_exec_test() {
assert_eq!(call_cmd_with_input("true", &vec![], "").unwrap(), "".to_string());
assert_eq!(call_cmd_with_input("cat", &vec![], "ok").unwrap(), "ok".to_string());
assert_eq!(
call_cmd_with_input(
"cat",
&vec![],
r###"line 1
line 2
line 3
line 4"###
)
.unwrap(),
"line 1\nline 2\nline 3\nline 4".to_string()
);
assert_ne!(call_cmd_with_input("cat", &vec![], "notok").unwrap(), "ok".to_string());
assert_eq!(
call_cmd_with_input("echo", &vec!["-n".to_string(), "test is ok".to_string()], "").unwrap(),
"test is ok".to_string()
);
}
}