mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Implement support for parsing groups and entries from KeePass 1 databases.
Still missing: - Key files. - Twofish encryption. Refs #2
This commit is contained in:
parent
6234065898
commit
79b15e2ac6
@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
#include "core/Endian.h"
|
#include "core/Endian.h"
|
||||||
|
#include "core/Entry.h"
|
||||||
|
#include "core/Group.h"
|
||||||
#include "crypto/CryptoHash.h"
|
#include "crypto/CryptoHash.h"
|
||||||
#include "format/KeePass1.h"
|
#include "format/KeePass1.h"
|
||||||
#include "keys/CompositeKey.h"
|
#include "keys/CompositeKey.h"
|
||||||
@ -50,7 +52,9 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
|||||||
const QByteArray& keyfileData)
|
const QByteArray& keyfileData)
|
||||||
{
|
{
|
||||||
QScopedPointer<Database> db(new Database());
|
QScopedPointer<Database> db(new Database());
|
||||||
|
QScopedPointer<Group> tmpParent(new Group());
|
||||||
m_db = db.data();
|
m_db = db.data();
|
||||||
|
m_tmpParent = tmpParent.data();
|
||||||
m_device = device;
|
m_device = device;
|
||||||
m_error = false;
|
m_error = false;
|
||||||
m_errorStr = QString();
|
m_errorStr = QString();
|
||||||
@ -142,6 +146,55 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<Group*> groups;
|
||||||
|
for (quint32 i = 0; i < numGroups; i++) {
|
||||||
|
Group* group = readGroup(cipherStream.data());
|
||||||
|
if (!group) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
groups.append(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<Entry*> entries;
|
||||||
|
for (quint32 i = 0; i < numEntries; i++) {
|
||||||
|
Entry* entry = readEntry(cipherStream.data());
|
||||||
|
if (!entry) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
entries.append(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!constructGroupTree(groups)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_FOREACH (Entry* entry, entries) {
|
||||||
|
if (isMetaStream(entry)) {
|
||||||
|
if (!parseMetaStream(entry)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_ASSERT(m_tmpParent->children().isEmpty());
|
||||||
|
|
||||||
|
Q_FOREACH (Entry* entry, m_tmpParent->entries()) {
|
||||||
|
qWarning("Orphaned entry found, assigning to root group.");
|
||||||
|
entry->setGroup(m_db->rootGroup());
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_FOREACH (Group* group, groups) {
|
||||||
|
group->setUpdateTimeinfo(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_FOREACH (Entry* entry, entries) {
|
||||||
|
entry->setUpdateTimeinfo(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return db.take();
|
return db.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,7 +324,6 @@ bool KeePass1Reader::verifyKey(SymmetricCipherStream* cipherStream)
|
|||||||
if (readResult != buffer.size()) {
|
if (readResult != buffer.size()) {
|
||||||
buffer.resize(readResult);
|
buffer.resize(readResult);
|
||||||
}
|
}
|
||||||
qDebug("read %d", buffer.size());
|
|
||||||
contentHash.addData(buffer);
|
contentHash.addData(buffer);
|
||||||
}
|
}
|
||||||
} while (readResult == buffer.size());
|
} while (readResult == buffer.size());
|
||||||
@ -279,12 +331,350 @@ bool KeePass1Reader::verifyKey(SymmetricCipherStream* cipherStream)
|
|||||||
return contentHash.result() == m_contentHashHeader;
|
return contentHash.result() == m_contentHashHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Group* KeePass1Reader::readGroup(QIODevice* cipherStream)
|
||||||
|
{
|
||||||
|
QScopedPointer<Group> group(new Group());
|
||||||
|
group->setUpdateTimeinfo(false);
|
||||||
|
group->setParent(m_tmpParent);
|
||||||
|
|
||||||
|
TimeInfo timeInfo;
|
||||||
|
// TODO: make sure these are initalized
|
||||||
|
quint32 groupId;
|
||||||
|
quint32 groupLevel;
|
||||||
|
bool ok;
|
||||||
|
bool reachedEnd = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
quint16 fieldType = Endian::readUInt16(cipherStream, KeePass1::BYTEORDER, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fieldSize = static_cast<int>(Endian::readUInt32(cipherStream, KeePass1::BYTEORDER, &ok));
|
||||||
|
if (!ok) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray fieldData = cipherStream->read(fieldSize);
|
||||||
|
if (fieldData.size() != fieldSize) {
|
||||||
|
// TODO error
|
||||||
|
Q_ASSERT(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (fieldType) {
|
||||||
|
case 0x0000:
|
||||||
|
// ignore field
|
||||||
|
break;
|
||||||
|
case 0x0001:
|
||||||
|
if (fieldSize != 4) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
groupId = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
|
||||||
|
break;
|
||||||
|
case 0x0002:
|
||||||
|
group->setName(QString::fromUtf8(fieldData.constData()));
|
||||||
|
break;
|
||||||
|
case 0x0003:
|
||||||
|
{
|
||||||
|
if (fieldSize != 5) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QDateTime dateTime = dateFromPackedStruct(fieldData);
|
||||||
|
if (dateTime.isValid()) {
|
||||||
|
timeInfo.setCreationTime(dateTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0004:
|
||||||
|
{
|
||||||
|
if (fieldSize != 5) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QDateTime dateTime = dateFromPackedStruct(fieldData);
|
||||||
|
if (dateTime.isValid()) {
|
||||||
|
timeInfo.setLastModificationTime(dateTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0005:
|
||||||
|
{
|
||||||
|
if (fieldSize != 5) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QDateTime dateTime = dateFromPackedStruct(fieldData);
|
||||||
|
if (dateTime.isValid()) {
|
||||||
|
timeInfo.setLastAccessTime(dateTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0006:
|
||||||
|
{
|
||||||
|
if (fieldSize != 5) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QDateTime dateTime = dateFromPackedStruct(fieldData);
|
||||||
|
if (dateTime.isValid()) {
|
||||||
|
timeInfo.setExpires(true);
|
||||||
|
timeInfo.setExpiryTime(dateTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0007:
|
||||||
|
{
|
||||||
|
if (fieldSize != 4) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
quint32 iconNumber = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
|
||||||
|
group->setIcon(iconNumber);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0008:
|
||||||
|
{
|
||||||
|
if (fieldSize != 2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
groupLevel = Endian::bytesToUInt16(fieldData, KeePass1::BYTEORDER);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0009:
|
||||||
|
// flags, ignore field
|
||||||
|
break;
|
||||||
|
case 0xFFFF:
|
||||||
|
reachedEnd = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// invalid field
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} while (!reachedEnd);
|
||||||
|
|
||||||
|
group->setUuid(Uuid::random());
|
||||||
|
group->setTimeInfo(timeInfo);
|
||||||
|
m_groupIds.insert(groupId, group.data());
|
||||||
|
m_groupLevels.insert(group.data(), groupLevel);
|
||||||
|
|
||||||
|
return group.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry* KeePass1Reader::readEntry(QIODevice* cipherStream)
|
||||||
|
{
|
||||||
|
QScopedPointer<Entry> entry(new Entry());
|
||||||
|
entry->setUpdateTimeinfo(false);
|
||||||
|
entry->setGroup(m_tmpParent);
|
||||||
|
|
||||||
|
TimeInfo timeInfo;
|
||||||
|
QString binaryName;
|
||||||
|
bool ok;
|
||||||
|
bool reachedEnd = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
quint16 fieldType = Endian::readUInt16(cipherStream, KeePass1::BYTEORDER, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fieldSize = static_cast<int>(Endian::readUInt32(cipherStream, KeePass1::BYTEORDER, &ok));
|
||||||
|
if (!ok) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray fieldData = cipherStream->read(fieldSize);
|
||||||
|
if (fieldData.size() != fieldSize) {
|
||||||
|
// TODO error
|
||||||
|
Q_ASSERT(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (fieldType) {
|
||||||
|
case 0x0000:
|
||||||
|
// ignore field
|
||||||
|
break;
|
||||||
|
case 0x0001:
|
||||||
|
if (fieldSize != 16) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
m_entryUuids.insert(fieldData, entry.data());
|
||||||
|
break;
|
||||||
|
case 0x0002:
|
||||||
|
{
|
||||||
|
if (fieldSize != 4) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
quint32 groupId = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
|
||||||
|
entry->setGroup(m_groupIds.value(groupId));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0003:
|
||||||
|
{
|
||||||
|
if (fieldSize != 4) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
quint32 iconNumber = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
|
||||||
|
entry->setIcon(iconNumber);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0004:
|
||||||
|
entry->setTitle(QString::fromUtf8(fieldData.constData()));
|
||||||
|
break;
|
||||||
|
case 0x0005:
|
||||||
|
entry->setUrl(QString::fromUtf8(fieldData.constData()));
|
||||||
|
break;
|
||||||
|
case 0x0006:
|
||||||
|
entry->setUsername(QString::fromUtf8(fieldData.constData()));
|
||||||
|
break;
|
||||||
|
case 0x0007:
|
||||||
|
entry->setPassword(QString::fromUtf8(fieldData.constData()));
|
||||||
|
break;
|
||||||
|
case 0x0008:
|
||||||
|
entry->setNotes(QString::fromUtf8(fieldData.constData()));
|
||||||
|
break;
|
||||||
|
case 0x0009:
|
||||||
|
{
|
||||||
|
if (fieldSize != 5) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QDateTime dateTime = dateFromPackedStruct(fieldData);
|
||||||
|
if (dateTime.isValid()) {
|
||||||
|
timeInfo.setCreationTime(dateTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x000A:
|
||||||
|
{
|
||||||
|
if (fieldSize != 5) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QDateTime dateTime = dateFromPackedStruct(fieldData);
|
||||||
|
if (dateTime.isValid()) {
|
||||||
|
timeInfo.setLastModificationTime(dateTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x000B:
|
||||||
|
{
|
||||||
|
if (fieldSize != 5) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QDateTime dateTime = dateFromPackedStruct(fieldData);
|
||||||
|
if (dateTime.isValid()) {
|
||||||
|
timeInfo.setLastAccessTime(dateTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x000C:
|
||||||
|
{
|
||||||
|
if (fieldSize != 5) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QDateTime dateTime = dateFromPackedStruct(fieldData);
|
||||||
|
if (dateTime.isValid()) {
|
||||||
|
timeInfo.setExpires(true);
|
||||||
|
timeInfo.setExpiryTime(dateTime);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x000D:
|
||||||
|
binaryName = QString::fromUtf8(fieldData.constData());
|
||||||
|
break;
|
||||||
|
case 0x000E:
|
||||||
|
if (fieldSize != 0) {
|
||||||
|
entry->attachments()->set(binaryName, fieldData);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0xFFFF:
|
||||||
|
reachedEnd = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// invalid field
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} while (!reachedEnd);
|
||||||
|
|
||||||
|
entry->setTimeInfo(timeInfo);
|
||||||
|
|
||||||
|
return entry.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KeePass1Reader::constructGroupTree(const QList<Group*> groups)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < groups.size(); i++) {
|
||||||
|
quint32 level = m_groupLevels.value(groups[i]);
|
||||||
|
|
||||||
|
if (level == 0) {
|
||||||
|
groups[i]->setParent(m_db->rootGroup());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int j = (i - 1); j >= 0; j--) {
|
||||||
|
if (m_groupLevels.value(groups[j]) < level) {
|
||||||
|
if ((m_groupLevels.value(groups[j]) - level) != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
groups[i]->setParent(groups[j]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groups[i]->parent() == m_tmpParent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KeePass1Reader::parseMetaStream(const Entry* entry)
|
||||||
|
{
|
||||||
|
// TODO: implement
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void KeePass1Reader::raiseError(const QString& str)
|
void KeePass1Reader::raiseError(const QString& str)
|
||||||
{
|
{
|
||||||
m_error = true;
|
m_error = true;
|
||||||
m_errorStr = str;
|
m_errorStr = str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDateTime KeePass1Reader::dateFromPackedStruct(const QByteArray& data)
|
||||||
|
{
|
||||||
|
Q_ASSERT(data.size() == 5);
|
||||||
|
|
||||||
|
quint32 dw1 = static_cast<uchar>(data.at(0));
|
||||||
|
quint32 dw2 = static_cast<uchar>(data.at(1));
|
||||||
|
quint32 dw3 = static_cast<uchar>(data.at(2));
|
||||||
|
quint32 dw4 = static_cast<uchar>(data.at(3));
|
||||||
|
quint32 dw5 = static_cast<uchar>(data.at(4));
|
||||||
|
|
||||||
|
int y = (dw1 << 6) | (dw2 >> 2);
|
||||||
|
int mon = ((dw2 & 0x00000003) << 2) | (dw3 >> 6);
|
||||||
|
int d = (dw3 >> 1) & 0x0000001F;
|
||||||
|
int h = ((dw3 & 0x00000001) << 4) | (dw4 >> 4);
|
||||||
|
int min = ((dw4 & 0x0000000F) << 2) | (dw5 >> 6);
|
||||||
|
int s = dw5 & 0x0000003F;
|
||||||
|
|
||||||
|
QDateTime dateTime = QDateTime(QDate(y, mon, d), QTime(h, min, s), Qt::UTC);
|
||||||
|
|
||||||
|
// check for the special "never" datetime
|
||||||
|
if (dateTime == QDateTime(QDate(2999, 12, 28), QTime(23, 59, 59), Qt::UTC)) {
|
||||||
|
return QDateTime();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KeePass1Reader::isMetaStream(const Entry* entry)
|
||||||
|
{
|
||||||
|
return entry->attachments()->keys().contains("bin-stream")
|
||||||
|
&& !entry->notes().isEmpty()
|
||||||
|
&& entry->title() == "Meta-Info"
|
||||||
|
&& entry->username() == "SYSTEM"
|
||||||
|
&& entry->url() == "$"
|
||||||
|
&& entry->iconNumber() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QByteArray KeePass1Key::rawKey() const
|
QByteArray KeePass1Key::rawKey() const
|
||||||
{
|
{
|
||||||
|
@ -19,8 +19,12 @@
|
|||||||
#define KEEPASSX_KEEPASS1READER_H
|
#define KEEPASSX_KEEPASS1READER_H
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
|
#include <QtCore/QDateTime>
|
||||||
|
#include <QtCore/QHash>
|
||||||
|
|
||||||
class Database;
|
class Database;
|
||||||
|
class Entry;
|
||||||
|
class Group;
|
||||||
class SymmetricCipherStream;
|
class SymmetricCipherStream;
|
||||||
class QIODevice;
|
class QIODevice;
|
||||||
|
|
||||||
@ -49,9 +53,16 @@ private:
|
|||||||
qint64 contentPos);
|
qint64 contentPos);
|
||||||
QByteArray key(const QByteArray& password, const QByteArray& keyfileData);
|
QByteArray key(const QByteArray& password, const QByteArray& keyfileData);
|
||||||
bool verifyKey(SymmetricCipherStream* cipherStream);
|
bool verifyKey(SymmetricCipherStream* cipherStream);
|
||||||
|
Group* readGroup(QIODevice* cipherStream);
|
||||||
|
Entry* readEntry(QIODevice* cipherStream);
|
||||||
|
bool constructGroupTree(const QList<Group*> groups);
|
||||||
|
bool parseMetaStream(const Entry* entry);
|
||||||
void raiseError(const QString& str);
|
void raiseError(const QString& str);
|
||||||
|
static QDateTime dateFromPackedStruct(const QByteArray& data);
|
||||||
|
static bool isMetaStream(const Entry* entry);
|
||||||
|
|
||||||
Database* m_db;
|
Database* m_db;
|
||||||
|
Group* m_tmpParent;
|
||||||
QIODevice* m_device;
|
QIODevice* m_device;
|
||||||
quint32 m_encryptionFlags;
|
quint32 m_encryptionFlags;
|
||||||
QByteArray m_masterSeed;
|
QByteArray m_masterSeed;
|
||||||
@ -59,6 +70,9 @@ private:
|
|||||||
QByteArray m_contentHashHeader;
|
QByteArray m_contentHashHeader;
|
||||||
QByteArray m_transformSeed;
|
QByteArray m_transformSeed;
|
||||||
quint32 m_transformRounds;
|
quint32 m_transformRounds;
|
||||||
|
QHash<quint32, Group*> m_groupIds;
|
||||||
|
QHash<Group*, quint32> m_groupLevels;
|
||||||
|
QHash<QByteArray, Entry*> m_entryUuids;
|
||||||
|
|
||||||
bool m_error;
|
bool m_error;
|
||||||
QString m_errorStr;
|
QString m_errorStr;
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
#include "config-keepassx-tests.h"
|
#include "config-keepassx-tests.h"
|
||||||
#include "tests.h"
|
#include "tests.h"
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
|
#include "core/Entry.h"
|
||||||
|
#include "core/Group.h"
|
||||||
#include "crypto/Crypto.h"
|
#include "crypto/Crypto.h"
|
||||||
#include "format/KeePass1Reader.h"
|
#include "format/KeePass1Reader.h"
|
||||||
|
|
||||||
@ -39,7 +41,49 @@ void TestKeePass1Reader::testBasic()
|
|||||||
QVERIFY(db);
|
QVERIFY(db);
|
||||||
QVERIFY(!reader.hasError());
|
QVERIFY(!reader.hasError());
|
||||||
|
|
||||||
|
QCOMPARE(db->rootGroup()->children().size(), 2);
|
||||||
|
|
||||||
|
Group* group1 = db->rootGroup()->children().at(0);
|
||||||
|
QCOMPARE(group1->name(), QString("Internet"));
|
||||||
|
QCOMPARE(group1->iconNumber(), 1);
|
||||||
|
QCOMPARE(group1->entries().size(), 2);
|
||||||
|
|
||||||
|
Entry* entry11 = group1->entries().at(0);
|
||||||
|
QCOMPARE(entry11->title(), QString("Test entry"));
|
||||||
|
QCOMPARE(entry11->iconNumber(), 1);
|
||||||
|
QCOMPARE(entry11->username(), QString("I"));
|
||||||
|
QCOMPARE(entry11->url(), QString("http://example.com/"));
|
||||||
|
QCOMPARE(entry11->password(), QString("secretpassword"));
|
||||||
|
QCOMPARE(entry11->notes(), QString("Lorem ipsum\ndolor sit amet"));
|
||||||
|
QVERIFY(entry11->timeInfo().expires());
|
||||||
|
QCOMPARE(entry11->timeInfo().expiryTime(), genDT(2012, 5, 9, 10, 32));
|
||||||
|
QCOMPARE(entry11->attachments()->keys().size(), 1);
|
||||||
|
QCOMPARE(entry11->attachments()->keys().first(), QString("attachment.txt"));
|
||||||
|
QCOMPARE(entry11->attachments()->value("attachment.txt"), QByteArray("hello world\n"));
|
||||||
|
|
||||||
|
Entry* entry12 = group1->entries().at(1);
|
||||||
|
QCOMPARE(entry12->title(), QString(""));
|
||||||
|
QCOMPARE(entry12->iconNumber(), 0);
|
||||||
|
QCOMPARE(entry12->username(), QString(""));
|
||||||
|
QCOMPARE(entry12->url(), QString(""));
|
||||||
|
QCOMPARE(entry12->password(), QString(""));
|
||||||
|
QCOMPARE(entry12->notes(), QString(""));
|
||||||
|
QVERIFY(!entry12->timeInfo().expires());
|
||||||
|
QCOMPARE(entry12->attachments()->keys().size(), 0);
|
||||||
|
|
||||||
|
Group* group2 = db->rootGroup()->children().at(1);
|
||||||
|
QCOMPARE(group2->name(), QString("eMail"));
|
||||||
|
QCOMPARE(group2->iconNumber(), 19);
|
||||||
|
QCOMPARE(group2->entries().size(), 0);
|
||||||
|
|
||||||
delete db;
|
delete db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDateTime TestKeePass1Reader::genDT(int year, int month, int day, int hour, int min)
|
||||||
|
{
|
||||||
|
QDate date(year, month, day);
|
||||||
|
QTime time(hour, min, 0);
|
||||||
|
return QDateTime(date, time, Qt::UTC);
|
||||||
|
}
|
||||||
|
|
||||||
KEEPASSX_QTEST_CORE_MAIN(TestKeePass1Reader)
|
KEEPASSX_QTEST_CORE_MAIN(TestKeePass1Reader)
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#ifndef KEEPASSX_TESTKEEPASS1READER_H
|
#ifndef KEEPASSX_TESTKEEPASS1READER_H
|
||||||
#define KEEPASSX_TESTKEEPASS1READER_H
|
#define KEEPASSX_TESTKEEPASS1READER_H
|
||||||
|
|
||||||
|
#include <QtCore/QDateTime>
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
|
|
||||||
class TestKeePass1Reader : public QObject
|
class TestKeePass1Reader : public QObject
|
||||||
@ -27,6 +28,9 @@ class TestKeePass1Reader : public QObject
|
|||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void initTestCase();
|
void initTestCase();
|
||||||
void testBasic();
|
void testBasic();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QDateTime genDT(int year, int month, int day, int hour, int min);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_TESTKEEPASS1READER_H
|
#endif // KEEPASSX_TESTKEEPASS1READER_H
|
||||||
|
@ -42,7 +42,7 @@ private Q_SLOTS:
|
|||||||
void cleanupTestCase();
|
void cleanupTestCase();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDateTime genDT(int year, int month, int day, int hour, int min, int second);
|
static QDateTime genDT(int year, int month, int day, int hour, int min, int second);
|
||||||
|
|
||||||
Database* m_db;
|
Database* m_db;
|
||||||
};
|
};
|
||||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user