1use std::collections::HashMap;
2
3use base64::{engine::general_purpose as base64_engine, Engine as _};
4use thiserror::Error;
5
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "save_kdbx4")]
9use crate::format::xml_db::tags::join_tags;
10use crate::{
11 crypt::{ciphers::Cipher, CryptographyError},
12 db::{AttachmentId, Color, EntryId, EntryMut, GroupId},
13 format::xml_db::{
14 custom_serde::{cs_bool, cs_opt_bool, cs_opt_fromstr, cs_opt_string},
15 meta::CustomData,
16 tags::split_tags,
17 times::Times,
18 UUID,
19 },
20};
21
22#[derive(Debug, Serialize, Deserialize)]
23#[serde(rename_all = "PascalCase")]
24pub struct Entry {
25 #[serde(rename = "UUID")]
26 pub uuid: UUID,
27
28 #[serde(
29 default,
30 rename = "IconID",
31 with = "cs_opt_fromstr",
32 skip_serializing_if = "Option::is_none"
33 )]
34 pub icon_id: Option<usize>,
35
36 #[serde(default, rename = "CustomIconUUID", skip_serializing_if = "Option::is_none")]
37 pub custom_icon_uuid: Option<UUID>,
38
39 #[serde(default, with = "cs_opt_string", skip_serializing_if = "Option::is_none")]
40 pub foreground_color: Option<Color>,
41
42 #[serde(default, with = "cs_opt_string", skip_serializing_if = "Option::is_none")]
43 pub background_color: Option<Color>,
44
45 #[serde(
46 default,
47 rename = "OverrideURL",
48 with = "cs_opt_string",
49 skip_serializing_if = "Option::is_none"
50 )]
51 pub override_url: Option<String>,
52
53 #[serde(default, with = "cs_opt_string", skip_serializing_if = "Option::is_none")]
54 pub tags: Option<String>,
55
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub times: Option<Times>,
58
59 #[serde(default, rename = "String")]
60 pub string_fields: Vec<StringField>,
61
62 #[serde(default, rename = "Binary")]
63 pub binary_fields: Vec<BinaryField>,
64
65 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub auto_type: Option<AutoType>,
67
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub history: Option<History>,
70
71 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub custom_data: Option<CustomData>,
73
74 #[serde(default, with = "cs_opt_bool", skip_serializing_if = "Option::is_none")]
75 pub quality_check: Option<bool>,
76
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub previous_parent_group: Option<UUID>,
79}
80
81impl Entry {
82 pub(crate) fn xml_to_db_handle(
83 self,
84 mut target: crate::db::EntryMut<'_>,
85 attachments: &HashMap<crate::db::AttachmentId, crate::db::Attachment>,
86 custom_icons: &HashMap<crate::db::CustomIconId, crate::db::CustomIcon>,
87 inner_decryptor: &mut dyn Cipher,
88 ) -> Result<(), UnprotectError> {
89 target.icon = if let Some(ci) = self.custom_icon_uuid.and_then(|ci| {
90 let icon_id = crate::db::CustomIconId::from_uuid(ci.0);
91 custom_icons.contains_key(&icon_id).then_some(icon_id)
92 }) {
93 Some(crate::db::Icon::Custom(ci))
94 } else {
95 self.icon_id.map(crate::db::Icon::BuiltIn)
96 };
97
98 target.foreground_color = self.foreground_color;
99 target.background_color = self.background_color;
100 target.override_url = self.override_url;
101 target.tags = self.tags.as_deref().map(split_tags).unwrap_or_default();
102
103 target.times = self.times.map(|t| t.into()).unwrap_or_default();
104
105 for field in self.string_fields {
106 let fval = field.value.value.unwrap_or_default();
107 let value = if field.value.protected {
108 let fval = base64_engine::STANDARD.decode(fval)?;
109 let fval = inner_decryptor.decrypt(&fval)?;
110 let fval = String::from_utf8_lossy(&fval).to_string();
111
112 crate::db::Value::protected(fval)
113 } else {
114 crate::db::Value::unprotected(fval)
115 };
116 target.fields.insert(field.key, value);
117 }
118
119 for field in self.binary_fields {
120 let id = AttachmentId::new(field.value.value_ref);
121 if attachments.contains_key(&id) {
122 target.attachments.insert(field.key.clone(), id);
123 }
124 }
125
126 target.autotype = self.auto_type.map(|at| at.into());
127
128 if let Some(h) = self.history {
129 target.history = Some(crate::db::History { entries: Vec::new() });
130
131 for (i, e) in h.entries.into_iter().enumerate() {
132 let id = EntryId::from_uuid(e.uuid.0);
133
134 let mut he = crate::db::Entry::with_id(id, target.parent);
135 he.history = None; if let Some(h) = target.history.as_mut() {
138 h.entries.push(he);
139 }
140
141 let historical = EntryMut::new_historical(target.database_mut(), id, Some(i));
142 e.xml_to_db_handle(historical, attachments, custom_icons, inner_decryptor)?;
143 }
144 }
145
146 if let Some(cd) = self.custom_data {
147 target.custom_data = cd.into();
148 }
149
150 target.quality_check = self.quality_check.unwrap_or(true);
151
152 target.previous_parent_group = self.previous_parent_group.map(|g| GroupId::from_uuid(g.0));
153
154 Ok(())
155 }
156
157 #[cfg(feature = "save_kdbx4")]
158 pub(crate) fn db_to_xml(
159 db: crate::db::EntryRef<'_>,
160 inner_encryptor: &mut dyn Cipher,
161 ) -> Result<Self, CryptographyError> {
162 let (icon_id, custom_icon_uuid) = match db.icon {
163 Some(crate::db::Icon::Custom(cid)) => (None, Some(UUID(cid.uuid()))),
164 Some(crate::db::Icon::BuiltIn(i)) => (Some(i), None),
165 _ => (None, None),
166 };
167
168 let mut string_fields = Vec::with_capacity(db.fields.len());
169 for (k, v) in &db.fields {
170 let value = if v.is_protected() {
171 let encrypted = inner_encryptor.encrypt(v.get().as_bytes())?;
172 let encoded = base64_engine::STANDARD.encode(&encrypted);
173
174 StringValue {
175 protected: true,
176 value: Some(encoded),
177 }
178 } else {
179 StringValue {
180 protected: false,
181 value: Some(v.as_str().to_string()),
182 }
183 };
184
185 string_fields.push(StringField {
186 key: k.clone(),
187 value,
188 });
189 }
190
191 let mut binary_fields = Vec::with_capacity(db.attachments.len());
192 for (key, attachment) in &db.attachments {
193 binary_fields.push(BinaryField {
194 key: key.clone(),
195 value: BinaryValue {
196 value_ref: attachment.id(),
197 },
198 });
199 }
200
201 let history = if let Some(h) = db.history.as_ref() {
202 let entries = (0..h.entries.len())
203 .filter_map(|i| Some(Entry::db_to_xml(db.historical(i)?, inner_encryptor)))
204 .collect::<Result<Vec<_>, CryptographyError>>()?;
205
206 Some(History { entries })
207 } else {
208 None
209 };
210
211 let custom_data: Option<CustomData> = if db.custom_data.is_empty() {
212 None
213 } else {
214 Some(db.custom_data.clone().into())
215 };
216
217 Ok(Entry {
218 uuid: UUID(db.id().uuid()),
219 icon_id,
220 custom_icon_uuid,
221 foreground_color: db.foreground_color.clone(),
222 background_color: db.background_color.clone(),
223 override_url: db.override_url.clone(),
224 tags: join_tags(&db.tags),
225 times: Some(db.times.clone().into()),
226 string_fields,
227 binary_fields,
228 auto_type: db.autotype.as_ref().map(|at| at.clone().into()),
229 history,
230 custom_data,
231 quality_check: Some(db.quality_check),
232 previous_parent_group: db.previous_parent_group.map(|g| UUID(g.uuid())),
233 })
234 }
235}
236
237#[derive(Debug, Error)]
238#[non_exhaustive]
239pub enum UnprotectError {
240 #[error("Error base64 decoding protected value: {0}")]
241 Base64(#[from] base64::DecodeError),
242
243 #[error("Error decrypting protected value: {0}")]
244 Decrypt(#[from] CryptographyError),
245
246 #[error(transparent)]
247 Io(#[from] std::io::Error),
248
249 #[error(transparent)]
251 DuplicateEntryId(#[from] crate::db::DuplicateEntryIdError),
252
253 #[error(transparent)]
255 DuplicateGroupId(#[from] crate::db::DuplicateGroupIdError),
256}
257
258#[derive(Debug, Serialize, Deserialize)]
259#[serde(rename_all = "PascalCase")]
260pub struct StringField {
261 pub key: String,
262 pub value: StringValue,
263}
264
265#[derive(Debug, Deserialize)]
266pub struct StringValue {
267 #[serde(default, rename = "@Protected", with = "cs_bool")]
268 protected: bool,
269
270 #[serde(
271 default,
272 rename = "$value",
273 with = "cs_opt_string",
274 skip_serializing_if = "Option::is_none"
275 )]
276 value: Option<String>,
277}
278
279impl Serialize for StringValue {
280 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
281 where
282 S: serde::Serializer,
283 {
284 use serde::ser::SerializeStruct;
285
286 if self.protected {
287 let mut state = serializer.serialize_struct("StringValue", 2)?;
288 state.serialize_field("@Protected", if self.protected { "True" } else { "False" })?;
289
290 if let Some(ref val) = self.value {
291 state.serialize_field("$value", val)?;
292 } else {
293 state.serialize_field("$value", "")?;
294 }
295 state.end()
296 } else {
297 let mut state = serializer.serialize_struct("StringValue", 1)?;
298
299 if let Some(ref val) = self.value {
300 state.serialize_field("$value", val)?;
301 } else {
302 state.serialize_field("$value", "")?;
303 }
304 state.end()
305 }
306 }
307}
308
309#[derive(Debug, Serialize, Deserialize)]
310#[serde(rename_all = "PascalCase")]
311pub struct BinaryField {
312 pub key: String,
313 pub value: BinaryValue,
314}
315
316#[derive(Debug, Serialize, Deserialize)]
317pub struct BinaryValue {
318 #[serde(rename = "@Ref")]
319 pub value_ref: usize,
320}
321
322#[derive(Debug, Serialize, Deserialize)]
323#[serde(rename_all = "PascalCase")]
324pub struct AutoType {
325 #[serde(default, with = "cs_bool")]
326 pub enabled: bool,
327
328 #[serde(default, with = "cs_opt_fromstr", skip_serializing_if = "Option::is_none")]
329 pub data_transfer_obfuscation: Option<usize>,
330
331 #[serde(default, with = "cs_opt_string", skip_serializing_if = "Option::is_none")]
332 pub default_sequence: Option<String>,
333
334 #[serde(rename = "Association", default)]
335 pub associations: Vec<AutoTypeAssociation>,
336}
337
338impl From<AutoType> for crate::db::AutoType {
339 fn from(value: AutoType) -> Self {
340 crate::db::AutoType {
341 enabled: value.enabled,
342 default_sequence: value.default_sequence,
343 data_transfer_obfuscation: value
344 .data_transfer_obfuscation
345 .map(|d| d.into())
346 .unwrap_or_default(),
347 associations: value.associations.into_iter().map(|a| a.into()).collect(),
348 }
349 }
350}
351
352impl From<crate::db::AutoType> for AutoType {
353 fn from(value: crate::db::AutoType) -> Self {
354 Self {
355 enabled: value.enabled,
356 data_transfer_obfuscation: Some(value.data_transfer_obfuscation.into()),
357 default_sequence: value.default_sequence,
358 associations: value.associations.into_iter().map(|a| a.into()).collect(),
359 }
360 }
361}
362
363impl From<usize> for crate::db::DataTransferObfuscation {
364 fn from(value: usize) -> Self {
365 match value {
366 0 => Self::None,
367 1 => Self::UseClipboard,
368 _ => Self::None, }
370 }
371}
372
373impl From<crate::db::DataTransferObfuscation> for usize {
374 fn from(value: crate::db::DataTransferObfuscation) -> Self {
375 match value {
376 crate::db::DataTransferObfuscation::None => 0,
377 crate::db::DataTransferObfuscation::UseClipboard => 1,
378 }
379 }
380}
381
382#[derive(Debug, Serialize, Deserialize)]
383#[serde(rename_all = "PascalCase")]
384pub struct AutoTypeAssociation {
385 pub window: String,
386 pub keystroke_sequence: String,
387}
388
389impl From<AutoTypeAssociation> for crate::db::AutoTypeAssociation {
390 fn from(val: AutoTypeAssociation) -> Self {
391 crate::db::AutoTypeAssociation {
392 window: val.window,
393 sequence: val.keystroke_sequence,
394 }
395 }
396}
397
398impl From<crate::db::AutoTypeAssociation> for AutoTypeAssociation {
399 fn from(value: crate::db::AutoTypeAssociation) -> Self {
400 Self {
401 window: value.window,
402 keystroke_sequence: value.sequence,
403 }
404 }
405}
406
407#[derive(Debug, Serialize, Deserialize)]
408#[serde(rename_all = "PascalCase")]
409pub struct History {
410 #[serde(default, rename = "Entry")]
411 pub entries: Vec<Entry>,
412}
413
414#[allow(clippy::indexing_slicing, clippy::unwrap_used)]
415#[cfg(test)]
416mod tests {
417
418 use super::*;
419
420 #[derive(Debug, Serialize, Deserialize)]
421 struct Test<T>(T);
422
423 #[test]
424 fn test_deserialize_string_field() {
425 let xml = r#"<String>
426 <Key>Title</Key>
427 <Value>Example Title</Value>
428 </String>"#;
429
430 let deserialized: Test<StringField> = quick_xml::de::from_str(xml).unwrap();
431 assert_eq!(deserialized.0.key, "Title");
432 assert_eq!(deserialized.0.value.value.unwrap(), "Example Title");
433 assert!(!deserialized.0.value.protected);
434
435 let xml_protected = r#"<String>
436 <Key>Password</Key>
437 <Value Protected="True">cGFzc3dvcmQ=</Value>
438 </String>"#;
439
440 let deserialized_protected: Test<StringField> = quick_xml::de::from_str(xml_protected).unwrap();
441 assert_eq!(deserialized_protected.0.key, "Password");
442 assert_eq!(deserialized_protected.0.value.value.unwrap(), "cGFzc3dvcmQ=");
443 assert!(deserialized_protected.0.value.protected);
444 }
445
446 #[test]
447 fn test_serialize_string_field() {
448 let string_field = StringField {
449 key: "Username".to_string(),
450 value: StringValue {
451 protected: false,
452 value: Some("user123".to_string()),
453 },
454 };
455
456 let serialized = quick_xml::se::to_string(&Test(string_field)).unwrap();
457 assert_eq!(
458 serialized,
459 r#"<Test><Key>Username</Key><Value>user123</Value></Test>"#
460 );
461
462 let string_field_protected = StringField {
463 key: "Password".to_string(),
464 value: StringValue {
465 protected: true,
466 value: Some("cGFzc3dvcmQ=".to_string()),
467 },
468 };
469
470 let serialized_protected = quick_xml::se::to_string(&Test(string_field_protected)).unwrap();
471 assert_eq!(
472 serialized_protected,
473 r#"<Test><Key>Password</Key><Value Protected="True">cGFzc3dvcmQ=</Value></Test>"#
474 );
475 }
476
477 #[test]
478 fn test_deserialize_binary_field() {
479 let xml = r#"<Binary>
480 <Key>Attachment</Key>
481 <Value Ref="1"/>
482 </Binary>"#;
483
484 let deserialized: Test<BinaryField> = quick_xml::de::from_str(xml).unwrap();
485 assert_eq!(deserialized.0.key, "Attachment");
486 assert_eq!(deserialized.0.value.value_ref, 1);
487 }
488
489 #[test]
490 fn test_serialize_binary_field() {
491 let binary_field = BinaryField {
492 key: "Attachment".to_string(),
493 value: BinaryValue { value_ref: 1 },
494 };
495 let serialized = quick_xml::se::to_string(&Test(binary_field)).unwrap();
496 assert_eq!(
497 serialized,
498 r#"<Test><Key>Attachment</Key><Value Ref="1"/></Test>"#
499 );
500 }
501
502 #[test]
503 fn test_deserialize_autotype() {
504 let xml = r#"<AutoType>
505 <Enabled>True</Enabled>
506 <DataTransferObfuscation>0</DataTransferObfuscation>
507 <DefaultSequence>{USERNAME}{TAB}{PASSWORD}{ENTER}</DefaultSequence>
508 </AutoType>"#;
509
510 let deserialized: Test<AutoType> = quick_xml::de::from_str(xml).unwrap();
511 assert!(deserialized.0.enabled);
512 assert_eq!(deserialized.0.data_transfer_obfuscation, Some(0));
513 assert_eq!(
514 deserialized.0.default_sequence.unwrap(),
515 "{USERNAME}{TAB}{PASSWORD}{ENTER}"
516 );
517 }
518
519 #[test]
520 fn test_serialize_autotype() {
521 let autotype = AutoType {
522 enabled: true,
523 data_transfer_obfuscation: Some(0),
524 default_sequence: Some("{USERNAME}{TAB}{PASSWORD}{ENTER}".to_string()),
525 associations: vec![AutoTypeAssociation {
526 window: "Example Window".to_string(),
527 keystroke_sequence: "{USERNAME}{TAB}{PASSWORD}{ENTER}".to_string(),
528 }],
529 };
530
531 let serialized = quick_xml::se::to_string(&Test(autotype)).unwrap();
532 assert_eq!(
533 serialized,
534 r#"<Test><Enabled>True</Enabled><DataTransferObfuscation>0</DataTransferObfuscation><DefaultSequence>{USERNAME}{TAB}{PASSWORD}{ENTER}</DefaultSequence><Association><Window>Example Window</Window><KeystrokeSequence>{USERNAME}{TAB}{PASSWORD}{ENTER}</KeystrokeSequence></Association></Test>"#
535 );
536 }
537
538 #[test]
539 fn test_deserialize_entry() {
540 let xml = r#"<Entry>
541 <UUID>AAECAwQFBgcICQoLDA0ODw==</UUID>
542 <IconID>1</IconID>
543 <ForegroundColor>#FF0000</ForegroundColor>
544 <BackgroundColor>#00FF00</BackgroundColor>
545 <OverrideURL>https://example.com</OverrideURL>
546 <Tags>tag1;tag2</Tags>
547 <Times>
548 <CreationTime>2023-10-05T12:34:56Z</CreationTime>
549 <LastModificationTime>2023-10-06T12:34:56Z</LastModificationTime>
550 <LastAccessTime>2023-10-07T12:34:56Z</LastAccessTime>
551 <ExpiryTime>2024-10-05T12:34:56Z</ExpiryTime>
552 <Expires>True</Expires>
553 <UsageCount>5</UsageCount>
554 <LocationChanged>2023-10-08T12:34:56Z</LocationChanged>
555 </Times>
556 <String>
557 <Key>Title</Key>
558 <Value>Example Title</Value>
559 </String>
560 <Binary>
561 <Key>Attachment</Key>
562 <Value Ref="1"/>
563 </Binary>
564 <AutoType>
565 <Enabled>True</Enabled>
566 <DataTransferObfuscation>0</DataTransferObfuscation>
567 <DefaultSequence>{USERNAME}{TAB}{PASSWORD}{ENTER}</DefaultSequence>
568 </AutoType>
569 </Entry>"#;
570
571 let deserialized: Test<Entry> = quick_xml::de::from_str(xml).unwrap();
572 assert_eq!(
573 deserialized.0.uuid.0.as_bytes(),
574 &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]
575 );
576 assert_eq!(deserialized.0.icon_id.unwrap(), 1);
577 assert_eq!(deserialized.0.foreground_color.unwrap().to_string(), "#FF0000");
578 assert_eq!(deserialized.0.background_color.unwrap().to_string(), "#00FF00");
579 assert_eq!(deserialized.0.override_url.unwrap(), "https://example.com");
580 assert_eq!(deserialized.0.tags.unwrap(), "tag1;tag2");
581 assert_eq!(deserialized.0.string_fields.len(), 1);
582 assert_eq!(deserialized.0.string_fields[0].key, "Title");
583 assert_eq!(
584 deserialized.0.string_fields[0].value.value.as_ref().unwrap(),
585 "Example Title"
586 );
587 assert_eq!(deserialized.0.binary_fields.len(), 1);
588 assert_eq!(deserialized.0.binary_fields[0].key, "Attachment");
589 assert_eq!(deserialized.0.binary_fields[0].value.value_ref, 1);
590 assert!(deserialized.0.auto_type.is_some());
591 let autotype = deserialized.0.auto_type.unwrap();
592 assert!(autotype.enabled);
593 assert_eq!(autotype.data_transfer_obfuscation, Some(0));
594 assert_eq!(
595 autotype.default_sequence.unwrap(),
596 "{USERNAME}{TAB}{PASSWORD}{ENTER}"
597 );
598
599 assert!(deserialized.0.history.is_none());
600 }
601
602 #[test]
603 fn test_deserialize_entry_minimal() {
604 let xml = r#"<Entry>
605 <UUID>AAECAwQFBgcICQoLDA0ODw==</UUID>
606 <IconID/>
607 <ForegroundColor/>
608 <BackgroundColor/>
609 <OverrideURL/>
610 <Tags/>
611 <Times/>
612 <AutoType/>
613 </Entry>"#;
614
615 let deserialized: Test<Entry> = quick_xml::de::from_str(xml).unwrap();
616
617 println!("{:#?}", deserialized);
618
619 assert!(deserialized.0.icon_id.is_none());
620 assert!(deserialized.0.foreground_color.is_none());
621 assert!(deserialized.0.background_color.is_none());
622 assert!(deserialized.0.override_url.is_none());
623 assert!(deserialized.0.tags.is_none());
624 assert!(deserialized.0.string_fields.is_empty());
625 assert!(deserialized.0.binary_fields.is_empty());
626 }
627}