Skip to main content

keepass_ng/db/types/
mod.rs

1pub(crate) mod attachment;
2pub(crate) mod autotype;
3pub(crate) mod color;
4pub(crate) mod custom_data;
5pub(crate) mod entry;
6pub(crate) mod group;
7pub(crate) mod history;
8pub(crate) mod icon;
9pub(crate) mod iconid;
10pub(crate) mod meta;
11pub(crate) mod node;
12pub(crate) mod times;
13pub(crate) mod value;
14
15pub use attachment::Attachment;
16pub use autotype::{AutoType, AutoTypeAssociation, DataTransferObfuscation};
17pub use color::{Color, ParseColorError};
18pub use custom_data::{CustomDataItem, CustomDataValue};
19pub use entry::Entry;
20pub use group::Group;
21pub use history::History;
22pub use icon::{CustomIcon, Icon};
23pub use iconid::IconId;
24pub use meta::{MemoryProtection, Meta};
25pub use node::{
26    Node, NodeIterator, NodePtr, SerializableNodePtr, group_add_child, group_get_children, group_remove_node_by_uuid, node_is_entry,
27    node_is_equals_to, node_is_group, rc_refcell_node, search_node_by_uuid, search_node_by_uuid_with_specific_type, with_node,
28    with_node_mut,
29};
30pub use times::Times;
31pub use value::Value;
32
33use crate::config::DatabaseConfig;
34use std::collections::{HashMap, HashSet};
35
36use chrono::NaiveDateTime;
37use uuid::Uuid;
38
39/// A decrypted `KeePass` database
40#[derive(Debug)]
41#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
42pub struct Database {
43    /// Configuration settings of the database such as encryption and compression algorithms
44    pub config: DatabaseConfig,
45
46    /// Root node of the KeePass database
47    pub root: SerializableNodePtr,
48
49    /// References to previously-deleted objects and their deletion times.
50    pub deleted_objects: HashMap<Uuid, Option<NaiveDateTime>>,
51
52    /// Metadata of the KeePass database
53    pub meta: Meta,
54}
55
56impl Clone for Database {
57    fn clone(&self) -> Self {
58        Self {
59            config: self.config.clone(),
60            root: self.root.borrow().duplicate().into(),
61            deleted_objects: self.deleted_objects.clone(),
62            meta: self.meta.clone(),
63        }
64    }
65}
66
67impl PartialEq for Database {
68    fn eq(&self, other: &Self) -> bool {
69        self.config == other.config
70            && self.deleted_objects == other.deleted_objects
71            && self.meta == other.meta
72            && node_is_equals_to(&self.root, &other.root)
73    }
74}
75
76impl Eq for Database {}
77
78impl Database {
79    /// Remove custom icons that are not referenced by any group, entry, or history item.
80    ///
81    /// Returns the number of removed icons.
82    pub fn purge_unused_custom_icons(&mut self) -> usize {
83        let mut referenced = HashSet::new();
84
85        for node in NodeIterator::new(&self.root) {
86            let node = node.borrow();
87            if let Some(uuid) = node.get_custom_icon_uuid() {
88                referenced.insert(uuid);
89            }
90
91            if let Some(entry) = node.downcast_ref::<Entry>() {
92                for history_entry in entry.get_history().iter().flat_map(|history| &history.entries) {
93                    if let Some(uuid) = history_entry.custom_icon {
94                        referenced.insert(uuid);
95                    }
96                }
97            }
98        }
99
100        let before = self.meta.custom_icons.len();
101        self.meta.custom_icons.retain(|uuid, _| referenced.contains(uuid));
102        before - self.meta.custom_icons.len()
103    }
104
105    /// Create a new, empty database
106    pub fn new(config: DatabaseConfig) -> Database {
107        Self {
108            config,
109            root: rc_refcell_node(Group::new("Root")).into(),
110            deleted_objects: Default::default(),
111            meta: Meta::new(),
112        }
113    }
114
115    pub fn node_get_parents(&self, node: &NodePtr) -> Vec<Uuid> {
116        let mut parents = Vec::new();
117        let mut parent_uuid = node.borrow().get_parent();
118        while let Some(uuid) = parent_uuid {
119            parents.push(uuid);
120            let parent_node = search_node_by_uuid_with_specific_type::<Group>(&self.root, uuid);
121            parent_uuid = parent_node.and_then(|node| node.borrow().get_parent());
122        }
123        parents
124    }
125
126    pub fn set_recycle_bin_enabled(&mut self, enabled: bool) {
127        self.meta.set_recycle_bin_enabled(enabled);
128    }
129
130    pub fn recycle_bin_enabled(&self) -> bool {
131        self.meta.recycle_bin_enabled()
132    }
133
134    pub fn node_is_recycle_bin(&self, node: &NodePtr) -> bool {
135        let uuid = node.borrow().get_uuid();
136        node_is_group(node) && self.get_recycle_bin().is_some_and(|bin| bin.borrow().get_uuid() == uuid)
137    }
138
139    pub fn node_is_in_recycle_bin(&self, node: Uuid) -> bool {
140        if let Some(node) = search_node_by_uuid(&self.root, node) {
141            let parents = self.node_get_parents(&node);
142            self.get_recycle_bin()
143                .map(|bin| bin.borrow().get_uuid())
144                .is_some_and(|uuid| parents.contains(&uuid))
145        } else {
146            false
147        }
148    }
149
150    pub fn get_recycle_bin(&self) -> Option<NodePtr> {
151        if !self.recycle_bin_enabled() {
152            return None;
153        }
154        let uuid = self.meta.recyclebin_uuid?;
155        group_get_children(&self.root).and_then(|children| {
156            children
157                .into_iter()
158                .find(|child| child.borrow().get_uuid() == uuid && node_is_group(child))
159        })
160    }
161
162    pub fn create_recycle_bin(&mut self) -> crate::Result<NodePtr> {
163        use crate::error::Error;
164        if !self.recycle_bin_enabled() {
165            return Err(Error::RecycleBinDisabled);
166        }
167        if self.get_recycle_bin().is_some() {
168            return Err(Error::RecycleBinAlreadyExists);
169        }
170        let recycle_bin = rc_refcell_node(Group::new("Recycle Bin"));
171        recycle_bin.borrow_mut().set_icon_id(Some(IconId::RECYCLE_BIN));
172        self.meta.recyclebin_uuid = Some(recycle_bin.borrow().get_uuid());
173        let count = group_get_children(&self.root).ok_or("")?.len();
174        group_add_child(&self.root, recycle_bin.clone(), count)?;
175        Ok(recycle_bin)
176    }
177
178    pub fn remove_node_by_uuid(&mut self, uuid: Uuid) -> crate::Result<NodePtr> {
179        if !self.recycle_bin_enabled() {
180            let node = group_remove_node_by_uuid(&self.root, uuid)?;
181            self.deleted_objects.insert(uuid, Some(Times::now()));
182            return Ok(node);
183        }
184        let node_in_recycle_bin = self.node_is_in_recycle_bin(uuid);
185        let recycle_bin = self.get_recycle_bin().ok_or("").or_else(|_| self.create_recycle_bin())?;
186        let recycle_bin_uuid = recycle_bin.borrow().get_uuid();
187        // This can remove the recycle bin itself, or node in the recycle bin, or node not in the recycle bin
188        let node = group_remove_node_by_uuid(&self.root, uuid)?;
189        self.deleted_objects.insert(uuid, Some(Times::now()));
190        if uuid != recycle_bin_uuid && !node_in_recycle_bin {
191            group_add_child(&recycle_bin, node.clone(), 0)?;
192        }
193        self.meta.set_recycle_bin_changed();
194        Ok(node)
195    }
196
197    pub fn search_node_by_uuid(&self, uuid: Uuid) -> Option<NodePtr> {
198        search_node_by_uuid(&self.root, uuid)
199    }
200
201    fn create_new_node<T: Node + Default>(&self, parent: Uuid, index: usize) -> crate::Result<NodePtr> {
202        let new_node = rc_refcell_node(T::default());
203        let parent = search_node_by_uuid_with_specific_type::<Group>(&self.root, parent)
204            .or_else(|| Some(self.root.clone().into()))
205            .ok_or("No parent node")?;
206        with_node_mut::<Group, _, _>(&parent, |parent| {
207            parent.add_child(new_node.clone(), index);
208        });
209        Ok(new_node)
210    }
211
212    pub fn create_new_entry(&self, parent: Uuid, index: usize) -> crate::Result<NodePtr> {
213        self.create_new_node::<Entry>(parent, index)
214    }
215
216    pub fn create_new_group(&self, parent: Uuid, index: usize) -> crate::Result<NodePtr> {
217        self.create_new_node::<Group>(parent, index)
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use uuid::uuid;
225
226    fn custom_icon(id: Uuid) -> CustomIcon {
227        CustomIcon {
228            id,
229            name: None,
230            last_modification_time: None,
231            data: vec![1, 2, 3],
232        }
233    }
234
235    #[test]
236    fn purge_unused_custom_icons_keeps_all_referenced_icons() {
237        let group_icon = uuid!("11111111111111111111111111111111");
238        let entry_icon = uuid!("22222222222222222222222222222222");
239        let history_icon = uuid!("33333333333333333333333333333333");
240        let orphan_icon = uuid!("44444444444444444444444444444444");
241
242        let mut database = Database::new(DatabaseConfig::default());
243        database.meta.custom_icons.extend([
244            (group_icon, custom_icon(group_icon)),
245            (entry_icon, custom_icon(entry_icon)),
246            (history_icon, custom_icon(history_icon)),
247            (orphan_icon, custom_icon(orphan_icon)),
248        ]);
249
250        with_node_mut::<Group, _, _>(&database.root, |root| {
251            root.custom_icon_uuid = Some(group_icon);
252        })
253        .unwrap();
254
255        let history_entry = Entry {
256            custom_icon: Some(history_icon),
257            ..Entry::default()
258        };
259        let entry = Entry {
260            custom_icon: Some(entry_icon),
261            history: Some(History {
262                entries: vec![history_entry],
263            }),
264            ..Entry::default()
265        };
266        group_add_child(&database.root, rc_refcell_node(entry), 0).unwrap();
267
268        assert_eq!(database.purge_unused_custom_icons(), 1);
269        assert!(database.meta.custom_icons.contains_key(&group_icon));
270        assert!(database.meta.custom_icons.contains_key(&entry_icon));
271        assert!(database.meta.custom_icons.contains_key(&history_icon));
272        assert!(!database.meta.custom_icons.contains_key(&orphan_icon));
273        assert_eq!(database.purge_unused_custom_icons(), 0);
274    }
275}