diff --git a/src/crypt.rs b/src/crypt.rs index 1d40ae7..bf42c58 100644 --- a/src/crypt.rs +++ b/src/crypt.rs @@ -165,7 +165,7 @@ pub fn encrypt_value(value: &str, master_key: [u8; 32]) -> EncryptedValue { hmac.input(&output[..]); EncryptedValue { ciphertext: output, - iv: iv, + iv, mac: hmac.result(), } } diff --git a/src/db.rs b/src/db.rs index 72e0e61..73d6dc6 100644 --- a/src/db.rs +++ b/src/db.rs @@ -4,9 +4,10 @@ use rusqlite::{params, Connection}; use rustc_serialize::base64; use rustc_serialize::base64::{FromBase64, ToBase64}; use std::error::Error; +use std::fmt; use std::path::PathBuf; -static DB_INIT_QUERY: &'static str = include_str!("init.sql"); +static DB_INIT_QUERY: &str = include_str!("init.sql"); pub struct DbHandle { conn: Connection, @@ -23,6 +24,11 @@ pub struct DbNamespace { pub name: EncryptedValue, } +pub struct Namespace { + pub id: i64, + pub name: String, +} + pub struct DbNote { pub id: i64, pub namespace: DbNamespace, @@ -44,13 +50,13 @@ impl DbHandle { pub fn new(db_path: &Option) -> DbHandle { let path: PathBuf = db_path .as_ref() - .map(|path: &String| PathBuf::from(path)) + .map(PathBuf::from) .unwrap_or_else(|| dirs::home_dir().unwrap().join(".foil").to_path_buf()); let mut conn: Connection = Connection::open(path).unwrap(); let tx = conn.transaction().unwrap(); tx.execute_batch(DB_INIT_QUERY).unwrap(); tx.commit().unwrap(); - DbHandle { conn: conn } + DbHandle { conn } } pub fn get_namespace_id( @@ -87,7 +93,7 @@ impl DbHandle { ) .unwrap(); let rowid: i64 = tx.last_insert_rowid(); - let _ = tx.commit().unwrap(); + tx.commit().unwrap(); Ok(rowid) } @@ -119,7 +125,7 @@ impl DbHandle { ) .unwrap(); - let _ = tx.commit().unwrap(); + tx.commit().unwrap(); } pub fn read_notes(&mut self, master_key: [u8; 32]) -> rusqlite::Result> { @@ -146,13 +152,37 @@ impl DbHandle { title: note.title.decrypt_to_string(master_key).unwrap(), value: note.value.decrypt_to_string(master_key).unwrap(), }), - Err(e) => return Err(e), + Err(e) => Err(e), }) .collect(); notes } + pub fn list_namespaces(&mut self, master_key: [u8; 32]) -> rusqlite::Result> { + let mut stmt = self + .conn + .prepare("SELECT id, name FROM namespaces") + .unwrap(); + let rows = stmt.query_map(params![], |row| { + Ok(DbNamespace { + id: row.get(0)?, + name: row.get(1)?, + }) + })?; + + let namespaces: Result, _> = rows + .map(|namespace_result| match namespace_result { + Ok(namespace) => Ok(Namespace { + id: namespace.id, + name: namespace.name.decrypt_to_string(master_key).unwrap(), + }), + Err(e) => Err(e), + }) + .collect(); + namespaces + } + pub fn get_db_property(&self, name: &str) -> Result, Box> { let mut stmt = self .conn @@ -185,8 +215,14 @@ impl DbHandle { .map(|option| option.map(|prop| prop.from_base64().unwrap())) } - pub fn set_db_property_bytes(&self, name: &str, value: &Vec) { + pub fn set_db_property_bytes(&self, name: &str, value: &[u8]) { let b64value: String = value.to_base64(base64::STANDARD); self.set_db_property(name, &b64value); } } + +impl fmt::Display for Namespace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} diff --git a/src/generate.rs b/src/generate.rs index 2c27c11..4b2110d 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -5,7 +5,7 @@ use std::iter::FromIterator; pub fn generate(spec: &str) { let (len, rest) = { - let mut separated = spec.splitn(2, ","); + let mut separated = spec.splitn(2, ','); let len = separated.next().unwrap(); let rest = separated.next().unwrap(); (len, rest) diff --git a/src/main.rs b/src/main.rs index 1e2be17..dd4540f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![warn(clippy::all)] + use crypto::hmac::Hmac; use crypto::mac::{Mac, MacResult}; use crypto::sha2::Sha256; @@ -14,7 +16,7 @@ pub mod crypt; pub mod db; pub mod generate; -static USAGE: &'static str = " +static USAGE: &str = " foil Usage: @@ -168,21 +170,58 @@ fn shell_generate() { generate::generate(&spec); } -fn shell(db_conn: &mut db::DbHandle, master_key: [u8; 32], namespace: &str) { +fn change_namespace(db_conn: &mut db::DbHandle, master_key: [u8; 32]) -> String { + let namespaces = db_conn.list_namespaces(master_key).unwrap(); + let selection = Select::with_theme(&ColorfulTheme::default()) + .with_prompt("Change Namespace") + .default(0) + .items(&namespaces[..]) + .interact() + .unwrap(); + namespaces[selection].name.to_owned() +} + +fn create_namespace(db_conn: &mut db::DbHandle, master_key: [u8; 32]) -> String { + let namespace: String = Input::with_theme(&ColorfulTheme::default()) + .with_prompt("Namespace") + .default("main".to_owned()) + .interact() + .unwrap(); + namespace +} + +fn shell(db_conn: &mut db::DbHandle, master_key: [u8; 32], _namespace: &str) { + let mut namespace: String = _namespace.to_owned(); + loop { - let selections = &["get", "list", "set", "generate", "exit"]; + let selections = &[ + "get", + "list", + "set", + "generate", + "change namespace", + "create namespace", + "exit", + ]; + let main_menu_prompt = format!("Main Menu ({})", namespace); let selection = Select::with_theme(&ColorfulTheme::default()) - .with_prompt("Main Menu") + .with_prompt(&main_menu_prompt) .default(0) .items(&selections[..]) .interact() .unwrap(); match selections[selection] { - "get" => get(db_conn, master_key, namespace), - "list" => list(db_conn, master_key, namespace), - "set" => set(db_conn, master_key, namespace), + "get" => get(db_conn, master_key, &namespace), + "list" => list(db_conn, master_key, &namespace), + "set" => set(db_conn, master_key, &namespace), "generate" => shell_generate(), + "change namespace" => { + namespace = change_namespace(db_conn, master_key); + } + "create namespace" => { + namespace = create_namespace(db_conn, master_key); + } "exit" => break, _ => panic!("Unrecognized command"), };