Skip to main content

kdbx_rs/
database.rs

1//! Keepass data types
2//!
3//! A database is made up of two primary parts, a set of meta
4//! information about the database itself, like the name or description
5//! and a tree structure of groups and database entries. Groups can also
6//! be nested within other groups.
7//!
8//! ## Meta information
9//!
10//! You can access the entire [`Meta`] struct using [`Database::meta()`].
11//! For the most common information the following shortcut methods are provided:
12//!
13//! * [`Database::name`] / [`Database::set_name`]
14//! * [`Database::description`] / [`Database::set_description`]
15//!
16//! ## Example operations
17//!
18//! ### Add a entry to the root group
19//!
20//! ```
21//! # use kdbx_rs::database::{Database,Entry};
22//! let mut database = Database::default();
23//! let entry = Entry::default();
24//! database.add_entry(entry);
25//! ```
26//!
27//! ### Add a child group to the root group
28//!
29//! ```
30//! # use kdbx_rs::database::{Database,Group};
31//! let mut database = Database::default();
32//! let group = Group::new("Child group");
33//! database.add_group(group);
34//! ```
35//!
36//! ### Updating a password for a given URL
37//!
38//! ```
39//! # let mut database = kdbx_rs::database::doc_sample_db();
40//! database.find_entry_mut(|f| f.url() == Some("http://example.com"))
41//!     .unwrap()
42//!     .set_password("password2")
43//! ```
44//!
45//! ### Moving an entry from one folder to another
46//!
47//! [`Group::find_entry_mut()`] gives us a reference, while moving a folder to
48//! another group requires an owned [`Entry`]. So instead we take its UUID
49//! and remove it from the source group first.
50//!
51//! ```
52//! # let mut database = kdbx_rs::database::doc_sample_db();
53//! let uuid = database.find_entry_mut(|f| f.title() == Some("Foo"))
54//!     .unwrap()
55//!     .uuid();
56//! # let mut source_group = database.root_mut();
57//! let entry = source_group.remove_entry(uuid).unwrap();
58//!
59//! let mut target_group = database.find_group_mut(|g| g.name() == "Child Group").unwrap();
60//! target_group.add_entry(entry);
61//! ```
62
63use chrono::{NaiveDateTime, Timelike};
64use std::borrow::Cow;
65use std::ops::{Index, IndexMut};
66use uuid::Uuid;
67
68#[doc(hidden)]
69pub fn doc_sample_db() -> Database {
70    let mut database = Database::default();
71
72    let mut root_entry = Entry::default();
73    root_entry.set_title("Foo");
74    root_entry.set_url("http://example.com");
75    root_entry.set_password("password1");
76
77    database.add_entry(root_entry);
78
79    let child_group = Group::new("Child Group");
80    database.add_group(child_group);
81
82    let mut child_entry = Entry::default();
83    child_entry.set_title("Bar");
84    child_entry.set_url("http://example.com");
85    child_entry.set_password("password2");
86    database
87        .find_group_mut(|g: &Group| g.name == "Child Group")
88        .unwrap()
89        .add_entry(child_entry);
90
91    database
92}
93
94/// A value for a `Field` stored in an `Entry`
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub(crate) enum Value {
97    /// A value using in-memory encryption
98    Protected(String),
99    /// A value that's unencrypted in the database
100    Standard(String),
101    /// A empty value
102    Empty,
103    /// A empty value that should be protected if filled
104    ProtectEmpty,
105}
106
107impl Default for Value {
108    fn default() -> Value {
109        Value::Empty
110    }
111}
112
113#[derive(Debug, Default, Clone, PartialEq, Eq)]
114/// A key value pair
115pub struct Field {
116    /// The name of this field
117    pub(crate) key: String,
118    /// The (optionally encrypted) value of this field
119    pub(crate) value: Value,
120}
121
122impl Field {
123    /// Create a new field without memory protection
124    pub fn new(key: &str, value: &str) -> Field {
125        Field {
126            key: key.to_string(),
127            value: Value::Standard(value.to_string()),
128        }
129    }
130
131    /// Create a new field without memory protection
132    pub fn new_protected(key: &str, value: &str) -> Field {
133        Field {
134            key: key.to_string(),
135            value: Value::Protected(value.to_string()),
136        }
137    }
138
139    /// Key for this field
140    pub fn key(&self) -> &str {
141        &self.key
142    }
143
144    /// Set a new key for this field
145    pub fn set_key(&mut self, new_key: &str) {
146        self.key = new_key.to_string();
147    }
148
149    /// Value for this field
150    pub fn value(&self) -> Option<&str> {
151        match self.value {
152            Value::Protected(ref s) => Some(s),
153            Value::Standard(ref s) => Some(s),
154            _ => None,
155        }
156    }
157
158    /// Set a new value for this field
159    pub fn set_value(&mut self, value: &str) {
160        if self.protected() {
161            self.value = Value::Protected(value.to_string());
162        } else {
163            self.value = Value::Standard(value.to_string());
164        }
165    }
166
167    /// Empty out the field stored in this value
168    pub fn clear(&mut self) {
169        if self.protected() {
170            self.value = Value::ProtectEmpty;
171        } else {
172            self.value = Value::Empty;
173        }
174    }
175
176    /// Get whether memory protection and extra encryption should be applied
177    ///
178    /// Note: This is instructional for official clients, this library does not
179    /// support memory protection
180    pub fn protected(&self) -> bool {
181        matches!(self.value, Value::Protected(_))
182    }
183
184    /// Set whether memory protection and extra encryption should be applied
185    ///
186    /// Note: This is instructional for official clients, this library does not
187    /// support memory protection
188    pub fn set_protected(&mut self, protected: bool) {
189        let existing_value = std::mem::take(&mut self.value);
190        self.value = match (protected, existing_value) {
191            (true, Value::Standard(s)) => Value::Protected(s),
192            (false, Value::Protected(s)) => Value::Standard(s),
193            (true, Value::Empty) => Value::ProtectEmpty,
194            (false, Value::ProtectEmpty) => Value::Empty,
195            (_, v) => v,
196        }
197    }
198}
199
200/// Historical versions of a single entry
201#[derive(Default, Debug, Clone, PartialEq, Eq)]
202pub struct History {
203    entries: Vec<Entry>,
204}
205
206impl History {
207    /// Get a history entry by its index
208    pub fn get(&self, index: usize) -> Option<&Entry> {
209        self.entries.get(index)
210    }
211
212    /// Get a history entry mutably by its index
213    pub fn get_mut(&mut self, index: usize) -> Option<&mut Entry> {
214        self.entries.get_mut(index)
215    }
216
217    /// Add a new version of an entry to the history
218    pub fn push(&mut self, entry: Entry) {
219        self.entries.push(entry);
220    }
221
222    /// Count of entries in this history
223    pub fn len(&self) -> usize {
224        self.entries.len()
225    }
226
227    /// Count of entries in this history
228    pub fn is_empty(&self) -> bool {
229        self.len() == 0
230    }
231
232    /// Remove a historical version by index
233    pub fn remove(&mut self, idx: usize) -> Entry {
234        self.entries.remove(idx)
235    }
236
237    /// Iterate over all historical entries
238    pub fn entries(&self) -> impl Iterator<Item = &Entry> {
239        self.entries.iter()
240    }
241
242    /// Iterate mutably over all historical entries
243    pub fn entries_mut(&mut self) -> impl Iterator<Item = &mut Entry> {
244        self.entries.iter_mut()
245    }
246}
247
248impl Index<usize> for History {
249    type Output = Entry;
250    fn index(&self, index: usize) -> &Self::Output {
251        self.get(index).unwrap()
252    }
253}
254
255impl IndexMut<usize> for History {
256    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
257        self.get_mut(index).unwrap()
258    }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
262/// A single password entry
263pub struct Entry {
264    /// Identifier for this entry
265    uuid: Uuid,
266    /// Key-value pairs of current data for this entry
267    fields: Vec<Field>,
268    /// Previous versions of this entry
269    pub(crate) history: History,
270    /// Information about access times
271    pub(crate) times: Times,
272}
273
274impl Entry {
275    /// Add a new field to the entry
276    pub fn add_field(&mut self, field: Field) {
277        self.fields.push(field);
278    }
279
280    /// Remove a field by its key
281    ///
282    /// If there are duplicate fields, removes them all
283    pub fn remove_field(&mut self, key: &str) {
284        let mut matching_field_indices: Vec<_> = self
285            .fields
286            .iter()
287            .enumerate()
288            .filter_map(|(idx, field)| if field.key == key { Some(idx) } else { None })
289            .collect();
290        matching_field_indices.sort();
291        matching_field_indices.reverse();
292        for index in matching_field_indices {
293            self.fields.remove(index);
294        }
295    }
296
297    /// Generate a new version of this entry, pushing the current state to history
298    pub fn new_version(&mut self) {
299        let mut new_entry = self.clone();
300        new_entry.history = History::default();
301        self.history.push(new_entry);
302    }
303
304    /// Iterate through all the fields
305    pub fn fields(&self) -> impl Iterator<Item = &Field> {
306        self.fields.iter()
307    }
308
309    /// Iterate through all the field mutably
310    pub fn fields_mut(&mut self) -> impl Iterator<Item = &mut Field> {
311        self.fields.iter_mut()
312    }
313
314    /// Iterate through all the fields
315    pub fn history(&self) -> &History {
316        &self.history
317    }
318
319    /// Iterate through all the field mutably
320    pub fn history_mut(&mut self) -> &mut History {
321        &mut self.history
322    }
323
324    /// Find a field in this entry with a given key
325    pub fn find(&self, key: &str) -> Option<&Field> {
326        self.fields.iter().find(|i| i.key.as_str() == key)
327    }
328
329    /// Find a field in this entry with a given key
330    pub fn find_mut(&mut self, key: &str) -> Option<&mut Field> {
331        self.fields.iter_mut().find(|i| i.key.as_str() == key)
332    }
333
334    /// Audit times for this entry
335    pub fn times(&self) -> &Times {
336        &self.times
337    }
338
339    /// Mutable audit times for this entry
340    pub fn times_mut(&mut self) -> &mut Times {
341        &mut self.times
342    }
343
344    fn find_string_value(&self, key: &str) -> Option<&str> {
345        self.find(key).and_then(|f| f.value())
346    }
347
348    /// Set the identifier for this item
349    pub fn uuid(&self) -> Uuid {
350        self.uuid
351    }
352
353    /// Get the identifier for this item
354    pub fn set_uuid(&mut self, uuid: Uuid) {
355        self.uuid = uuid;
356    }
357
358    /// Return the title of this item
359    pub fn title(&self) -> Option<&str> {
360        self.find_string_value("Title")
361    }
362
363    /// Set the title of this entry
364    pub fn set_title<S: ToString>(&mut self, title: S) {
365        let title = title.to_string();
366        match self.find_mut("Title") {
367            Some(f) => f.value = Value::Standard(title),
368            None => self.fields.push(Field::new("Title", &title)),
369        }
370    }
371
372    /// Return the username of this item
373    pub fn username(&self) -> Option<&str> {
374        self.find_string_value("UserName")
375    }
376
377    /// Set the username of this entry
378    pub fn set_username<S: ToString>(&mut self, username: S) {
379        let username = username.to_string();
380        match self.find_mut("UserName") {
381            Some(f) => f.value = Value::Standard(username),
382            None => self.fields.push(Field::new("UserName", &username)),
383        }
384    }
385
386    /// Return the URL of this item
387    pub fn url(&self) -> Option<&str> {
388        self.find_string_value("URL")
389    }
390
391    /// Set the URL of this entry
392    pub fn set_url<S: ToString>(&mut self, url: S) {
393        let url = url.to_string();
394        match self.find_mut("URL") {
395            Some(f) => f.value = Value::Standard(url),
396            None => self.fields.push(Field::new("URL", &url)),
397        }
398    }
399
400    /// Return the TOTP of this item, as stored by KeepassXC
401    pub fn otp(&self) -> Option<Otp> {
402        self.find_string_value("otp").map(|url| Otp {
403            url: Cow::Borrowed(url),
404        })
405    }
406
407    /// Return the TOTP of this item, as stored by KeepassXC
408    pub fn set_otp(&mut self, otp: Otp) {
409        match self.find_mut("otp") {
410            Some(f) => f.value = Value::Protected(otp.url.to_string()),
411            None => self
412                .fields
413                .push(Field::new_protected("otp", otp.url.as_ref())),
414        }
415    }
416
417    /// Return the password of this item
418    pub fn password(&self) -> Option<&str> {
419        self.find_string_value("Password")
420    }
421
422    /// Set the password of this entry
423    pub fn set_password<S: ToString>(&mut self, password: S) {
424        let password = password.to_string();
425        match self.find_mut("Password") {
426            Some(f) => f.value = Value::Protected(password),
427            None => self
428                .fields
429                .push(Field::new_protected("Password", &password)),
430        }
431    }
432}
433
434impl Default for Entry {
435    fn default() -> Entry {
436        Entry {
437            uuid: Uuid::new_v4(),
438            fields: Vec::new(),
439            history: History::default(),
440            times: Times::default(),
441        }
442    }
443}
444
445#[derive(Debug, Clone, PartialEq, Eq)]
446/// A group or folder of password entries and child groups
447pub struct Group {
448    /// Identifier for this group
449    uuid: Uuid,
450    /// Name of this group
451    name: String,
452    /// Password items within this group
453    entries: Vec<Entry>,
454    /// Subfolders of this group
455    groups: Vec<Group>,
456    /// Access times for this group
457    pub(crate) times: Times,
458}
459
460impl Group {
461    /// Create a new group with the given name
462    pub fn new<S: ToString>(name: S) -> Group {
463        Group {
464            uuid: Uuid::new_v4(),
465            name: name.to_string(),
466            entries: Vec::new(),
467            groups: Vec::new(),
468            times: Times::default(),
469        }
470    }
471
472    /// Identifier for this group
473    pub fn uuid(&self) -> Uuid {
474        self.uuid
475    }
476
477    /// Set identifier for this group
478    pub fn set_uuid(&mut self, uuid: Uuid) {
479        self.uuid = uuid
480    }
481
482    /// Display name for this group
483    pub fn name(&self) -> &str {
484        &self.name
485    }
486
487    /// Set display name for this group
488    pub fn set_name<S: ToString>(&mut self, name: S) {
489        self.name = name.to_string();
490    }
491
492    /// Add a new entry to this group
493    pub fn add_entry(&mut self, entry: Entry) {
494        self.entries.push(entry);
495    }
496
497    /// Remove an entry by its UUID
498    ///
499    /// This is a no-op if the no direct child of this group has the
500    /// given UUID
501    pub fn remove_entry(&mut self, uuid: Uuid) -> Option<Entry> {
502        let index = self
503            .entries
504            .iter()
505            .enumerate()
506            .find(|(_, entry)| entry.uuid() == uuid)
507            .map(|(index, _)| index);
508
509        if let Some(index) = index {
510            Some(self.entries.remove(index))
511        } else {
512            None
513        }
514    }
515
516    /// Add a new child group to this group
517    pub fn add_group(&mut self, group: Group) {
518        self.groups.push(group);
519    }
520
521    /// Remove an child group by its UUID
522    ///
523    /// This is a no-op if the no direct child of this group has the
524    /// given UUID
525    pub fn remove_group(&mut self, uuid: Uuid) -> Option<Group> {
526        let index = self
527            .groups
528            .iter()
529            .enumerate()
530            .find(|(_, group)| group.uuid() == uuid)
531            .map(|(index, _)| index);
532
533        if let Some(index) = index {
534            Some(self.groups.remove(index))
535        } else {
536            None
537        }
538    }
539
540    /// Iterate through all the direct child groups of this group
541    pub fn groups(&self) -> impl Iterator<Item = &Group> {
542        self.groups.iter()
543    }
544
545    /// Iterate mutably through all the direct child groups of this group
546    pub fn groups_mut(&mut self) -> impl Iterator<Item = &mut Group> {
547        self.groups.iter_mut()
548    }
549
550    /// Count of direct child groups of this group
551    pub fn group_count(&self) -> usize {
552        self.groups.len()
553    }
554
555    /// Count of direct entries of this group
556    pub fn entry_count(&self) -> usize {
557        self.entries.len()
558    }
559
560    /// Iterate through all the direct entries of this group
561    pub fn entries(&self) -> impl Iterator<Item = &Entry> {
562        self.entries.iter()
563    }
564
565    /// Iterate mutably through all the direct entries of this group
566    pub fn entries_mut(&mut self) -> impl Iterator<Item = &mut Entry> {
567        self.entries.iter_mut()
568    }
569
570    /// Iterator through all entries in this group or children
571    pub fn recursive_entries<'a>(&'a self) -> Box<dyn Iterator<Item = &Entry> + 'a> {
572        Box::new(
573            self.groups
574                .iter()
575                .flat_map(|c| c.recursive_entries())
576                .chain(self.entries.iter()),
577        )
578    }
579
580    /// Mutable Iterator through all entries in this group or children
581    pub fn recursive_entries_mut<'a>(&'a mut self) -> Box<dyn Iterator<Item = &mut Entry> + 'a> {
582        Box::new(
583            self.groups
584                .iter_mut()
585                .flat_map(|c| c.recursive_entries_mut())
586                .chain(self.entries.iter_mut()),
587        )
588    }
589
590    /// Iterator through all child groups of this group
591    pub fn recursive_groups<'a>(&'a self) -> Box<dyn Iterator<Item = &Group> + 'a> {
592        Box::new(
593            self.groups
594                .iter()
595                .flat_map(|g| g.recursive_groups())
596                .chain(self.groups.iter()),
597        )
598    }
599
600    /// Find a group in this group's children or it's children's children
601    pub fn find_group<F: FnMut(&Group) -> bool>(&self, mut f: F) -> Option<&Group> {
602        self.find_group_internal(&mut f)
603    }
604
605    fn find_group_internal<F: FnMut(&Group) -> bool>(&self, f: &mut F) -> Option<&Group> {
606        for group in self.groups() {
607            if f(group) {
608                return Some(group);
609            } else if let Some(g) = group.find_group_internal(f) {
610                return Some(g);
611            }
612        }
613        None
614    }
615
616    /// Find a mutable group in this group's children or it's children's children
617    pub fn find_group_mut<F: FnMut(&Group) -> bool>(&mut self, mut f: F) -> Option<&mut Group> {
618        self.find_group_mut_internal(&mut f)
619    }
620
621    fn find_group_mut_internal<F: FnMut(&Group) -> bool>(
622        &mut self,
623        f: &mut F,
624    ) -> Option<&mut Group> {
625        for group in self.groups_mut() {
626            if f(group) {
627                return Some(group);
628            } else if let Some(g) = group.find_group_mut_internal(f) {
629                return Some(g);
630            }
631        }
632        None
633    }
634
635    /// Find a entry in this group's children or it's children's children
636    pub fn find_entry<F: FnMut(&Entry) -> bool>(&self, mut f: F) -> Option<&Entry> {
637        self.find_entry_internal(&mut f)
638    }
639
640    fn find_entry_internal<F: FnMut(&Entry) -> bool>(&self, f: &mut F) -> Option<&Entry> {
641        for entry in self.entries() {
642            if f(entry) {
643                return Some(entry);
644            }
645        }
646        for group in self.groups() {
647            if let Some(e) = group.find_entry_internal(f) {
648                return Some(e);
649            }
650        }
651        None
652    }
653
654    /// Find a mutable entry in this group's children or it's children's children
655    pub fn find_entry_mut<F: FnMut(&Entry) -> bool>(&mut self, mut f: F) -> Option<&mut Entry> {
656        self.find_entry_mut_internal(&mut f)
657    }
658
659    fn find_entry_mut_internal<F: FnMut(&Entry) -> bool>(
660        &mut self,
661        f: &mut F,
662    ) -> Option<&mut Entry> {
663        let found_in_entries = self
664            .entries()
665            .enumerate()
666            .find(|(_, e)| f(e))
667            .map(|(idx, _)| idx);
668
669        if let Some(idx) = found_in_entries {
670            return Some(&mut self.entries[idx]);
671        } else {
672            for group in self.groups_mut() {
673                if let Some(e) = group.find_entry_mut_internal(f) {
674                    return Some(e);
675                }
676            }
677        }
678        None
679    }
680
681    /// Audit times for this group
682    pub fn times(&self) -> &Times {
683        &self.times
684    }
685
686    /// Mutable audit times for this group
687    pub fn times_mut(&mut self) -> &mut Times {
688        &mut self.times
689    }
690}
691
692impl Default for Group {
693    fn default() -> Group {
694        Group {
695            uuid: Uuid::new_v4(),
696            name: String::new(),
697            entries: Vec::new(),
698            groups: Vec::new(),
699            times: Times::default(),
700        }
701    }
702}
703
704#[derive(Debug, Default, Clone, PartialEq, Eq)]
705/// Identifies which fields are encrypted in memory for official clients
706pub struct MemoryProtection {
707    /// Whether title fields should be encrypted
708    pub protect_title: bool,
709    /// Whether username fields should be encrypted
710    pub protect_user_name: bool,
711    /// Whether password fields should be encrypted
712    pub protect_password: bool,
713    /// Whether URL fields should be encrypted
714    pub protect_url: bool,
715    /// Whether Notes fields should be encrypted
716    pub protect_notes: bool,
717}
718
719#[derive(Debug, Default, Clone, PartialEq, Eq)]
720/// Meta information about this database
721pub struct Meta {
722    /// Application used to generate this database
723    pub generator: String,
724    /// Short name for the database
725    pub database_name: String,
726    /// Longer description of the database
727    pub database_description: String,
728    /// Non standard information from plugins and other clients
729    pub custom_data: Vec<Field>,
730    /// Memory protection configuration for this client
731    pub memory_protection: MemoryProtection,
732}
733
734#[derive(Debug, Clone, PartialEq, Eq)]
735/// Audit times for this item
736pub struct Times {
737    /// Time last edited
738    pub last_modification_time: NaiveDateTime,
739    /// Time created
740    pub creation_time: NaiveDateTime,
741    /// Time last accessed
742    pub last_access_time: NaiveDateTime,
743    /// Time at which this password needs rotation
744    pub expiry_time: NaiveDateTime,
745    /// Time at which this password was last moved within the database
746    pub location_changed: NaiveDateTime,
747    /// Whether this password expires
748    pub expires: bool,
749    /// Count of usages with autofill functions
750    pub usage_count: u32,
751}
752
753impl Default for Times {
754    fn default() -> Times {
755        let now = chrono::Local::now()
756            .naive_local()
757            .with_nanosecond(0)
758            .unwrap();
759        Times {
760            expires: false,
761            usage_count: 0,
762            last_modification_time: now,
763            creation_time: now,
764            last_access_time: now,
765            expiry_time: now,
766            location_changed: now,
767        }
768    }
769}
770
771#[derive(Debug, Clone, PartialEq, Eq)]
772/// Decrypted password database
773///
774/// See the [module-level documentation][crate::database] for more information.
775pub struct Database {
776    /// Meta information about this database
777    pub(crate) meta: Meta,
778    /// Trees of items in this database
779    pub(crate) groups: Vec<Group>,
780}
781
782impl Default for Database {
783    fn default() -> Self {
784        let root = Group::new("Root");
785        Database {
786            meta: Meta::default(),
787            groups: vec![root],
788        }
789    }
790}
791
792impl Database {
793    /// Return meta information about the database like name and access times
794    pub fn meta(&self) -> &Meta {
795        &self.meta
796    }
797
798    /// Mutable meta information about the database like name and access times
799    pub fn meta_mut(&mut self) -> &mut Meta {
800        &mut self.meta
801    }
802
803    /// Get the database name
804    pub fn name(&self) -> &str {
805        &self.meta.database_name
806    }
807
808    /// Set the database name
809    pub fn set_name<S: ToString>(&mut self, name: S) {
810        self.meta.database_name = name.to_string();
811    }
812
813    /// Get the database description
814    pub fn description(&self) -> &str {
815        &self.meta.database_description
816    }
817
818    /// Set the database name
819    pub fn set_description<S: ToString>(&mut self, desc: S) {
820        self.meta.database_description = desc.to_string();
821    }
822
823    /// Add a entry to the root group
824    pub fn add_entry(&mut self, entry: Entry) {
825        self.groups[0].entries.push(entry);
826    }
827
828    /// Add a child group to the root group
829    pub fn add_group(&mut self, entry: Group) {
830        self.groups[0].groups.push(entry);
831    }
832
833    /// Replace the root group (and therefore all entries!) with a custom tree
834    pub fn replace_root(&mut self, group: Group) {
835        self.groups = vec![group];
836    }
837
838    /// Recursively searches for the first group matching a filter
839    pub fn find_group<F: FnMut(&Group) -> bool>(&self, f: F) -> Option<&Group> {
840        self.root().find_group(f)
841    }
842
843    /// Recursively searches for the first group matching a filter, returns it mutably
844    pub fn find_group_mut<F: FnMut(&Group) -> bool>(&mut self, f: F) -> Option<&mut Group> {
845        self.root_mut().find_group_mut(f)
846    }
847
848    /// Recursively searches for the first entry matching a filter
849    pub fn find_entry<F: FnMut(&Entry) -> bool>(&self, f: F) -> Option<&Entry> {
850        self.root().find_entry(f)
851    }
852
853    /// Recursively searches for the first entry matching a filter, returns it mutably
854    pub fn find_entry_mut<F: FnMut(&Entry) -> bool>(&mut self, f: F) -> Option<&mut Entry> {
855        self.root_mut().find_entry_mut(f)
856    }
857
858    /// Top level group for database entries
859    pub fn root(&self) -> &Group {
860        &self.groups[0]
861    }
862
863    /// Mutable top level group for database entries
864    pub fn root_mut(&mut self) -> &mut Group {
865        &mut self.groups[0]
866    }
867}
868
869/// TOTP one time password secret in KeepassXC format
870pub struct Otp<'a> {
871    url: Cow<'a, str>,
872}
873
874impl<'a> Otp<'a> {
875    /// Create a new OTP password from the given details
876    pub fn new<S: ToString>(secret: S, period: u32, digits: u32) -> Otp<'static> {
877        let url = format!(
878            "otpauth://totp/kdbxrs:kdbxrs?secret={}&period={}&digits={}",
879            secret.to_string(),
880            period,
881            digits
882        );
883        Otp {
884            url: Cow::Owned(url),
885        }
886    }
887
888    fn find_url_param(&self, key: &str) -> Option<&str> {
889        let mut parts = self.url.split('?');
890        let _path = parts.next()?;
891        let params = parts.next()?;
892        let params = params.split('&');
893
894        for param in params {
895            let mut param_parts = param.split('=');
896            let pkey = param_parts.next()?;
897            if pkey == key {
898                return param_parts.next();
899            }
900        }
901        None
902    }
903
904    /// Retrieve the secret used to generate one time passwords
905    pub fn secret(&self) -> Option<&str> {
906        self.find_url_param("secret")
907    }
908
909    /// Return the period for which passwords are valid
910    pub fn period(&self) -> Option<u32> {
911        self.find_url_param("secret").and_then(|p| p.parse().ok())
912    }
913
914    /// Return the number of digits in the resulting code
915    pub fn digits(&self) -> Option<u32> {
916        self.find_url_param("digits").and_then(|p| p.parse().ok())
917    }
918}