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:
@@ -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"
|
||||
@@ -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(©_command, ©_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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
];
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user