use chrono::{NaiveDateTime, Timelike};
use std::borrow::Cow;
use std::ops::{Index, IndexMut};
use uuid::Uuid;
#[doc(hidden)]
pub fn doc_sample_db() -> Database {
let mut database = Database::default();
let mut root_entry = Entry::default();
root_entry.set_title("Foo");
root_entry.set_url("http://example.com");
root_entry.set_password("password1");
database.add_entry(root_entry);
let child_group = Group::new("Child Group");
database.add_group(child_group);
let mut child_entry = Entry::default();
child_entry.set_title("Bar");
child_entry.set_url("http://example.com");
child_entry.set_password("password2");
database
.find_group_mut(|g: &Group| g.name == "Child Group")
.unwrap()
.add_entry(child_entry);
database
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Value {
Protected(String),
Standard(String),
Empty,
ProtectEmpty,
}
impl Default for Value {
fn default() -> Value {
Value::Empty
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Field {
pub(crate) key: String,
pub(crate) value: Value,
}
impl Field {
pub fn new(key: &str, value: &str) -> Field {
Field {
key: key.to_string(),
value: Value::Standard(value.to_string()),
}
}
pub fn new_protected(key: &str, value: &str) -> Field {
Field {
key: key.to_string(),
value: Value::Protected(value.to_string()),
}
}
pub fn key(&self) -> &str {
&self.key
}
pub fn set_key(&mut self, new_key: &str) {
self.key = new_key.to_string();
}
pub fn value(&self) -> Option<&str> {
match self.value {
Value::Protected(ref s) => Some(s),
Value::Standard(ref s) => Some(s),
_ => None,
}
}
pub fn set_value(&mut self, value: &str) {
if self.protected() {
self.value = Value::Protected(value.to_string());
} else {
self.value = Value::Standard(value.to_string());
}
}
pub fn clear(&mut self) {
if self.protected() {
self.value = Value::ProtectEmpty;
} else {
self.value = Value::Empty;
}
}
pub fn protected(&self) -> bool {
matches!(self.value, Value::Protected(_))
}
pub fn set_protected(&mut self, protected: bool) {
let existing_value = std::mem::take(&mut self.value);
self.value = match (protected, existing_value) {
(true, Value::Standard(s)) => Value::Protected(s),
(false, Value::Protected(s)) => Value::Standard(s),
(true, Value::Empty) => Value::ProtectEmpty,
(false, Value::ProtectEmpty) => Value::Empty,
(_, v) => v,
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct History {
entries: Vec<Entry>,
}
impl History {
pub fn get(&self, index: usize) -> Option<&Entry> {
self.entries.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut Entry> {
self.entries.get_mut(index)
}
pub fn push(&mut self, entry: Entry) {
self.entries.push(entry);
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn remove(&mut self, idx: usize) -> Entry {
self.entries.remove(idx)
}
pub fn entries(&self) -> impl Iterator<Item = &Entry> {
self.entries.iter()
}
pub fn entries_mut(&mut self) -> impl Iterator<Item = &mut Entry> {
self.entries.iter_mut()
}
}
impl Index<usize> for History {
type Output = Entry;
fn index(&self, index: usize) -> &Self::Output {
self.get(index).unwrap()
}
}
impl IndexMut<usize> for History {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
self.get_mut(index).unwrap()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Entry {
uuid: Uuid,
fields: Vec<Field>,
pub(crate) history: History,
pub(crate) times: Times,
}
impl Entry {
pub fn add_field(&mut self, field: Field) {
self.fields.push(field);
}
pub fn remove_field(&mut self, key: &str) {
let mut matching_field_indices: Vec<_> = self
.fields
.iter()
.enumerate()
.filter_map(|(idx, field)| if field.key == key { Some(idx) } else { None })
.collect();
matching_field_indices.sort();
matching_field_indices.reverse();
for index in matching_field_indices {
self.fields.remove(index);
}
}
pub fn new_version(&mut self) {
let mut new_entry = self.clone();
new_entry.history = History::default();
self.history.push(new_entry);
}
pub fn fields(&self) -> impl Iterator<Item = &Field> {
self.fields.iter()
}
pub fn fields_mut(&mut self) -> impl Iterator<Item = &mut Field> {
self.fields.iter_mut()
}
pub fn history(&self) -> &History {
&self.history
}
pub fn history_mut(&mut self) -> &mut History {
&mut self.history
}
pub fn find(&self, key: &str) -> Option<&Field> {
self.fields.iter().find(|i| i.key.as_str() == key)
}
pub fn find_mut(&mut self, key: &str) -> Option<&mut Field> {
self.fields.iter_mut().find(|i| i.key.as_str() == key)
}
pub fn times(&self) -> &Times {
&self.times
}
pub fn times_mut(&mut self) -> &mut Times {
&mut self.times
}
fn find_string_value(&self, key: &str) -> Option<&str> {
self.find(key).and_then(|f| f.value())
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn set_uuid(&mut self, uuid: Uuid) {
self.uuid = uuid;
}
pub fn title(&self) -> Option<&str> {
self.find_string_value("Title")
}
pub fn set_title<S: ToString>(&mut self, title: S) {
let title = title.to_string();
match self.find_mut("Title") {
Some(f) => f.value = Value::Standard(title),
None => self.fields.push(Field::new("Title", &title)),
}
}
pub fn username(&self) -> Option<&str> {
self.find_string_value("UserName")
}
pub fn set_username<S: ToString>(&mut self, username: S) {
let username = username.to_string();
match self.find_mut("UserName") {
Some(f) => f.value = Value::Standard(username),
None => self.fields.push(Field::new("UserName", &username)),
}
}
pub fn url(&self) -> Option<&str> {
self.find_string_value("URL")
}
pub fn set_url<S: ToString>(&mut self, url: S) {
let url = url.to_string();
match self.find_mut("URL") {
Some(f) => f.value = Value::Standard(url),
None => self.fields.push(Field::new("URL", &url)),
}
}
pub fn otp(&self) -> Option<Otp> {
self.find_string_value("otp").map(|url| Otp {
url: Cow::Borrowed(url),
})
}
pub fn set_otp(&mut self, otp: Otp) {
match self.find_mut("otp") {
Some(f) => f.value = Value::Protected(otp.url.to_string()),
None => self
.fields
.push(Field::new_protected("otp", otp.url.as_ref())),
}
}
pub fn password(&self) -> Option<&str> {
self.find_string_value("Password")
}
pub fn set_password<S: ToString>(&mut self, password: S) {
let password = password.to_string();
match self.find_mut("Password") {
Some(f) => f.value = Value::Protected(password),
None => self
.fields
.push(Field::new_protected("Password", &password)),
}
}
}
impl Default for Entry {
fn default() -> Entry {
Entry {
uuid: Uuid::new_v4(),
fields: Vec::new(),
history: History::default(),
times: Times::default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Group {
uuid: Uuid,
name: String,
entries: Vec<Entry>,
groups: Vec<Group>,
pub(crate) times: Times,
}
impl Group {
pub fn new<S: ToString>(name: S) -> Group {
Group {
uuid: Uuid::new_v4(),
name: name.to_string(),
entries: Vec::new(),
groups: Vec::new(),
times: Times::default(),
}
}
pub fn uuid(&self) -> Uuid {
self.uuid
}
pub fn set_uuid(&mut self, uuid: Uuid) {
self.uuid = uuid
}
pub fn name(&self) -> &str {
&self.name
}
pub fn set_name<S: ToString>(&mut self, name: S) {
self.name = name.to_string();
}
pub fn add_entry(&mut self, entry: Entry) {
self.entries.push(entry);
}
pub fn remove_entry(&mut self, uuid: Uuid) -> Option<Entry> {
let index = self
.entries
.iter()
.enumerate()
.find(|(_, entry)| entry.uuid() == uuid)
.map(|(index, _)| index);
if let Some(index) = index {
Some(self.entries.remove(index))
} else {
None
}
}
pub fn add_group(&mut self, group: Group) {
self.groups.push(group);
}
pub fn remove_group(&mut self, uuid: Uuid) -> Option<Group> {
let index = self
.groups
.iter()
.enumerate()
.find(|(_, group)| group.uuid() == uuid)
.map(|(index, _)| index);
if let Some(index) = index {
Some(self.groups.remove(index))
} else {
None
}
}
pub fn groups(&self) -> impl Iterator<Item = &Group> {
self.groups.iter()
}
pub fn groups_mut(&mut self) -> impl Iterator<Item = &mut Group> {
self.groups.iter_mut()
}
pub fn group_count(&self) -> usize {
self.groups.len()
}
pub fn entry_count(&self) -> usize {
self.entries.len()
}
pub fn entries(&self) -> impl Iterator<Item = &Entry> {
self.entries.iter()
}
pub fn entries_mut(&mut self) -> impl Iterator<Item = &mut Entry> {
self.entries.iter_mut()
}
pub fn recursive_entries<'a>(&'a self) -> Box<dyn Iterator<Item = &Entry> + 'a> {
Box::new(
self.groups
.iter()
.flat_map(|c| c.recursive_entries())
.chain(self.entries.iter()),
)
}
pub fn recursive_entries_mut<'a>(&'a mut self) -> Box<dyn Iterator<Item = &mut Entry> + 'a> {
Box::new(
self.groups
.iter_mut()
.flat_map(|c| c.recursive_entries_mut())
.chain(self.entries.iter_mut()),
)
}
pub fn recursive_groups<'a>(&'a self) -> Box<dyn Iterator<Item = &Group> + 'a> {
Box::new(
self.groups
.iter()
.flat_map(|g| g.recursive_groups())
.chain(self.groups.iter()),
)
}
pub fn find_group<F: FnMut(&Group) -> bool>(&self, mut f: F) -> Option<&Group> {
self.find_group_internal(&mut f)
}
fn find_group_internal<F: FnMut(&Group) -> bool>(&self, f: &mut F) -> Option<&Group> {
for group in self.groups() {
if f(group) {
return Some(group);
} else if let Some(g) = group.find_group_internal(f) {
return Some(g);
}
}
None
}
pub fn find_group_mut<F: FnMut(&Group) -> bool>(&mut self, mut f: F) -> Option<&mut Group> {
self.find_group_mut_internal(&mut f)
}
fn find_group_mut_internal<F: FnMut(&Group) -> bool>(
&mut self,
f: &mut F,
) -> Option<&mut Group> {
for group in self.groups_mut() {
if f(group) {
return Some(group);
} else if let Some(g) = group.find_group_mut_internal(f) {
return Some(g);
}
}
None
}
pub fn find_entry<F: FnMut(&Entry) -> bool>(&self, mut f: F) -> Option<&Entry> {
self.find_entry_internal(&mut f)
}
fn find_entry_internal<F: FnMut(&Entry) -> bool>(&self, f: &mut F) -> Option<&Entry> {
for entry in self.entries() {
if f(entry) {
return Some(entry);
}
}
for group in self.groups() {
if let Some(e) = group.find_entry_internal(f) {
return Some(e);
}
}
None
}
pub fn find_entry_mut<F: FnMut(&Entry) -> bool>(&mut self, mut f: F) -> Option<&mut Entry> {
self.find_entry_mut_internal(&mut f)
}
fn find_entry_mut_internal<F: FnMut(&Entry) -> bool>(
&mut self,
f: &mut F,
) -> Option<&mut Entry> {
let found_in_entries = self
.entries()
.enumerate()
.find(|(_, e)| f(e))
.map(|(idx, _)| idx);
if let Some(idx) = found_in_entries {
return Some(&mut self.entries[idx]);
} else {
for group in self.groups_mut() {
if let Some(e) = group.find_entry_mut_internal(f) {
return Some(e);
}
}
}
None
}
pub fn times(&self) -> &Times {
&self.times
}
pub fn times_mut(&mut self) -> &mut Times {
&mut self.times
}
}
impl Default for Group {
fn default() -> Group {
Group {
uuid: Uuid::new_v4(),
name: String::new(),
entries: Vec::new(),
groups: Vec::new(),
times: Times::default(),
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct MemoryProtection {
pub protect_title: bool,
pub protect_user_name: bool,
pub protect_password: bool,
pub protect_url: bool,
pub protect_notes: bool,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Meta {
pub generator: String,
pub database_name: String,
pub database_description: String,
pub custom_data: Vec<Field>,
pub memory_protection: MemoryProtection,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Times {
pub last_modification_time: NaiveDateTime,
pub creation_time: NaiveDateTime,
pub last_access_time: NaiveDateTime,
pub expiry_time: NaiveDateTime,
pub location_changed: NaiveDateTime,
pub expires: bool,
pub usage_count: u32,
}
impl Default for Times {
fn default() -> Times {
let now = chrono::Local::now()
.naive_local()
.with_nanosecond(0)
.unwrap();
Times {
expires: false,
usage_count: 0,
last_modification_time: now,
creation_time: now,
last_access_time: now,
expiry_time: now,
location_changed: now,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Database {
pub(crate) meta: Meta,
pub(crate) groups: Vec<Group>,
}
impl Default for Database {
fn default() -> Self {
let root = Group::new("Root");
Database {
meta: Meta::default(),
groups: vec![root],
}
}
}
impl Database {
pub fn meta(&self) -> &Meta {
&self.meta
}
pub fn meta_mut(&mut self) -> &mut Meta {
&mut self.meta
}
pub fn name(&self) -> &str {
&self.meta.database_name
}
pub fn set_name<S: ToString>(&mut self, name: S) {
self.meta.database_name = name.to_string();
}
pub fn description(&self) -> &str {
&self.meta.database_description
}
pub fn set_description<S: ToString>(&mut self, desc: S) {
self.meta.database_description = desc.to_string();
}
pub fn add_entry(&mut self, entry: Entry) {
self.groups[0].entries.push(entry);
}
pub fn add_group(&mut self, entry: Group) {
self.groups[0].groups.push(entry);
}
pub fn replace_root(&mut self, group: Group) {
self.groups = vec![group];
}
pub fn find_group<F: FnMut(&Group) -> bool>(&self, f: F) -> Option<&Group> {
self.root().find_group(f)
}
pub fn find_group_mut<F: FnMut(&Group) -> bool>(&mut self, f: F) -> Option<&mut Group> {
self.root_mut().find_group_mut(f)
}
pub fn find_entry<F: FnMut(&Entry) -> bool>(&self, f: F) -> Option<&Entry> {
self.root().find_entry(f)
}
pub fn find_entry_mut<F: FnMut(&Entry) -> bool>(&mut self, f: F) -> Option<&mut Entry> {
self.root_mut().find_entry_mut(f)
}
pub fn root(&self) -> &Group {
&self.groups[0]
}
pub fn root_mut(&mut self) -> &mut Group {
&mut self.groups[0]
}
}
pub struct Otp<'a> {
url: Cow<'a, str>,
}
impl<'a> Otp<'a> {
pub fn new<S: ToString>(secret: S, period: u32, digits: u32) -> Otp<'static> {
let url = format!(
"otpauth://totp/kdbxrs:kdbxrs?secret={}&period={}&digits={}",
secret.to_string(),
period,
digits
);
Otp {
url: Cow::Owned(url),
}
}
fn find_url_param(&self, key: &str) -> Option<&str> {
let mut parts = self.url.split('?');
let _path = parts.next()?;
let params = parts.next()?;
let params = params.split('&');
for param in params {
let mut param_parts = param.split('=');
let pkey = param_parts.next()?;
if pkey == key {
return param_parts.next();
}
}
None
}
pub fn secret(&self) -> Option<&str> {
self.find_url_param("secret")
}
pub fn period(&self) -> Option<u32> {
self.find_url_param("secret").and_then(|p| p.parse().ok())
}
pub fn digits(&self) -> Option<u32> {
self.find_url_param("digits").and_then(|p| p.parse().ok())
}
}