1use super::decoders::{encode_datetime, encode_uuid};
2use crate::database::{Database, Entry, Field, Group, MemoryProtection, Meta, Times, Value};
3use base64::prelude::{Engine, BASE64_STANDARD};
4use cipher::StreamCipher;
5use std::io::Write;
6use thiserror::Error;
7use xml::writer::events::XmlEvent;
8use xml::writer::EventWriter as XmlWriter;
9
10#[derive(Debug, Error)]
11pub enum Error {
13 #[error("Could not write XML: {0}")]
15 Xml(#[from] xml::writer::Error),
16 #[error("Error encountered encrypting with a stream cipher: {0}")]
18 Cipher(String),
19}
20
21type Result<T> = std::result::Result<T, Error>;
22
23fn write_bool_tag<W: Write>(writer: &mut XmlWriter<W>, name: &str, value: bool) -> Result<()> {
24 writer.write(XmlEvent::start_element(name))?;
25 writer.write(XmlEvent::characters(if value { "True" } else { "False" }))?;
26 writer.write(XmlEvent::end_element())?;
27 Ok(())
28}
29
30fn write_string_tag<W: Write, S: AsRef<str>>(
31 writer: &mut XmlWriter<W>,
32 name: &str,
33 value: S,
34) -> Result<()> {
35 writer.write(XmlEvent::start_element(name))?;
36 writer.write(XmlEvent::characters(value.as_ref()))?;
37 writer.write(XmlEvent::end_element())?;
38 Ok(())
39}
40
41fn write_field<W: Write, S: StreamCipher + ?Sized>(
42 writer: &mut XmlWriter<W>,
43 wrapper: &str,
44 field: &Field,
45 stream_cipher: &mut S,
46) -> Result<()> {
47 writer.write(XmlEvent::start_element(wrapper))?;
48 write_string_tag(writer, "Key", &field.key)?;
49 match &field.value {
50 Value::Protected(v) => {
51 writer.write(XmlEvent::start_element("Value").attr("Protected", "True"))?;
52 let mut encrypt_buf = v.clone().into_bytes();
53 stream_cipher
54 .try_apply_keystream(&mut encrypt_buf)
55 .map_err(|e| Error::Cipher(format!("Encryption cipher failed: {}", e)))?;
56 let encrypted = BASE64_STANDARD.encode(&encrypt_buf);
57 writer.write(XmlEvent::characters(&encrypted))?;
58 writer.write(XmlEvent::end_element())?;
59 }
60 Value::Standard(v) => write_string_tag(writer, "Value", v)?,
61 Value::Empty | Value::ProtectEmpty => {
62 writer.write(XmlEvent::start_element("Value"))?;
63 writer.write(XmlEvent::end_element())?;
64 }
65 }
66 writer.write(XmlEvent::end_element())?;
67 Ok(())
68}
69
70fn write_memory_protection<W: Write>(
71 writer: &mut XmlWriter<W>,
72 protection: &MemoryProtection,
73) -> Result<()> {
74 writer.write(XmlEvent::start_element("MemoryProtection"))?;
75 write_bool_tag(writer, "ProtectUserName", protection.protect_user_name)?;
76 write_bool_tag(writer, "ProtectPassword", protection.protect_password)?;
77 write_bool_tag(writer, "ProtectTitle", protection.protect_title)?;
78 write_bool_tag(writer, "ProtectNotes", protection.protect_notes)?;
79 write_bool_tag(writer, "ProtectURL", protection.protect_url)?;
80 writer.write(XmlEvent::end_element())?;
81 Ok(())
82}
83
84fn write_meta<W: Write, S: StreamCipher + ?Sized>(
85 writer: &mut XmlWriter<W>,
86 meta: &Meta,
87 stream_cipher: &mut S,
88) -> Result<()> {
89 writer.write(XmlEvent::start_element("Meta"))?;
90 write_string_tag(writer, "Generator", "kdbx-rs")?;
91 write_string_tag(writer, "DatabaseName", &meta.database_name)?;
92 write_string_tag(writer, "DatabaseDescription", &meta.database_description)?;
93 writer.write(XmlEvent::start_element("CustomData"))?;
94 for field in &meta.custom_data {
95 write_field(writer, "Item", field, stream_cipher)?;
96 }
97 writer.write(XmlEvent::end_element())?;
98 write_memory_protection(writer, &meta.memory_protection)?;
99 writer.write(XmlEvent::end_element())?;
100 Ok(())
101}
102
103fn write_times<W: Write>(writer: &mut XmlWriter<W>, times: &Times) -> Result<()> {
104 writer.write(XmlEvent::start_element("Times"))?;
105 write_string_tag(
106 writer,
107 "LastModificationTime",
108 encode_datetime(times.last_modification_time),
109 )?;
110 write_string_tag(writer, "CreationTime", encode_datetime(times.creation_time))?;
111 write_string_tag(
112 writer,
113 "LastAccessTime",
114 encode_datetime(times.last_access_time),
115 )?;
116 write_string_tag(
117 writer,
118 "LocationChanged",
119 encode_datetime(times.location_changed),
120 )?;
121 write_string_tag(writer, "ExpiryTime", encode_datetime(times.expiry_time))?;
122 write_string_tag(writer, "UsageCount", times.usage_count.to_string())?;
123 write_bool_tag(writer, "Expires", times.expires)?;
124 writer.write(XmlEvent::end_element())?;
125 Ok(())
126}
127
128fn write_entry<W: Write, S: StreamCipher + ?Sized>(
129 writer: &mut XmlWriter<W>,
130 entry: &Entry,
131 stream_cipher: &mut S,
132) -> Result<()> {
133 writer.write(XmlEvent::start_element("Entry"))?;
134 write_string_tag(writer, "UUID", &encode_uuid(entry.uuid()))?;
135 write_times(writer, &entry.times)?;
136 for field in entry.fields() {
137 write_field(writer, "String", field, stream_cipher)?;
138 }
139 if !entry.history.is_empty() {
140 writer.write(XmlEvent::start_element("History"))?;
141 for old_entry in entry.history.entries() {
142 write_entry(writer, old_entry, stream_cipher)?;
143 }
144 writer.write(XmlEvent::end_element())?;
145 }
146 writer.write(XmlEvent::end_element())?;
147 Ok(())
148}
149
150fn write_group<W: Write, S: StreamCipher + ?Sized>(
151 writer: &mut XmlWriter<W>,
152 group: &Group,
153 stream_cipher: &mut S,
154) -> Result<()> {
155 writer.write(XmlEvent::start_element("Group"))?;
156 write_string_tag(writer, "UUID", encode_uuid(group.uuid()))?;
157 write_string_tag(writer, "Name", group.name())?;
158 write_times(writer, &group.times)?;
159 for entry in group.entries() {
160 write_entry(writer, entry, stream_cipher)?;
161 }
162 for group in group.groups() {
163 write_group(writer, group, stream_cipher)?;
164 }
165 writer.write(XmlEvent::end_element())?;
166 Ok(())
167}
168
169pub fn write_xml<W: Write, S: StreamCipher + ?Sized>(
176 output: W,
177 database: &Database,
178 stream_cipher: &mut S,
179) -> Result<()> {
180 let config = xml::EmitterConfig::default()
181 .perform_indent(true)
182 .indent_string("\t");
183 let mut writer = xml::EventWriter::new_with_config(output, config);
184 writer.write(XmlEvent::start_element("KeePassFile"))?;
185 write_meta(&mut writer, &database.meta, stream_cipher)?;
186 writer.write(XmlEvent::start_element("Root"))?;
187 for group in &database.groups {
188 write_group(&mut writer, group, stream_cipher)?;
189 }
190 writer.write(XmlEvent::end_element())?;
191 writer.write(XmlEvent::end_element())?;
192 Ok(())
193}