Skip to main content

keepass/db/types/
attachment.rs

1use std::{
2    collections::HashSet,
3    ops::{Deref, DerefMut},
4};
5
6use crate::{
7    db::{EntryId, EntryMut, EntryRef, Value},
8    Database,
9};
10
11/// Identifier for an [Attachment]
12#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
13#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
14pub struct AttachmentId(usize);
15
16impl AttachmentId {
17    pub(crate) fn new(id: usize) -> Self {
18        AttachmentId(id)
19    }
20
21    /// Get the underlying usize ID of this attachment.
22    pub fn id(&self) -> usize {
23        self.0
24    }
25
26    pub(crate) fn next_free(database: &Database) -> Self {
27        let mut id = 0;
28        while database.attachments.contains_key(&AttachmentId(id)) {
29            id += 1;
30        }
31        AttachmentId(id)
32    }
33}
34
35impl std::fmt::Display for AttachmentId {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        write!(f, "{}", self.0)
38    }
39}
40
41/// Attachment for an entry.
42///
43/// Both header attachments (KDBX4-style) and XML attachments (KDBX3-style) will be converted to
44/// this format when parsing.
45#[derive(Debug, PartialEq, Eq, Clone)]
46#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
47pub struct Attachment {
48    pub(crate) id: AttachmentId,
49
50    /// The entries that reference this attachment, along with the history index of the entry
51    /// version that references it (if applicable).
52    pub(crate) entries: HashSet<(EntryId, Option<usize>)>,
53
54    /// The binary data of the attachment.
55    pub data: Value<Vec<u8>>,
56}
57
58impl Attachment {
59    /// Get the ID of this attachment.
60    pub fn id(&self) -> AttachmentId {
61        self.id
62    }
63}
64
65impl Deref for Attachment {
66    type Target = Value<Vec<u8>>;
67
68    fn deref(&self) -> &Self::Target {
69        &self.data
70    }
71}
72
73impl DerefMut for Attachment {
74    fn deref_mut(&mut self) -> &mut Self::Target {
75        &mut self.data
76    }
77}
78
79/// An immutable reference to an [Attachment]. Implements [Deref] to [&Attachment][Attachment]
80pub struct AttachmentRef<'a> {
81    database: &'a Database,
82    id: AttachmentId,
83}
84
85impl AttachmentRef<'_> {
86    pub(crate) fn new(database: &Database, id: AttachmentId) -> AttachmentRef<'_> {
87        AttachmentRef { database, id }
88    }
89
90    /// Get an immutable reference to the database that owns this attachment.
91    pub fn database(&self) -> &Database {
92        self.database
93    }
94
95    /// Get an iterator over the entries that reference this attachment.
96    ///
97    /// If `include_historical` is false, only returns entries that currently reference this
98    /// attachment. If `include_historical` is true, also returns old versions of entries that
99    /// reference this attachment, even if they have been modified to no longer reference it.
100    pub fn entries(&self, include_historical: bool) -> impl Iterator<Item = EntryRef<'_>> {
101        self.entries.iter().filter_map(move |&(id, history_index)| {
102            if !include_historical && history_index.is_some() {
103                return None;
104            }
105
106            Some(EntryRef::new_historical(self.database, id, history_index))
107        })
108    }
109}
110
111impl Deref for AttachmentRef<'_> {
112    type Target = Attachment;
113
114    fn deref(&self) -> &Self::Target {
115        // UNWRAP safety: AttachmentRef should only be created with valid AttachmentIds
116        #[allow(clippy::expect_used)]
117        self.database
118            .attachments
119            .get(&self.id)
120            .expect("AttachmentRef points to non-existent attachment")
121    }
122}
123
124/// A mutable reference to an [Attachment]. Implements [DerefMut] to [&mut Attachment][Attachment]
125pub struct AttachmentMut<'a> {
126    database: &'a mut Database,
127    id: AttachmentId,
128}
129
130impl AttachmentMut<'_> {
131    pub(crate) fn new(database: &mut Database, id: AttachmentId) -> AttachmentMut<'_> {
132        AttachmentMut { database, id }
133    }
134
135    /// Get an immutable reference to this attachment.
136    pub fn as_ref(&self) -> AttachmentRef<'_> {
137        AttachmentRef {
138            database: self.database,
139            id: self.id,
140        }
141    }
142
143    /// Edit this attachment with a closure, which is passed a mutable reference to this attachment.
144    pub fn edit(&mut self, f: impl FnOnce(&mut AttachmentMut<'_>)) -> &mut Self {
145        f(self);
146        self
147    }
148
149    /// Get a mutable reference to the database that owns this attachment.
150    pub fn database_mut(&mut self) -> &mut Database {
151        self.database
152    }
153
154    /// Get an iterator over the entries that reference this attachment, with mutable access.
155    ///
156    /// If `include_historical` is false, only returns entries that currently reference this
157    /// attachment. If `include_historical` is true, also returns old versions of entries that
158    /// reference this attachment, even if they have been modified to no longer reference it.
159    pub fn foreach_entry_mut<F>(&mut self, mut f: F, include_historical: bool)
160    where
161        F: FnMut(EntryMut<'_>),
162    {
163        let entries: Vec<(EntryId, Option<usize>)> = self.entries.iter().copied().collect();
164        for (id, history_index) in entries {
165            if !include_historical && history_index.is_some() {
166                continue;
167            }
168
169            f(EntryMut::new_historical(self.database, id, history_index));
170        }
171    }
172
173    /// Remove this attachment from the database, and all references to it
174    pub fn remove(mut self) {
175        let id = self.id;
176
177        self.foreach_entry_mut(
178            |mut entry| {
179                let mut attachments_to_remove = Vec::new();
180                for (name, attachment_id) in &entry.attachments {
181                    if *attachment_id == id {
182                        attachments_to_remove.push(name.clone());
183                    }
184                }
185
186                for name in attachments_to_remove {
187                    entry.attachments.remove(&name);
188                }
189            },
190            true,
191        );
192
193        self.database.attachments.remove(&self.id);
194    }
195}
196
197impl Deref for AttachmentMut<'_> {
198    type Target = Attachment;
199
200    fn deref(&self) -> &Self::Target {
201        // UNWRAP safety: AttachmentMut should only be created with valid AttachmentIds
202        #[allow(clippy::expect_used)]
203        self.database
204            .attachments
205            .get(&self.id)
206            .expect("AttachmentMut points to non-existent attachment")
207    }
208}
209
210impl DerefMut for AttachmentMut<'_> {
211    fn deref_mut(&mut self) -> &mut Self::Target {
212        // UNWRAP safety: AttachmentMut should only be created with valid AttachmentIds
213        #[allow(clippy::expect_used)]
214        self.database
215            .attachments
216            .get_mut(&self.id)
217            .expect("AttachmentMut points to non-existent attachment")
218    }
219}