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::entry::Entry;
23use super::entry_uuid::EntryUuid;
24use super::error::Error;
25use super::group::Group;
26use super::group_uuid::GroupUuid;
27use super::master_cipher::MasterCipher;
28use super::result::Result;
29use super::stream_cipher::StreamCipher;
30use super::string_value::StringValue;
31use super::transform_rounds::TransformRounds;
32use super::version::Version;
33
34#[derive(Clone, Debug, PartialEq)]
36pub struct Database {
37 pub comment: Option<Comment>,
39
40 pub composite_key: CompositeKey,
42
43 pub compression: Compression,
45
46 pub db_type: DbType,
48
49 pub master_cipher: MasterCipher,
51
52 pub stream_cipher: StreamCipher,
54
55 pub transform_rounds: TransformRounds,
57
58 pub version: Version,
60
61 pub binaries: BinariesMap,
63
64 pub color: Option<Color>,
66
67 pub custom_data: CustomDataMap,
69
70 pub custom_icons: CustomIconsMap,
72
73 pub def_username: String,
75
76 pub def_username_changed: DateTime<Utc>,
78
79 pub description: String,
81
82 pub description_changed: DateTime<Utc>,
84
85 pub entry_templates_group_changed: DateTime<Utc>,
87
88 pub entry_templates_group_uuid: GroupUuid,
90
91 pub generator: String,
93
94 pub history_max_items: i32,
96
97 pub history_max_size: i32,
99
100 pub last_selected_group: GroupUuid,
102
103 pub last_top_visible_group: GroupUuid,
105
106 pub maintenance_history_days: i32,
108
109 pub master_key_change_force: i32,
110
111 pub master_key_change_rec: i32,
112
113 pub master_key_changed: DateTime<Utc>,
115
116 pub name: String,
118
119 pub name_changed: DateTime<Utc>,
121
122 pub protect_notes: bool,
124
125 pub protect_password: bool,
127
128 pub protect_title: bool,
130
131 pub protect_url: bool,
133
134 pub protect_username: bool,
136
137 pub recycle_bin_changed: DateTime<Utc>,
139
140 pub recycle_bin_enabled: bool,
142
143 pub recycle_bin_uuid: GroupUuid,
145
146 pub root_group: Group,
148}
149
150impl Database {
151 pub fn new(key: &CompositeKey) -> Database {
162 let now = Utc::now();
163 Database {
164 comment: None,
165 composite_key: key.clone(),
166 compression: Compression::GZip,
167 db_type: DbType::Kdb2,
168 master_cipher: MasterCipher::Aes256,
169 stream_cipher: StreamCipher::Salsa20,
170 transform_rounds: TransformRounds(10000),
171 version: Version::new_kdb2(),
172 binaries: BinariesMap::new(),
173 color: None,
174 custom_data: CustomDataMap::new(),
175 custom_icons: CustomIconsMap::new(),
176 def_username: String::new(),
177 def_username_changed: now,
178 description: String::new(),
179 description_changed: now,
180 entry_templates_group_changed: now,
181 entry_templates_group_uuid: GroupUuid::nil(),
182 generator: String::from(common::GENERATOR_NAME),
183 history_max_items: common::HISTORY_MAX_ITEMS_DEFAULT,
184 history_max_size: common::HISTORY_MAX_SIZE_DEFAULT,
185 last_selected_group: GroupUuid::nil(),
186 last_top_visible_group: GroupUuid::nil(),
187 maintenance_history_days: common::MAINTENANCE_HISTORY_DAYS_DEFAULT,
188 master_key_change_force: common::MASTER_KEY_CHANGE_FORCE_DEFAULT,
189 master_key_change_rec: common::MASTER_KEY_CHANGE_REC_DEFAULT,
190 master_key_changed: now,
191 name: String::new(),
192 name_changed: now,
193 protect_notes: common::PROTECT_NOTES_DEFAULT,
194 protect_password: common::PROTECT_PASSWORD_DEFAULT,
195 protect_title: common::PROTECT_TITLE_DEFAULT,
196 protect_url: common::PROTECT_URL_DEFAULT,
197 protect_username: common::PROTECT_USERNAME_DEFAULT,
198 recycle_bin_changed: now,
199 recycle_bin_enabled: common::RECYCLE_BIN_ENABLED_DEFAULT,
200 recycle_bin_uuid: GroupUuid::nil(),
201 root_group: Group::new(common::ROOT_GROUP_NAME),
202 }
203 }
204
205 pub fn find_entries<'a, S: Into<String>>(&'a self, text: S) -> Vec<&'a Entry> {
231 let mut list = Vec::new();
232 let text = &text.into().to_lowercase();
233 for group in self.root_group.iter() {
234 for entry in group.entries.iter() {
235 if entry_contains_string(entry, text) {
236 list.push(entry);
237 }
238 }
239 }
240 list
241 }
242
243
244 pub fn find_entries_mut<'a, S: Into<String>>(&'a mut self, text: S) -> Vec<&'a mut Entry> {
267 let mut list = Vec::new();
268 let text = &text.into().to_lowercase();
269 for group in self.root_group.iter_mut() {
270 for entry in group.entries.iter_mut() {
271 if entry_contains_string(entry, text) {
272 list.push(entry);
273 }
274 }
275 }
276 list
277 }
278
279 pub fn find_groups<'a, S: Into<String>>(&'a self, name: S) -> Vec<&'a Group> {
298 let name = &name.into().to_lowercase();
299 self.root_group
300 .iter()
301 .filter(|g| g.name.to_lowercase().contains(name))
302 .collect::<Vec<&'a Group>>()
303 }
304
305 pub fn find_groups_mut<'a, S: Into<String>>(&'a mut self, name: S) -> Vec<&'a mut Group> {
321 let name = &name.into().to_lowercase();
322 self.root_group
323 .iter_mut()
324 .filter(|g| g.name.to_lowercase().contains(name))
325 .collect::<Vec<&'a mut Group>>()
326 }
327
328 pub fn get_entry<'a>(&'a self, uuid: EntryUuid) -> Option<&'a Entry> {
345 for group in self.root_group.iter() {
346 for entry in group.entries.iter() {
347 if entry.uuid == uuid {
348 return Some(entry);
349 }
350 }
351 }
352 None
353 }
354
355 pub fn get_entry_mut<'a>(&'a mut self, uuid: EntryUuid) -> Option<&'a mut Entry> {
372 for group in self.root_group.iter_mut() {
373 for entry in group.entries.iter_mut() {
374 if entry.uuid == uuid {
375 return Some(entry);
376 }
377 }
378 }
379 None
380 }
381
382 pub fn get_group<'a>(&'a self, uuid: GroupUuid) -> Option<&'a Group> {
399 self.root_group.iter().find(|g| g.uuid == uuid)
400 }
401
402 pub fn get_group_mut<'a>(&'a mut self, uuid: GroupUuid) -> Option<&'a mut Group> {
419 self.root_group.iter_mut().find(|g| g.uuid == uuid)
420 }
421
422 pub fn open<R: Read>(reader: &mut R, key: &CompositeKey) -> Result<Database> {
439 let mut reader = LogReader::new(reader);
440 let mut buffer = [0u8; 4];
441
442 try!(reader.read(&mut buffer));
443 if buffer != common::DB_SIGNATURE {
444 return Err(Error::InvalidDbSignature(buffer));
445 }
446
447 try!(reader.read(&mut buffer));
448 if buffer == common::KDB1_SIGNATURE {
449 return Err(Error::UnhandledDbType(buffer));
450 } else if buffer == common::KDB2_SIGNATURE {
451 Database::open_kdb2(&mut reader, key)
452 } else {
453 return Err(Error::UnhandledDbType(buffer));
454 }
455 }
456
457 pub fn save<W: Write>(&self, writer: &mut W) -> Result<()> {
476 let mut writer = LogWriter::new(writer);
477 match self.db_type {
478 DbType::Kdb1 => Err(Error::Unimplemented(String::from("KeePass v1 not supported"))),
479 DbType::Kdb2 => kdb2_writer::write(&mut writer, self),
480 }
481 }
482
483 fn open_kdb2<R: Log + Read>(reader: &mut R, key: &CompositeKey) -> Result<Database> {
484 let (meta_data, xml_data) = try!(kdb2_reader::read(reader, key));
485 match xml_data.header_hash {
486 Some(header_hash) => {
487 if meta_data.header_hash != header_hash {
488 return Err(Error::InvalidHeaderHash);
489 }
490 }
491 None => {}
492 }
493
494 let root_group = match xml_data.root_group {
495 Some(group) => group,
496 None => Group::new(common::ROOT_GROUP_NAME),
497 };
498
499 let db = Database {
500 comment: meta_data.comment,
501 composite_key: key.clone(),
502 compression: meta_data.compression,
503 db_type: DbType::Kdb2,
504 master_cipher: meta_data.master_cipher,
505 stream_cipher: meta_data.stream_cipher,
506 transform_rounds: meta_data.transform_rounds,
507 version: meta_data.version,
508
509 binaries: xml_data.binaries,
510 color: xml_data.color,
511 custom_data: xml_data.custom_data,
512 custom_icons: xml_data.custom_icons,
513 def_username: xml_data.def_username,
514 def_username_changed: xml_data.def_username_changed,
515 description: xml_data.description,
516 description_changed: xml_data.description_changed,
517 entry_templates_group_changed: xml_data.entry_templates_group_changed,
518 entry_templates_group_uuid: xml_data.entry_templates_group_uuid,
519 generator: xml_data.generator,
520 history_max_items: xml_data.history_max_items,
521 history_max_size: xml_data.history_max_size,
522 last_selected_group: xml_data.last_selected_group,
523 last_top_visible_group: xml_data.last_top_visible_group,
524 maintenance_history_days: xml_data.maintenance_history_days,
525 master_key_change_force: xml_data.master_key_change_force,
526 master_key_change_rec: xml_data.master_key_change_rec,
527 master_key_changed: xml_data.master_key_changed,
528 name: xml_data.name,
529 name_changed: xml_data.name_changed,
530 protect_notes: xml_data.protect_notes,
531 protect_password: xml_data.protect_password,
532 protect_title: xml_data.protect_title,
533 protect_url: xml_data.protect_url,
534 protect_username: xml_data.protect_username,
535 recycle_bin_changed: xml_data.recycle_bin_changed,
536 recycle_bin_enabled: xml_data.recycle_bin_enabled,
537 recycle_bin_uuid: xml_data.recycle_bin_uuid,
538 root_group: root_group,
539 };
540
541 Ok(db)
542 }
543}
544
545fn entry_contains_string(entry: &Entry, name: &String) -> bool {
546 for value in entry.strings.values() {
547 match *value {
548 StringValue::Plain(ref string) => {
549 if string.to_lowercase().contains(name) {
550 return true;
551 }
552 }
553 StringValue::Protected(_) => {}
554 }
555 }
556 false
557}
558
559#[cfg(test)]
560mod tests {
561
562 use chrono::Utc;
563 use super::*;
564 use types::BinariesMap;
565 use types::CompositeKey;
566 use types::Compression;
567 use types::CustomDataMap;
568 use types::CustomIconsMap;
569 use types::DbType;
570 use types::GroupUuid;
571 use types::MasterCipher;
572 use types::StreamCipher;
573 use types::TransformRounds;
574 use types::Version;
575 use utils::test::approx_equal_datetime;
576
577 #[test]
578 fn test_new_returns_correct_instance() {
579 let now = Utc::now();
580 let key = CompositeKey::from_password("5pZ5mgpTkLCDaM46IuH7yGafZFIICyvC");
581 let db = Database::new(&key);
582 assert_eq!(db.comment, None);
583 assert_eq!(db.composite_key, key);
584 assert_eq!(db.compression, Compression::GZip);
585 assert_eq!(db.db_type, DbType::Kdb2);
586 assert_eq!(db.master_cipher, MasterCipher::Aes256);
587 assert_eq!(db.stream_cipher, StreamCipher::Salsa20);
588 assert_eq!(db.transform_rounds, TransformRounds(10000));
589 assert_eq!(db.version, Version::new_kdb2());
590 assert_eq!(db.binaries, BinariesMap::new());
591 assert_eq!(db.color, None);
592 assert_eq!(db.custom_data, CustomDataMap::new());
593 assert_eq!(db.custom_icons, CustomIconsMap::new());
594 assert_eq!(db.def_username, "");
595 assert!(approx_equal_datetime(db.def_username_changed, now));
596 assert_eq!(db.description, "");
597 assert!(approx_equal_datetime(db.description_changed, now));
598 assert!(approx_equal_datetime(db.entry_templates_group_changed, now));
599 assert_eq!(db.entry_templates_group_uuid, GroupUuid::nil());
600 assert_eq!(db.generator, "rust-kpdb");
601 assert_eq!(db.history_max_items, 10);
602 assert_eq!(db.history_max_size, 6291456);
603 assert_eq!(db.last_selected_group, GroupUuid::nil());
604 assert_eq!(db.last_top_visible_group, GroupUuid::nil());
605 assert_eq!(db.maintenance_history_days, 365);
606 assert_eq!(db.master_key_change_force, -1);
607 assert_eq!(db.master_key_change_rec, -1);
608 assert!(approx_equal_datetime(db.master_key_changed, now));
609 assert_eq!(db.name, "");
610 assert!(approx_equal_datetime(db.name_changed, now));
611 assert_eq!(db.protect_notes, false);
612 assert_eq!(db.protect_password, true);
613 assert_eq!(db.protect_title, false);
614 assert_eq!(db.protect_url, false);
615 assert_eq!(db.protect_username, false);
616 assert!(approx_equal_datetime(db.recycle_bin_changed, now));
617 assert_eq!(db.recycle_bin_enabled, true);
618 assert_eq!(db.recycle_bin_uuid, GroupUuid::nil());
619 assert!(db.root_group.uuid != GroupUuid::nil());
620 }
621
622 #[test]
623 fn test_find_entries_returns_correct_entries() {
624 let db = db_with_groups_and_entries();
625 let result = db.find_entries("Proton");
626 assert_eq!(result.len(), 2);
627
628 let result = db.find_entries("Unknown");
629 assert_eq!(result.len(), 0);
630 }
631
632 #[test]
633 fn test_find_entries_mut_returns_correct_entries() {
634 let mut db = db_with_groups_and_entries();
635 {
636 let result = db.find_entries_mut("Proton");
637 assert_eq!(result.len(), 2);
638 }
639 {
640 let result = db.find_entries_mut("Unknown");
641 assert_eq!(result.len(), 0);
642 }
643 }
644
645 #[test]
646 fn test_find_groups_returns_correct_groups() {
647 let db = db_with_groups_and_entries();
648 let result = db.find_groups("mail");
649 assert_eq!(result.len(), 1);
650
651 let result = db.find_groups("Unknown");
652 assert_eq!(result.len(), 0);
653 }
654
655 #[test]
656 fn test_find_groups_mut_returns_correct_groups() {
657 let mut db = db_with_groups_and_entries();
658 {
659 let result = db.find_groups_mut("mail");
660 assert_eq!(result.len(), 1);
661 }
662 {
663 let result = db.find_groups_mut("Unknown");
664 assert_eq!(result.len(), 0);
665 }
666 }
667
668 #[test]
669 fn test_get_entry_returns_correct_entry() {
670 let entry = Entry::new();
671 let entry_uuid = entry.uuid;
672
673 let mut group = Group::new("Group");
674 group.add_entry(entry.clone());
675
676 let mut db = Database::new(&CompositeKey::from_password("test"));
677 assert_eq!(db.get_entry(entry_uuid), None);
678
679 db.root_group.add_group(group);
680 assert_eq!(db.get_entry(entry_uuid), Some(&entry));
681 }
682
683 #[test]
684 fn test_get_entry_mut_returns_correct_entry() {
685 let mut entry = Entry::new();
686 let entry_uuid = entry.uuid;
687
688 let mut group = Group::new("Group");
689 group.add_entry(entry.clone());
690
691 let mut db = Database::new(&CompositeKey::from_password("test"));
692 assert_eq!(db.get_entry_mut(entry_uuid), None);
693
694 db.root_group.add_group(group);
695 assert_eq!(db.get_entry_mut(entry_uuid), Some(&mut entry));
696 }
697
698 #[test]
699 fn test_get_group_returns_correct_group() {
700 let group = Group::new("Group");
701 let group_uuid = group.uuid;
702
703 let mut db = Database::new(&CompositeKey::from_password("test"));
704 assert_eq!(db.get_group(group_uuid), None);
705
706 db.root_group.add_group(group.clone());
707 assert_eq!(db.get_group(group_uuid), Some(&group));
708 }
709
710 #[test]
711 fn test_get_group_mut_returns_correct_group() {
712 let mut group = Group::new("Group");
713 let group_uuid = group.uuid;
714
715 let mut db = Database::new(&CompositeKey::from_password("test"));
716 assert_eq!(db.get_group_mut(group_uuid), None);
717
718 db.root_group.add_group(group.clone());
719 assert_eq!(db.get_group_mut(group_uuid), Some(&mut group));
720 }
721
722 fn db_with_groups_and_entries() -> Database {
723 let mut gmail = Entry::new();
724 gmail.set_title("Gmail");
725 gmail.set_username("guser");
726 gmail.set_password("gpass");
727 gmail.set_url("https://mail.google.com");
728
729 let mut protonmail = Entry::new();
730 protonmail.set_title("ProtonMail");
731 protonmail.set_username("puser");
732 protonmail.set_password("ppass");
733 protonmail.set_url("https://mail.protonmail.com");
734
735 let mut protonvpn = Entry::new();
736 protonvpn.set_title("ProtonVPN");
737 protonvpn.set_username("puser");
738 protonvpn.set_password("ppass");
739 protonvpn.set_url("https://prontvpn.com");
740
741 let mut email_group = Group::new("Email");
742 email_group.add_entry(gmail);
743 email_group.add_entry(protonmail);
744
745 let mut vpn_group = Group::new("VPN");
746 vpn_group.add_entry(protonvpn);
747
748 let mut db = Database::new(&CompositeKey::from_password("test"));
749 db.root_group.add_group(email_group);
750 db.root_group.add_group(vpn_group);
751 db
752 }
753}