1 /*
2 * Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 or (at your option)
7 * version 3 of the License.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "KeePass2XmlReader.h"
19
20 #include <QBuffer>
21 #include <QFile>
22
23 #include "core/Database.h"
24 #include "core/DatabaseIcons.h"
25 #include "core/Group.h"
26 #include "core/Metadata.h"
27 #include "core/Tools.h"
28 #include "format/KeePass2RandomStream.h"
29 #include "streams/QtIOCompressor"
30
31 typedef QPair<QString, QString> StringPair;
32
KeePass2XmlReader()33 KeePass2XmlReader::KeePass2XmlReader()
34 : m_randomStream(nullptr)
35 , m_db(nullptr)
36 , m_meta(nullptr)
37 , m_tmpParent(nullptr)
38 , m_error(false)
39 , m_strictMode(false)
40 {
41 }
42
setStrictMode(bool strictMode)43 void KeePass2XmlReader::setStrictMode(bool strictMode)
44 {
45 m_strictMode = strictMode;
46 }
47
readDatabase(QIODevice * device,Database * db,KeePass2RandomStream * randomStream)48 void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream)
49 {
50 m_error = false;
51 m_errorStr.clear();
52
53 m_xml.clear();
54 m_xml.setDevice(device);
55
56 m_db = db;
57 m_meta = m_db->metadata();
58 m_meta->setUpdateDatetime(false);
59
60 m_randomStream = randomStream;
61 m_headerHash.clear();
62
63 m_tmpParent = new Group();
64
65 bool rootGroupParsed = false;
66
67 if (!m_xml.error() && m_xml.readNextStartElement()) {
68 if (m_xml.name() == "KeePassFile") {
69 rootGroupParsed = parseKeePassFile();
70 }
71 }
72
73 if (!m_xml.error() && !rootGroupParsed) {
74 raiseError("No root group");
75 }
76
77 if (!m_xml.error()) {
78 if (!m_tmpParent->children().isEmpty()) {
79 qWarning("KeePass2XmlReader::readDatabase: found %d invalid group reference(s)",
80 m_tmpParent->children().size());
81 }
82
83 if (!m_tmpParent->entries().isEmpty()) {
84 qWarning("KeePass2XmlReader::readDatabase: found %d invalid entry reference(s)",
85 m_tmpParent->children().size());
86 }
87 }
88
89 const QSet<QString> poolKeys = m_binaryPool.keys().toSet();
90 const QSet<QString> entryKeys = m_binaryMap.keys().toSet();
91 const QSet<QString> unmappedKeys = entryKeys - poolKeys;
92 const QSet<QString> unusedKeys = poolKeys - entryKeys;
93
94 if (!unmappedKeys.isEmpty()) {
95 raiseError("Unmapped keys left.");
96 }
97
98 if (!m_xml.error()) {
99 for (const QString& key : unusedKeys) {
100 qWarning("KeePass2XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key));
101 }
102 }
103
104 QHash<QString, QPair<Entry*, QString> >::const_iterator i;
105 for (i = m_binaryMap.constBegin(); i != m_binaryMap.constEnd(); ++i) {
106 const QPair<Entry*, QString>& target = i.value();
107 target.first->attachments()->set(target.second, m_binaryPool[i.key()]);
108 }
109
110 m_meta->setUpdateDatetime(true);
111
112 QHash<Uuid, Group*>::const_iterator iGroup;
113 for (iGroup = m_groups.constBegin(); iGroup != m_groups.constEnd(); ++iGroup) {
114 iGroup.value()->setUpdateTimeinfo(true);
115 }
116
117 QHash<Uuid, Entry*>::const_iterator iEntry;
118 for (iEntry = m_entries.constBegin(); iEntry != m_entries.constEnd(); ++iEntry) {
119 iEntry.value()->setUpdateTimeinfo(true);
120
121 const QList<Entry*> historyItems = iEntry.value()->historyItems();
122 for (Entry* histEntry : historyItems) {
123 histEntry->setUpdateTimeinfo(true);
124 }
125 }
126
127 delete m_tmpParent;
128 }
129
readDatabase(QIODevice * device)130 Database* KeePass2XmlReader::readDatabase(QIODevice* device)
131 {
132 Database* db = new Database();
133 readDatabase(device, db);
134 return db;
135 }
136
readDatabase(const QString & filename)137 Database* KeePass2XmlReader::readDatabase(const QString& filename)
138 {
139 QFile file(filename);
140 file.open(QIODevice::ReadOnly);
141 return readDatabase(&file);
142 }
143
hasError()144 bool KeePass2XmlReader::hasError()
145 {
146 return m_error || m_xml.hasError();
147 }
148
errorString()149 QString KeePass2XmlReader::errorString()
150 {
151 if (m_error) {
152 return m_errorStr;
153 }
154 else if (m_xml.hasError()) {
155 return QString("XML error:\n%1\nLine %2, column %3")
156 .arg(m_xml.errorString())
157 .arg(m_xml.lineNumber())
158 .arg(m_xml.columnNumber());
159 }
160 else {
161 return QString();
162 }
163 }
164
raiseError(const QString & errorMessage)165 void KeePass2XmlReader::raiseError(const QString& errorMessage)
166 {
167 m_error = true;
168 m_errorStr = errorMessage;
169 }
170
headerHash()171 QByteArray KeePass2XmlReader::headerHash()
172 {
173 return m_headerHash;
174 }
175
parseKeePassFile()176 bool KeePass2XmlReader::parseKeePassFile()
177 {
178 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile");
179
180 bool rootElementFound = false;
181 bool rootParsedSuccesfully = false;
182
183 while (!m_xml.error() && m_xml.readNextStartElement()) {
184 if (m_xml.name() == "Meta") {
185 parseMeta();
186 }
187 else if (m_xml.name() == "Root") {
188 if (rootElementFound) {
189 rootParsedSuccesfully = false;
190 raiseError("Multiple root elements");
191 }
192 else {
193 rootParsedSuccesfully = parseRoot();
194 rootElementFound = true;
195 }
196 }
197 else {
198 skipCurrentElement();
199 }
200 }
201
202 return rootParsedSuccesfully;
203 }
204
parseMeta()205 void KeePass2XmlReader::parseMeta()
206 {
207 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Meta");
208
209 while (!m_xml.error() && m_xml.readNextStartElement()) {
210 if (m_xml.name() == "Generator") {
211 m_meta->setGenerator(readString());
212 }
213 else if (m_xml.name() == "HeaderHash") {
214 m_headerHash = readBinary();
215 }
216 else if (m_xml.name() == "DatabaseName") {
217 m_meta->setName(readString());
218 }
219 else if (m_xml.name() == "DatabaseNameChanged") {
220 m_meta->setNameChanged(readDateTime());
221 }
222 else if (m_xml.name() == "DatabaseDescription") {
223 m_meta->setDescription(readString());
224 }
225 else if (m_xml.name() == "DatabaseDescriptionChanged") {
226 m_meta->setDescriptionChanged(readDateTime());
227 }
228 else if (m_xml.name() == "DefaultUserName") {
229 m_meta->setDefaultUserName(readString());
230 }
231 else if (m_xml.name() == "DefaultUserNameChanged") {
232 m_meta->setDefaultUserNameChanged(readDateTime());
233 }
234 else if (m_xml.name() == "MaintenanceHistoryDays") {
235 m_meta->setMaintenanceHistoryDays(readNumber());
236 }
237 else if (m_xml.name() == "Color") {
238 m_meta->setColor(readColor());
239 }
240 else if (m_xml.name() == "MasterKeyChanged") {
241 m_meta->setMasterKeyChanged(readDateTime());
242 }
243 else if (m_xml.name() == "MasterKeyChangeRec") {
244 m_meta->setMasterKeyChangeRec(readNumber());
245 }
246 else if (m_xml.name() == "MasterKeyChangeForce") {
247 m_meta->setMasterKeyChangeForce(readNumber());
248 }
249 else if (m_xml.name() == "MemoryProtection") {
250 parseMemoryProtection();
251 }
252 else if (m_xml.name() == "CustomIcons") {
253 parseCustomIcons();
254 }
255 else if (m_xml.name() == "RecycleBinEnabled") {
256 m_meta->setRecycleBinEnabled(readBool());
257 }
258 else if (m_xml.name() == "RecycleBinUUID") {
259 m_meta->setRecycleBin(getGroup(readUuid()));
260 }
261 else if (m_xml.name() == "RecycleBinChanged") {
262 m_meta->setRecycleBinChanged(readDateTime());
263 }
264 else if (m_xml.name() == "EntryTemplatesGroup") {
265 m_meta->setEntryTemplatesGroup(getGroup(readUuid()));
266 }
267 else if (m_xml.name() == "EntryTemplatesGroupChanged") {
268 m_meta->setEntryTemplatesGroupChanged(readDateTime());
269 }
270 else if (m_xml.name() == "LastSelectedGroup") {
271 m_meta->setLastSelectedGroup(getGroup(readUuid()));
272 }
273 else if (m_xml.name() == "LastTopVisibleGroup") {
274 m_meta->setLastTopVisibleGroup(getGroup(readUuid()));
275 }
276 else if (m_xml.name() == "HistoryMaxItems") {
277 int value = readNumber();
278 if (value >= -1) {
279 m_meta->setHistoryMaxItems(value);
280 }
281 else {
282 raiseError("HistoryMaxItems invalid number");
283 }
284 }
285 else if (m_xml.name() == "HistoryMaxSize") {
286 int value = readNumber();
287 if (value >= -1) {
288 m_meta->setHistoryMaxSize(value);
289 }
290 else {
291 raiseError("HistoryMaxSize invalid number");
292 }
293 }
294 else if (m_xml.name() == "Binaries") {
295 parseBinaries();
296 }
297 else if (m_xml.name() == "CustomData") {
298 parseCustomData();
299 }
300 else {
301 skipCurrentElement();
302 }
303 }
304 }
305
parseMemoryProtection()306 void KeePass2XmlReader::parseMemoryProtection()
307 {
308 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "MemoryProtection");
309
310 while (!m_xml.error() && m_xml.readNextStartElement()) {
311 if (m_xml.name() == "ProtectTitle") {
312 m_meta->setProtectTitle(readBool());
313 }
314 else if (m_xml.name() == "ProtectUserName") {
315 m_meta->setProtectUsername(readBool());
316 }
317 else if (m_xml.name() == "ProtectPassword") {
318 m_meta->setProtectPassword(readBool());
319 }
320 else if (m_xml.name() == "ProtectURL") {
321 m_meta->setProtectUrl(readBool());
322 }
323 else if (m_xml.name() == "ProtectNotes") {
324 m_meta->setProtectNotes(readBool());
325 }
326 /*else if (m_xml.name() == "AutoEnableVisualHiding") {
327 m_meta->setAutoEnableVisualHiding(readBool());
328 }*/
329 else {
330 skipCurrentElement();
331 }
332 }
333 }
334
parseCustomIcons()335 void KeePass2XmlReader::parseCustomIcons()
336 {
337 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomIcons");
338
339 while (!m_xml.error() && m_xml.readNextStartElement()) {
340 if (m_xml.name() == "Icon") {
341 parseIcon();
342 }
343 else {
344 skipCurrentElement();
345 }
346 }
347 }
348
parseIcon()349 void KeePass2XmlReader::parseIcon()
350 {
351 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Icon");
352
353 Uuid uuid;
354 QImage icon;
355 bool uuidSet = false;
356 bool iconSet = false;
357
358 while (!m_xml.error() && m_xml.readNextStartElement()) {
359 if (m_xml.name() == "UUID") {
360 uuid = readUuid();
361 uuidSet = !uuid.isNull();
362 }
363 else if (m_xml.name() == "Data") {
364 icon.loadFromData(readBinary());
365 iconSet = true;
366 }
367 else {
368 skipCurrentElement();
369 }
370 }
371
372 if (uuidSet && iconSet) {
373 m_meta->addCustomIcon(uuid, icon);
374 }
375 else {
376 raiseError("Missing icon uuid or data");
377 }
378 }
379
parseBinaries()380 void KeePass2XmlReader::parseBinaries()
381 {
382 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries");
383
384 while (!m_xml.error() && m_xml.readNextStartElement()) {
385 if (m_xml.name() == "Binary") {
386 QXmlStreamAttributes attr = m_xml.attributes();
387
388 QString id = attr.value("ID").toString();
389
390 QByteArray data;
391 if (attr.value("Compressed").compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) {
392 data = readCompressedBinary();
393 }
394 else {
395 data = readBinary();
396 }
397
398 if (m_binaryPool.contains(id)) {
399 qWarning("KeePass2XmlReader::parseBinaries: overwriting binary item \"%s\"",
400 qPrintable(id));
401 }
402
403 m_binaryPool.insert(id, data);
404 }
405 else {
406 skipCurrentElement();
407 }
408 }
409 }
410
parseCustomData()411 void KeePass2XmlReader::parseCustomData()
412 {
413 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData");
414
415 while (!m_xml.error() && m_xml.readNextStartElement()) {
416 if (m_xml.name() == "Item") {
417 parseCustomDataItem();
418 }
419 else {
420 skipCurrentElement();
421 }
422 }
423 }
424
parseCustomDataItem()425 void KeePass2XmlReader::parseCustomDataItem()
426 {
427 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item");
428
429 QString key;
430 QString value;
431 bool keySet = false;
432 bool valueSet = false;
433
434 while (!m_xml.error() && m_xml.readNextStartElement()) {
435 if (m_xml.name() == "Key") {
436 key = readString();
437 keySet = true;
438 }
439 else if (m_xml.name() == "Value") {
440 value = readString();
441 valueSet = true;
442 }
443 else {
444 skipCurrentElement();
445 }
446 }
447
448 if (keySet && valueSet) {
449 m_meta->addCustomField(key, value);
450 }
451 else {
452 raiseError("Missing custom data key or value");
453 }
454 }
455
parseRoot()456 bool KeePass2XmlReader::parseRoot()
457 {
458 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Root");
459
460 bool groupElementFound = false;
461 bool groupParsedSuccesfully = false;
462
463 while (!m_xml.error() && m_xml.readNextStartElement()) {
464 if (m_xml.name() == "Group") {
465 if (groupElementFound) {
466 groupParsedSuccesfully = false;
467 raiseError("Multiple group elements");
468 continue;
469 }
470
471 Group* rootGroup = parseGroup();
472 if (rootGroup) {
473 Group* oldRoot = m_db->rootGroup();
474 m_db->setRootGroup(rootGroup);
475 delete oldRoot;
476 groupParsedSuccesfully = true;
477 }
478
479 groupElementFound = true;
480 }
481 else if (m_xml.name() == "DeletedObjects") {
482 parseDeletedObjects();
483 }
484 else {
485 skipCurrentElement();
486 }
487 }
488
489 return groupParsedSuccesfully;
490 }
491
parseGroup()492 Group* KeePass2XmlReader::parseGroup()
493 {
494 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group");
495
496 Group* group = new Group();
497 group->setUpdateTimeinfo(false);
498 QList<Group*> children;
499 QList<Entry*> entries;
500 while (!m_xml.error() && m_xml.readNextStartElement()) {
501 if (m_xml.name() == "UUID") {
502 Uuid uuid = readUuid();
503 if (uuid.isNull()) {
504 if (m_strictMode) {
505 raiseError("Null group uuid");
506 }
507 else {
508 group->setUuid(Uuid::random());
509 }
510 }
511 else {
512 group->setUuid(uuid);
513 }
514 }
515 else if (m_xml.name() == "Name") {
516 group->setName(readString());
517 }
518 else if (m_xml.name() == "Notes") {
519 group->setNotes(readString());
520 }
521 else if (m_xml.name() == "IconID") {
522 int iconId = readNumber();
523 if (iconId < 0) {
524 if (m_strictMode) {
525 raiseError("Invalid group icon number");
526 }
527 iconId = 0;
528 }
529 else {
530 if (iconId >= DatabaseIcons::IconCount) {
531 qWarning("KeePass2XmlReader::parseGroup: icon id \"%d\" not supported", iconId);
532 }
533 group->setIcon(iconId);
534 }
535 }
536 else if (m_xml.name() == "CustomIconUUID") {
537 Uuid uuid = readUuid();
538 if (!uuid.isNull()) {
539 group->setIcon(uuid);
540 }
541 }
542 else if (m_xml.name() == "Times") {
543 group->setTimeInfo(parseTimes());
544 }
545 else if (m_xml.name() == "IsExpanded") {
546 group->setExpanded(readBool());
547 }
548 else if (m_xml.name() == "DefaultAutoTypeSequence") {
549 group->setDefaultAutoTypeSequence(readString());
550 }
551 else if (m_xml.name() == "EnableAutoType") {
552 QString str = readString();
553
554 if (str.compare("null", Qt::CaseInsensitive) == 0) {
555 group->setAutoTypeEnabled(Group::Inherit);
556 }
557 else if (str.compare("true", Qt::CaseInsensitive) == 0) {
558 group->setAutoTypeEnabled(Group::Enable);
559 }
560 else if (str.compare("false", Qt::CaseInsensitive) == 0) {
561 group->setAutoTypeEnabled(Group::Disable);
562 }
563 else {
564 raiseError("Invalid EnableAutoType value");
565 }
566 }
567 else if (m_xml.name() == "EnableSearching") {
568 QString str = readString();
569
570 if (str.compare("null", Qt::CaseInsensitive) == 0) {
571 group->setSearchingEnabled(Group::Inherit);
572 }
573 else if (str.compare("true", Qt::CaseInsensitive) == 0) {
574 group->setSearchingEnabled(Group::Enable);
575 }
576 else if (str.compare("false", Qt::CaseInsensitive) == 0) {
577 group->setSearchingEnabled(Group::Disable);
578 }
579 else {
580 raiseError("Invalid EnableSearching value");
581 }
582 }
583 else if (m_xml.name() == "LastTopVisibleEntry") {
584 group->setLastTopVisibleEntry(getEntry(readUuid()));
585 }
586 else if (m_xml.name() == "Group") {
587 Group* newGroup = parseGroup();
588 if (newGroup) {
589 children.append(newGroup);
590 }
591 }
592 else if (m_xml.name() == "Entry") {
593 Entry* newEntry = parseEntry(false);
594 if (newEntry) {
595 entries.append(newEntry);
596 }
597 }
598 else {
599 skipCurrentElement();
600 }
601 }
602
603 if (group->uuid().isNull() && !m_strictMode) {
604 group->setUuid(Uuid::random());
605 }
606
607 if (!group->uuid().isNull()) {
608 Group* tmpGroup = group;
609 group = getGroup(tmpGroup->uuid());
610 group->copyDataFrom(tmpGroup);
611 group->setUpdateTimeinfo(false);
612 delete tmpGroup;
613 }
614 else if (!hasError()) {
615 raiseError("No group uuid found");
616 }
617
618 for (Group* child : asConst(children)) {
619 child->setParent(group);
620 }
621
622 for (Entry* entry : asConst(entries)) {
623 entry->setGroup(group);
624 }
625
626 return group;
627 }
628
parseDeletedObjects()629 void KeePass2XmlReader::parseDeletedObjects()
630 {
631 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObjects");
632
633 while (!m_xml.error() && m_xml.readNextStartElement()) {
634 if (m_xml.name() == "DeletedObject") {
635 parseDeletedObject();
636 }
637 else {
638 skipCurrentElement();
639 }
640 }
641 }
642
parseDeletedObject()643 void KeePass2XmlReader::parseDeletedObject()
644 {
645 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject");
646
647 DeletedObject delObj;
648
649 while (!m_xml.error() && m_xml.readNextStartElement()) {
650 if (m_xml.name() == "UUID") {
651 Uuid uuid = readUuid();
652 if (uuid.isNull()) {
653 if (m_strictMode) {
654 raiseError("Null DeleteObject uuid");
655 }
656 }
657 else {
658 delObj.uuid = uuid;
659 }
660 }
661 else if (m_xml.name() == "DeletionTime") {
662 delObj.deletionTime = readDateTime();
663 }
664 else {
665 skipCurrentElement();
666 }
667 }
668
669 if (!delObj.uuid.isNull() && !delObj.deletionTime.isNull()) {
670 m_db->addDeletedObject(delObj);
671 }
672 else if (m_strictMode) {
673 raiseError("Missing DeletedObject uuid or time");
674 }
675 }
676
parseEntry(bool history)677 Entry* KeePass2XmlReader::parseEntry(bool history)
678 {
679 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry");
680
681 Entry* entry = new Entry();
682 entry->setUpdateTimeinfo(false);
683 QList<Entry*> historyItems;
684 QList<StringPair> binaryRefs;
685
686 while (!m_xml.error() && m_xml.readNextStartElement()) {
687 if (m_xml.name() == "UUID") {
688 Uuid uuid = readUuid();
689 if (uuid.isNull()) {
690 if (m_strictMode) {
691 raiseError("Null entry uuid");
692 }
693 else {
694 entry->setUuid(Uuid::random());
695 }
696 }
697 else {
698 entry->setUuid(uuid);
699 }
700 }
701 else if (m_xml.name() == "IconID") {
702 int iconId = readNumber();
703 if (iconId < 0) {
704 if (m_strictMode) {
705 raiseError("Invalid entry icon number");
706 }
707 iconId = 0;
708 }
709 else {
710 entry->setIcon(iconId);
711 }
712 }
713 else if (m_xml.name() == "CustomIconUUID") {
714 Uuid uuid = readUuid();
715 if (!uuid.isNull()) {
716 entry->setIcon(uuid);
717 }
718 }
719 else if (m_xml.name() == "ForegroundColor") {
720 entry->setForegroundColor(readColor());
721 }
722 else if (m_xml.name() == "BackgroundColor") {
723 entry->setBackgroundColor(readColor());
724 }
725 else if (m_xml.name() == "OverrideURL") {
726 entry->setOverrideUrl(readString());
727 }
728 else if (m_xml.name() == "Tags") {
729 entry->setTags(readString());
730 }
731 else if (m_xml.name() == "Times") {
732 entry->setTimeInfo(parseTimes());
733 }
734 else if (m_xml.name() == "String") {
735 parseEntryString(entry);
736 }
737 else if (m_xml.name() == "Binary") {
738 QPair<QString, QString> ref = parseEntryBinary(entry);
739 if (!ref.first.isNull() && !ref.second.isNull()) {
740 binaryRefs.append(ref);
741 }
742 }
743 else if (m_xml.name() == "AutoType") {
744 parseAutoType(entry);
745 }
746 else if (m_xml.name() == "History") {
747 if (history) {
748 raiseError("History element in history entry");
749 }
750 else {
751 historyItems = parseEntryHistory();
752 }
753 }
754 else {
755 skipCurrentElement();
756 }
757 }
758
759 if (entry->uuid().isNull() && !m_strictMode) {
760 entry->setUuid(Uuid::random());
761 }
762
763 if (!entry->uuid().isNull()) {
764 if (history) {
765 entry->setUpdateTimeinfo(false);
766 }
767 else {
768 Entry* tmpEntry = entry;
769
770 entry = getEntry(tmpEntry->uuid());
771 entry->copyDataFrom(tmpEntry);
772 entry->setUpdateTimeinfo(false);
773
774 delete tmpEntry;
775 }
776 }
777 else if (!hasError()) {
778 raiseError("No entry uuid found");
779 }
780
781 for (Entry* historyItem : asConst(historyItems)) {
782 if (historyItem->uuid() != entry->uuid()) {
783 if (m_strictMode) {
784 raiseError("History element with different uuid");
785 } else {
786 historyItem->setUuid(entry->uuid());
787 }
788 }
789 entry->addHistoryItem(historyItem);
790 }
791
792 for (const StringPair& ref : asConst(binaryRefs)) {
793 m_binaryMap.insertMulti(ref.first, qMakePair(entry, ref.second));
794 }
795
796 return entry;
797 }
798
parseEntryString(Entry * entry)799 void KeePass2XmlReader::parseEntryString(Entry* entry)
800 {
801 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "String");
802
803 QString key;
804 QString value;
805 bool protect = false;
806 bool keySet = false;
807 bool valueSet = false;
808
809 while (!m_xml.error() && m_xml.readNextStartElement()) {
810 if (m_xml.name() == "Key") {
811 key = readString();
812 keySet = true;
813 }
814 else if (m_xml.name() == "Value") {
815 QXmlStreamAttributes attr = m_xml.attributes();
816 value = readString();
817
818 bool isProtected = attr.value("Protected") == "True";
819 bool protectInMemory = attr.value("ProtectInMemory") == "True";
820
821 if (isProtected && !value.isEmpty()) {
822 if (m_randomStream) {
823 QByteArray ciphertext = QByteArray::fromBase64(value.toLatin1());
824 bool ok;
825 QByteArray plaintext = m_randomStream->process(ciphertext, &ok);
826 if (!ok) {
827 value.clear();
828 raiseError(m_randomStream->errorString());
829 }
830 else {
831 value = QString::fromUtf8(plaintext);
832 }
833 }
834 else {
835 raiseError("Unable to decrypt entry string");
836 }
837 }
838
839 protect = isProtected || protectInMemory;
840 valueSet = true;
841 }
842 else {
843 skipCurrentElement();
844 }
845 }
846
847 if (keySet && valueSet) {
848 // the default attributes are always there so additionally check if it's empty
849 if (entry->attributes()->hasKey(key) && !entry->attributes()->value(key).isEmpty()) {
850 raiseError("Duplicate custom attribute found");
851 }
852 else {
853 entry->attributes()->set(key, value, protect);
854 }
855 }
856 else {
857 raiseError("Entry string key or value missing");
858 }
859 }
860
parseEntryBinary(Entry * entry)861 QPair<QString, QString> KeePass2XmlReader::parseEntryBinary(Entry* entry)
862 {
863 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binary");
864
865 QPair<QString, QString> poolRef;
866
867 QString key;
868 QByteArray value;
869 bool keySet = false;
870 bool valueSet = false;
871
872 while (!m_xml.error() && m_xml.readNextStartElement()) {
873 if (m_xml.name() == "Key") {
874 key = readString();
875 keySet = true;
876 }
877 else if (m_xml.name() == "Value") {
878 QXmlStreamAttributes attr = m_xml.attributes();
879
880 if (attr.hasAttribute("Ref")) {
881 poolRef = qMakePair(attr.value("Ref").toString(), key);
882 m_xml.skipCurrentElement();
883 }
884 else {
885 // format compatibility
886 value = readBinary();
887 bool isProtected = attr.hasAttribute("Protected")
888 && (attr.value("Protected") == "True");
889
890 if (isProtected && !value.isEmpty()) {
891 if (!m_randomStream->processInPlace(value)) {
892 raiseError(m_randomStream->errorString());
893 }
894 }
895 }
896
897 valueSet = true;
898 }
899 else {
900 skipCurrentElement();
901 }
902 }
903
904 if (keySet && valueSet) {
905 if (entry->attachments()->hasKey(key)) {
906 raiseError("Duplicate attachment found");
907 }
908 else {
909 entry->attachments()->set(key, value);
910 }
911 }
912 else {
913 raiseError("Entry binary key or value missing");
914 }
915
916 return poolRef;
917 }
918
parseAutoType(Entry * entry)919 void KeePass2XmlReader::parseAutoType(Entry* entry)
920 {
921 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "AutoType");
922
923 while (!m_xml.error() && m_xml.readNextStartElement()) {
924 if (m_xml.name() == "Enabled") {
925 entry->setAutoTypeEnabled(readBool());
926 }
927 else if (m_xml.name() == "DataTransferObfuscation") {
928 entry->setAutoTypeObfuscation(readNumber());
929 }
930 else if (m_xml.name() == "DefaultSequence") {
931 entry->setDefaultAutoTypeSequence(readString());
932 }
933 else if (m_xml.name() == "Association") {
934 parseAutoTypeAssoc(entry);
935 }
936 else {
937 skipCurrentElement();
938 }
939 }
940 }
941
parseAutoTypeAssoc(Entry * entry)942 void KeePass2XmlReader::parseAutoTypeAssoc(Entry* entry)
943 {
944 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Association");
945
946 AutoTypeAssociations::Association assoc;
947 bool windowSet = false;
948 bool sequenceSet = false;
949
950 while (!m_xml.error() && m_xml.readNextStartElement()) {
951 if (m_xml.name() == "Window") {
952 assoc.window = readString();
953 windowSet = true;
954 }
955 else if (m_xml.name() == "KeystrokeSequence") {
956 assoc.sequence = readString();
957 sequenceSet = true;
958 }
959 else {
960 skipCurrentElement();
961 }
962 }
963
964 if (windowSet && sequenceSet) {
965 entry->autoTypeAssociations()->add(assoc);
966 }
967 else {
968 raiseError("Auto-type association window or sequence missing");
969 }
970 }
971
parseEntryHistory()972 QList<Entry*> KeePass2XmlReader::parseEntryHistory()
973 {
974 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "History");
975
976 QList<Entry*> historyItems;
977
978 while (!m_xml.error() && m_xml.readNextStartElement()) {
979 if (m_xml.name() == "Entry") {
980 historyItems.append(parseEntry(true));
981 }
982 else {
983 skipCurrentElement();
984 }
985 }
986
987 return historyItems;
988 }
989
parseTimes()990 TimeInfo KeePass2XmlReader::parseTimes()
991 {
992 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times");
993
994 TimeInfo timeInfo;
995 while (!m_xml.error() && m_xml.readNextStartElement()) {
996 if (m_xml.name() == "LastModificationTime") {
997 timeInfo.setLastModificationTime(readDateTime());
998 }
999 else if (m_xml.name() == "CreationTime") {
1000 timeInfo.setCreationTime(readDateTime());
1001 }
1002 else if (m_xml.name() == "LastAccessTime") {
1003 timeInfo.setLastAccessTime(readDateTime());
1004 }
1005 else if (m_xml.name() == "ExpiryTime") {
1006 timeInfo.setExpiryTime(readDateTime());
1007 }
1008 else if (m_xml.name() == "Expires") {
1009 timeInfo.setExpires(readBool());
1010 }
1011 else if (m_xml.name() == "UsageCount") {
1012 timeInfo.setUsageCount(readNumber());
1013 }
1014 else if (m_xml.name() == "LocationChanged") {
1015 timeInfo.setLocationChanged(readDateTime());
1016 }
1017 else {
1018 skipCurrentElement();
1019 }
1020 }
1021
1022 return timeInfo;
1023 }
1024
readString()1025 QString KeePass2XmlReader::readString()
1026 {
1027 return m_xml.readElementText();
1028 }
1029
readBool()1030 bool KeePass2XmlReader::readBool()
1031 {
1032 QString str = readString();
1033
1034 if (str.compare("True", Qt::CaseInsensitive) == 0) {
1035 return true;
1036 }
1037 else if (str.compare("False", Qt::CaseInsensitive) == 0) {
1038 return false;
1039 }
1040 else {
1041 raiseError("Invalid bool value");
1042 return false;
1043 }
1044 }
1045
readDateTime()1046 QDateTime KeePass2XmlReader::readDateTime()
1047 {
1048 QString str = readString();
1049 QDateTime dt = QDateTime::fromString(str, Qt::ISODate);
1050
1051 if (!dt.isValid()) {
1052 if (m_strictMode) {
1053 raiseError("Invalid date time value");
1054 }
1055 else {
1056 dt = QDateTime::currentDateTimeUtc();
1057 }
1058 }
1059
1060 return dt;
1061 }
1062
readColor()1063 QColor KeePass2XmlReader::readColor()
1064 {
1065 QString colorStr = readString();
1066
1067 if (colorStr.isEmpty()) {
1068 return QColor();
1069 }
1070
1071 if (colorStr.length() != 7 || colorStr[0] != '#') {
1072 if (m_strictMode) {
1073 raiseError("Invalid color value");
1074 }
1075 return QColor();
1076 }
1077
1078 QColor color;
1079 for (int i = 0; i <= 2; i++) {
1080 QString rgbPartStr = colorStr.mid(1 + 2*i, 2);
1081 bool ok;
1082 int rgbPart = rgbPartStr.toInt(&ok, 16);
1083 if (!ok || rgbPart > 255) {
1084 if (m_strictMode) {
1085 raiseError("Invalid color rgb part");
1086 }
1087 return QColor();
1088 }
1089
1090 if (i == 0) {
1091 color.setRed(rgbPart);
1092 }
1093 else if (i == 1) {
1094 color.setGreen(rgbPart);
1095 }
1096 else {
1097 color.setBlue(rgbPart);
1098 }
1099 }
1100
1101 return color;
1102 }
1103
readNumber()1104 int KeePass2XmlReader::readNumber()
1105 {
1106 bool ok;
1107 int result = readString().toInt(&ok);
1108 if (!ok) {
1109 raiseError("Invalid number value");
1110 }
1111 return result;
1112 }
1113
readUuid()1114 Uuid KeePass2XmlReader::readUuid()
1115 {
1116 QByteArray uuidBin = readBinary();
1117 if (uuidBin.isEmpty()) {
1118 return Uuid();
1119 }
1120 else if (uuidBin.length() != Uuid::Length) {
1121 if (m_strictMode) {
1122 raiseError("Invalid uuid value");
1123 }
1124 return Uuid();
1125 }
1126 else {
1127 return Uuid(uuidBin);
1128 }
1129 }
1130
readBinary()1131 QByteArray KeePass2XmlReader::readBinary()
1132 {
1133 return QByteArray::fromBase64(readString().toLatin1());
1134 }
1135
readCompressedBinary()1136 QByteArray KeePass2XmlReader::readCompressedBinary()
1137 {
1138 QByteArray rawData = readBinary();
1139
1140 QBuffer buffer(&rawData);
1141 buffer.open(QIODevice::ReadOnly);
1142
1143 QtIOCompressor compressor(&buffer);
1144 compressor.setStreamFormat(QtIOCompressor::GzipFormat);
1145 compressor.open(QIODevice::ReadOnly);
1146
1147 QByteArray result;
1148 if (!Tools::readAllFromDevice(&compressor, result)) {
1149 raiseError("Unable to decompress binary");
1150 }
1151 return result;
1152 }
1153
getGroup(const Uuid & uuid)1154 Group* KeePass2XmlReader::getGroup(const Uuid& uuid)
1155 {
1156 if (uuid.isNull()) {
1157 return nullptr;
1158 }
1159
1160 if (m_groups.contains(uuid)) {
1161 return m_groups.value(uuid);
1162 }
1163 else {
1164 Group* group = new Group();
1165 group->setUpdateTimeinfo(false);
1166 group->setUuid(uuid);
1167 group->setParent(m_tmpParent);
1168 m_groups.insert(uuid, group);
1169 return group;
1170 }
1171 }
1172
getEntry(const Uuid & uuid)1173 Entry* KeePass2XmlReader::getEntry(const Uuid& uuid)
1174 {
1175 if (uuid.isNull()) {
1176 return nullptr;
1177 }
1178
1179 if (m_entries.contains(uuid)) {
1180 return m_entries.value(uuid);
1181 }
1182 else {
1183 Entry* entry = new Entry();
1184 entry->setUpdateTimeinfo(false);
1185 entry->setUuid(uuid);
1186 entry->setGroup(m_tmpParent);
1187 m_entries.insert(uuid, entry);
1188 return entry;
1189 }
1190 }
1191
skipCurrentElement()1192 void KeePass2XmlReader::skipCurrentElement()
1193 {
1194 qWarning("KeePass2XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString()));
1195 m_xml.skipCurrentElement();
1196 }
1197