mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-07-26 08:15:32 -04:00
Fix KDBX4 reader/writer attachment mapping error
Write duplicate attachments to the binary inner header only once and skip duplicate entries when reading a KDBX4 file. This fixes a an attachment mapping problem when an attachment appears more than once in a database (which occurs frequently when editing attachment entries and history is turned on)
This commit is contained in:
parent
ee03d44053
commit
c18d6b5ae5
5 changed files with 40 additions and 11 deletions
|
@ -22,7 +22,6 @@
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
#include "core/Endian.h"
|
#include "core/Endian.h"
|
||||||
#include "crypto/CryptoHash.h"
|
#include "crypto/CryptoHash.h"
|
||||||
#include "crypto/kdf/AesKdf.h"
|
|
||||||
#include "format/KeePass2RandomStream.h"
|
#include "format/KeePass2RandomStream.h"
|
||||||
#include "format/KdbxXmlReader.h"
|
#include "format/KdbxXmlReader.h"
|
||||||
#include "streams/HmacBlockStream.h"
|
#include "streams/HmacBlockStream.h"
|
||||||
|
@ -34,7 +33,7 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device, const QByteArray& hea
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
|
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
|
||||||
|
|
||||||
m_binaryPool.clear();
|
m_binaryPoolInverse.clear();
|
||||||
|
|
||||||
if (hasError()) {
|
if (hasError()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -135,7 +134,7 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device, const QByteArray& hea
|
||||||
|
|
||||||
Q_ASSERT(xmlDevice);
|
Q_ASSERT(xmlDevice);
|
||||||
|
|
||||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, m_binaryPool);
|
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, binaryPool());
|
||||||
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
|
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
|
||||||
|
|
||||||
if (xmlReader.hasError()) {
|
if (xmlReader.hasError()) {
|
||||||
|
@ -273,14 +272,20 @@ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device)
|
||||||
setProtectedStreamKey(fieldData);
|
setProtectedStreamKey(fieldData);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeePass2::InnerHeaderFieldID::Binary:
|
case KeePass2::InnerHeaderFieldID::Binary: {
|
||||||
if (fieldLen < 1) {
|
if (fieldLen < 1) {
|
||||||
raiseError(tr("Invalid inner header binary size"));
|
raiseError(tr("Invalid inner header binary size"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_binaryPool.insert(QString::number(m_binaryPool.size()), fieldData.mid(1));
|
auto data = fieldData.mid(1);
|
||||||
|
if (m_binaryPoolInverse.contains(data)) {
|
||||||
|
qWarning("Skipping duplicate binary record");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
m_binaryPoolInverse.insert(data, QString::number(m_binaryPoolInverse.size()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -417,7 +422,22 @@ QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device)
|
||||||
return vm;
|
return vm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mapping from attachment keys to binary data
|
||||||
|
*/
|
||||||
QHash<QString, QByteArray> Kdbx4Reader::binaryPool() const
|
QHash<QString, QByteArray> Kdbx4Reader::binaryPool() const
|
||||||
{
|
{
|
||||||
return m_binaryPool;
|
QHash<QString, QByteArray> binaryPool;
|
||||||
|
for (auto it = m_binaryPoolInverse.cbegin(); it != m_binaryPoolInverse.cend(); ++it) {
|
||||||
|
binaryPool.insert(it.value(), it.key());
|
||||||
|
}
|
||||||
|
return binaryPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mapping from binary data to attachment keys
|
||||||
|
*/
|
||||||
|
QHash<QByteArray, QString> Kdbx4Reader::binaryPoolInverse() const
|
||||||
|
{
|
||||||
|
return m_binaryPoolInverse;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ Q_DECLARE_TR_FUNCTIONS(Kdbx4Reader)
|
||||||
public:
|
public:
|
||||||
Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
|
Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
|
||||||
const CompositeKey& key, bool keepDatabase) override;
|
const CompositeKey& key, bool keepDatabase) override;
|
||||||
|
QHash<QByteArray, QString> binaryPoolInverse() const;
|
||||||
QHash<QString, QByteArray> binaryPool() const;
|
QHash<QString, QByteArray> binaryPool() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -41,7 +42,7 @@ private:
|
||||||
bool readInnerHeaderField(QIODevice* device);
|
bool readInnerHeaderField(QIODevice* device);
|
||||||
QVariantMap readVariantMap(QIODevice* device);
|
QVariantMap readVariantMap(QIODevice* device);
|
||||||
|
|
||||||
QHash<QString, QByteArray> m_binaryPool;
|
QHash<QByteArray, QString> m_binaryPoolInverse;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_KDBX4READER_H
|
#endif // KEEPASSX_KDBX4READER_H
|
||||||
|
|
|
@ -211,12 +211,20 @@ bool Kdbx4Writer::writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeader
|
||||||
void Kdbx4Writer::writeAttachments(QIODevice* device, Database* db)
|
void Kdbx4Writer::writeAttachments(QIODevice* device, Database* db)
|
||||||
{
|
{
|
||||||
const QList<Entry*> allEntries = db->rootGroup()->entriesRecursive(true);
|
const QList<Entry*> allEntries = db->rootGroup()->entriesRecursive(true);
|
||||||
|
QSet<QByteArray> writtenAttachments;
|
||||||
|
|
||||||
for (Entry* entry : allEntries) {
|
for (Entry* entry : allEntries) {
|
||||||
const QList<QString> attachmentKeys = entry->attachments()->keys();
|
const QList<QString> attachmentKeys = entry->attachments()->keys();
|
||||||
for (const QString& key : attachmentKeys) {
|
for (const QString& key : attachmentKeys) {
|
||||||
QByteArray data = entry->attachments()->value(key);
|
QByteArray data("\x01");
|
||||||
data.prepend("\x01");
|
data.append(entry->attachments()->value(key));
|
||||||
|
|
||||||
|
if (writtenAttachments.contains(data)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
writeInnerHeaderField(device, KeePass2::InnerHeaderFieldID::Binary, data);
|
writeInnerHeaderField(device, KeePass2::InnerHeaderFieldID::Binary, data);
|
||||||
|
writtenAttachments.insert(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ KdbxXmlReader::KdbxXmlReader(quint32 version)
|
||||||
* @param version KDBX version
|
* @param version KDBX version
|
||||||
* @param binaryPool binary pool
|
* @param binaryPool binary pool
|
||||||
*/
|
*/
|
||||||
KdbxXmlReader::KdbxXmlReader(quint32 version, QHash<QString, QByteArray>& binaryPool)
|
KdbxXmlReader::KdbxXmlReader(quint32 version, const QHash<QString, QByteArray>& binaryPool)
|
||||||
: m_kdbxVersion(version)
|
: m_kdbxVersion(version)
|
||||||
, m_binaryPool(binaryPool)
|
, m_binaryPool(binaryPool)
|
||||||
{
|
{
|
||||||
|
|
|
@ -42,7 +42,7 @@ Q_DECLARE_TR_FUNCTIONS(KdbxXmlReader)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit KdbxXmlReader(quint32 version);
|
explicit KdbxXmlReader(quint32 version);
|
||||||
explicit KdbxXmlReader(quint32 version, QHash<QString, QByteArray>& binaryPool);
|
explicit KdbxXmlReader(quint32 version, const QHash<QString, QByteArray>& binaryPool);
|
||||||
virtual ~KdbxXmlReader() = default;
|
virtual ~KdbxXmlReader() = default;
|
||||||
|
|
||||||
virtual Database* readDatabase(const QString& filename);
|
virtual Database* readDatabase(const QString& filename);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue