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