1use chrono::{DateTime, Utc};
10use common;
11use format::{kdb2_reader, kdb2_writer};
12use io::{Log, LogReader, LogWriter};
13use std::io::{Read, Write};
14use super::binaries_map::BinariesMap;
15use super::color::Color;
16use super::comment::Comment;
17use super::composite_key::CompositeKey;
18use super::compression::Compression;
19use super::custom_data_map::CustomDataMap;
20use super::custom_icons_map::CustomIconsMap;
21use super::db_type::DbType;
22use super::entries_map::EntriesMap;
23use super::error::Error;
24use super::group::Group;
25use super::group_uuid::GroupUuid;
26use super::groups_map::GroupsMap;
27use super::history_map::HistoryMap;
28use super::icon::Icon;
29use super::master_cipher::MasterCipher;
30use super::result::Result;
31use super::stream_cipher::StreamCipher;
32use super::transform_rounds::TransformRounds;
33use super::version::Version;
34
35#[derive(Clone, Debug, PartialEq)]
37pub struct Database {
38 pub comment: Option<Comment>,
40
41 pub composite_key: CompositeKey,
43
44 pub compression: Compression,
46
47 pub db_type: DbType,
49
50 pub master_cipher: MasterCipher,
52
53 pub stream_cipher: StreamCipher,
55
56 pub transform_rounds: TransformRounds,
58
59 pub version: Version,
61
62 pub binaries: BinariesMap,
64
65 pub color: Option<Color>,
67
68 pub custom_data: CustomDataMap,
70
71 pub custom_icons: CustomIconsMap,
73
74 pub def_username: String,
76
77 pub def_username_changed: DateTime<Utc>,
79
80 pub description: String,
82
83 pub description_changed: DateTime<Utc>,
85
86 pub entries: EntriesMap,
88
89 pub entry_templates_group_changed: DateTime<Utc>,
91
92 pub entry_templates_group_uuid: GroupUuid,
94
95 pub generator: String,
97
98 pub group_uuid: Option<GroupUuid>,
100
101 pub groups: GroupsMap,
103
104 pub history: HistoryMap,
106
107 pub history_max_items: i32,
109
110 pub history_max_size: i32,
112
113 pub last_selected_group: GroupUuid,
115
116 pub last_top_visible_group: GroupUuid,
118
119 pub maintenance_history_days: i32,
121
122 pub master_key_change_force: i32,
123
124 pub master_key_change_rec: i32,
125
126 pub master_key_changed: DateTime<Utc>,
128
129 pub name: String,
131
132 pub name_changed: DateTime<Utc>,
134
135 pub protect_notes: bool,
137
138 pub protect_password: bool,
140
141 pub protect_title: bool,
143
144 pub protect_url: bool,
146
147 pub protect_username: bool,
149
150 pub recycle_bin_changed: DateTime<Utc>,
152
153 pub recycle_bin_enabled: bool,
155
156 pub recycle_bin_uuid: GroupUuid,
158}
159
160impl Database {
161 pub fn new(key: &CompositeKey) -> Database {
172 let now = Utc::now();
173 let mut root = Group::new(common::ROOT_GROUP_NAME);
174 let mut recycle_bin = Group::new(common::RECYCLE_BIN_NAME);
175 let mut groups = GroupsMap::new();
176
177 recycle_bin.enable_auto_type = Some(false);
178 recycle_bin.enable_searching = Some(false);
179 recycle_bin.icon = Icon::RecycleBin;
180
181 let root_uuid = root.uuid;
182 let recycle_bin_uuid = recycle_bin.uuid;
183 root.groups.push(recycle_bin_uuid);
184 groups.insert(root_uuid, root);
185 groups.insert(recycle_bin_uuid, recycle_bin);
186
187 Database {
188 comment: None,
189 composite_key: key.clone(),
190 compression: Compression::GZip,
191 db_type: DbType::Kdb2,
192 master_cipher: MasterCipher::Aes256,
193 stream_cipher: StreamCipher::Salsa20,
194 transform_rounds: TransformRounds(10000),
195 version: Version::new_kdb2(),
196 binaries: BinariesMap::new(),
197 color: None,
198 custom_data: CustomDataMap::new(),
199 custom_icons: CustomIconsMap::new(),
200 def_username: String::new(),
201 def_username_changed: now,
202 description: String::new(),
203 description_changed: now,
204 entries: EntriesMap::new(),
205 entry_templates_group_changed: now,
206 entry_templates_group_uuid: GroupUuid::nil(),
207 generator: String::from(common::GENERATOR_NAME),
208 group_uuid: Some(root_uuid),
209 groups: groups,
210 history: HistoryMap::new(),
211 history_max_items: common::HISTORY_MAX_ITEMS_DEFAULT,
212 history_max_size: common::HISTORY_MAX_SIZE_DEFAULT,
213 last_selected_group: GroupUuid::nil(),
214 last_top_visible_group: GroupUuid::nil(),
215 maintenance_history_days: common::MAINTENANCE_HISTORY_DAYS_DEFAULT,
216 master_key_change_force: common::MASTER_KEY_CHANGE_FORCE_DEFAULT,
217 master_key_change_rec: common::MASTER_KEY_CHANGE_REC_DEFAULT,
218 master_key_changed: now,
219 name: String::new(),
220 name_changed: now,
221 protect_notes: common::PROTECT_NOTES_DEFAULT,
222 protect_password: common::PROTECT_PASSWORD_DEFAULT,
223 protect_title: common::PROTECT_TITLE_DEFAULT,
224 protect_url: common::PROTECT_URL_DEFAULT,
225 protect_username: common::PROTECT_USERNAME_DEFAULT,
226 recycle_bin_changed: now,
227 recycle_bin_enabled: common::RECYCLE_BIN_ENABLED_DEFAULT,
228 recycle_bin_uuid: recycle_bin_uuid,
229 }
230 }
231
232 pub fn open<R: Read>(reader: &mut R, key: &CompositeKey) -> Result<Database> {
249 let mut reader = LogReader::new(reader);
250 let mut buffer = [0u8; 4];
251
252 try!(reader.read(&mut buffer));
253 if buffer != common::DB_SIGNATURE {
254 return Err(Error::InvalidDbSignature(buffer));
255 }
256
257 try!(reader.read(&mut buffer));
258 if buffer == common::KDB1_SIGNATURE {
259 return Err(Error::UnhandledDbType(buffer));
260 } else if buffer == common::KDB2_SIGNATURE {
261 Database::open_kdb2(&mut reader, key)
262 } else {
263 return Err(Error::UnhandledDbType(buffer));
264 }
265 }
266
267 pub fn save<W: Write>(&self, writer: &mut W) -> Result<()> {
286 let mut writer = LogWriter::new(writer);
287 match self.db_type {
288 DbType::Kdb1 => Err(Error::Unimplemented(String::from("KeePass v1 not supported"))),
289 DbType::Kdb2 => kdb2_writer::write(&mut writer, self),
290 }
291 }
292
293 fn open_kdb2<R: Log + Read>(reader: &mut R, key: &CompositeKey) -> Result<Database> {
294 let (meta_data, xml_data) = try!(kdb2_reader::read(reader, key));
295 match xml_data.header_hash {
296 Some(header_hash) => {
297 if meta_data.header_hash != header_hash {
298 return Err(Error::InvalidHeaderHash);
299 }
300 }
301 None => {}
302 }
303
304 let db = Database {
305 comment: meta_data.comment,
306 composite_key: key.clone(),
307 compression: meta_data.compression,
308 db_type: DbType::Kdb2,
309 master_cipher: meta_data.master_cipher,
310 stream_cipher: meta_data.stream_cipher,
311 transform_rounds: meta_data.transform_rounds,
312 version: meta_data.version,
313
314 binaries: xml_data.binaries,
315 color: xml_data.color,
316 custom_data: xml_data.custom_data,
317 custom_icons: xml_data.custom_icons,
318 def_username: xml_data.def_username,
319 def_username_changed: xml_data.def_username_changed,
320 description: xml_data.description,
321 description_changed: xml_data.description_changed,
322 entries: xml_data.entries,
323 entry_templates_group_changed: xml_data.entry_templates_group_changed,
324 entry_templates_group_uuid: xml_data.entry_templates_group_uuid,
325 generator: xml_data.generator,
326 group_uuid: xml_data.group_uuid,
327 groups: xml_data.groups,
328 history: xml_data.history,
329 history_max_items: xml_data.history_max_items,
330 history_max_size: xml_data.history_max_size,
331 last_selected_group: xml_data.last_selected_group,
332 last_top_visible_group: xml_data.last_top_visible_group,
333 maintenance_history_days: xml_data.maintenance_history_days,
334 master_key_change_force: xml_data.master_key_change_force,
335 master_key_change_rec: xml_data.master_key_change_rec,
336 master_key_changed: xml_data.master_key_changed,
337 name: xml_data.name,
338 name_changed: xml_data.name_changed,
339 protect_notes: xml_data.protect_notes,
340 protect_password: xml_data.protect_password,
341 protect_title: xml_data.protect_title,
342 protect_url: xml_data.protect_url,
343 protect_username: xml_data.protect_username,
344 recycle_bin_changed: xml_data.recycle_bin_changed,
345 recycle_bin_enabled: xml_data.recycle_bin_enabled,
346 recycle_bin_uuid: xml_data.recycle_bin_uuid,
347 };
348
349 Ok(db)
350 }
351}
352
353#[cfg(test)]
354mod tests {
355
356 use chrono::Utc;
357 use super::*;
358 use types::BinariesMap;
359 use types::CompositeKey;
360 use types::Compression;
361 use types::CustomDataMap;
362 use types::CustomIconsMap;
363 use types::DbType;
364 use types::EntriesMap;
365 use types::GroupUuid;
366 use types::HistoryMap;
367 use types::MasterCipher;
368 use types::StreamCipher;
369 use types::TransformRounds;
370 use types::Version;
371 use utils::test::approx_equal_datetime;
372
373 #[test]
374 fn test_new_returns_correct_instance() {
375 let now = Utc::now();
376 let key = CompositeKey::from_password("5pZ5mgpTkLCDaM46IuH7yGafZFIICyvC");
377 let db = Database::new(&key);
378 assert_eq!(db.comment, None);
379 assert_eq!(db.composite_key, key);
380 assert_eq!(db.compression, Compression::GZip);
381 assert_eq!(db.db_type, DbType::Kdb2);
382 assert_eq!(db.master_cipher, MasterCipher::Aes256);
383 assert_eq!(db.stream_cipher, StreamCipher::Salsa20);
384 assert_eq!(db.transform_rounds, TransformRounds(10000));
385 assert_eq!(db.version, Version::new_kdb2());
386 assert_eq!(db.binaries, BinariesMap::new());
387 assert_eq!(db.color, None);
388 assert_eq!(db.custom_data, CustomDataMap::new());
389 assert_eq!(db.custom_icons, CustomIconsMap::new());
390 assert_eq!(db.def_username, "");
391 assert!(approx_equal_datetime(db.def_username_changed, now));
392 assert_eq!(db.description, "");
393 assert!(approx_equal_datetime(db.description_changed, now));
394 assert_eq!(db.entries, EntriesMap::new());
395 assert!(approx_equal_datetime(db.entry_templates_group_changed, now));
396 assert_eq!(db.entry_templates_group_uuid, GroupUuid::nil());
397 assert_eq!(db.generator, "rust-kpdb");
398 assert!(db.group_uuid != None);
399 assert!(db.group_uuid != Some(GroupUuid::nil()));
400 assert_eq!(db.groups.len(), 2);
401 assert_eq!(db.history, HistoryMap::new());
402 assert_eq!(db.history_max_items, 10);
403 assert_eq!(db.history_max_size, 6291456);
404 assert_eq!(db.last_selected_group, GroupUuid::nil());
405 assert_eq!(db.last_top_visible_group, GroupUuid::nil());
406 assert_eq!(db.maintenance_history_days, 365);
407 assert_eq!(db.master_key_change_force, -1);
408 assert_eq!(db.master_key_change_rec, -1);
409 assert!(approx_equal_datetime(db.master_key_changed, now));
410 assert_eq!(db.name, "");
411 assert!(approx_equal_datetime(db.name_changed, now));
412 assert_eq!(db.protect_notes, false);
413 assert_eq!(db.protect_password, true);
414 assert_eq!(db.protect_title, false);
415 assert_eq!(db.protect_url, false);
416 assert_eq!(db.protect_username, false);
417 assert!(approx_equal_datetime(db.recycle_bin_changed, now));
418 assert_eq!(db.recycle_bin_enabled, true);
419 assert!(db.recycle_bin_uuid != GroupUuid::nil());
420 }
421}